xref: /illumos-gate/usr/src/uts/common/io/dld/dld_drv.c (revision 45e5fcdea08b7ec8a83dbdd9ece08abb6e5ed569)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2015, Joyent Inc.
24  * Copyright (c) 2017, Joyent, Inc.
25  * Copyright 2025 MNX Cloud, Inc.
26  */
27 
28 /*
29  * Data-Link Driver
30  */
31 
32 #include	<sys/conf.h>
33 #include	<sys/mkdev.h>
34 #include	<sys/modctl.h>
35 #include	<sys/stat.h>
36 #include	<sys/dld_impl.h>
37 #include	<sys/dld_ioc.h>
38 #include	<sys/dls_impl.h>
39 #include	<sys/softmac.h>
40 #include	<sys/mac.h>
41 #include	<sys/mac_ether.h>
42 #include	<sys/mac_client.h>
43 #include	<sys/mac_client_impl.h>
44 #include	<sys/mac_client_priv.h>
45 #include	<inet/common.h>
46 #include	<sys/policy.h>
47 #include	<sys/priv_names.h>
48 #include	<sys/zone.h>
49 #include	<sys/sysmacros.h>
50 
51 static void	drv_init(void);
52 static int	drv_fini(void);
53 
54 static int	drv_getinfo(dev_info_t	*, ddi_info_cmd_t, void *, void **);
55 static int	drv_attach(dev_info_t *, ddi_attach_cmd_t);
56 static int	drv_detach(dev_info_t *, ddi_detach_cmd_t);
57 
58 /*
59  * Secure objects declarations
60  */
61 #define	SECOBJ_WEP_HASHSZ	67
62 static krwlock_t	drv_secobj_lock;
63 static kmem_cache_t	*drv_secobj_cachep;
64 static mod_hash_t	*drv_secobj_hash;
65 static void		drv_secobj_init(void);
66 static void		drv_secobj_fini(void);
67 static int		drv_ioc_setap(datalink_id_t, struct dlautopush *);
68 static int		drv_ioc_getap(datalink_id_t, struct dlautopush *);
69 static int		drv_ioc_clrap(datalink_id_t);
70 
71 
72 /*
73  * The following entry points are private to dld and are used for control
74  * operations only. The entry points exported to mac drivers are defined
75  * in dld_str.c. Refer to the comment on top of dld_str.c for details.
76  */
77 static int	drv_open(dev_t *, int, int, cred_t *);
78 static int	drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
79 
80 static dev_info_t	*dld_dip;	/* dev_info_t for the driver */
81 uint32_t		dld_opt = 0;	/* Global options */
82 
83 #define	NAUTOPUSH 32
84 static mod_hash_t *dld_ap_hashp;
85 static krwlock_t dld_ap_hash_lock;
86 
87 static struct cb_ops drv_cb_ops = {
88 	drv_open,		/* open */
89 	nulldev,		/* close */
90 	nulldev,		/* strategy */
91 	nulldev,		/* print */
92 	nodev,			/* dump */
93 	nodev,			/* read */
94 	nodev,			/* write */
95 	drv_ioctl,		/* ioctl */
96 	nodev,			/* devmap */
97 	nodev,			/* mmap */
98 	nodev,			/* segmap */
99 	nochpoll,		/* poll */
100 	ddi_prop_op,		/* cb_prop_op */
101 	0,			/* streamtab  */
102 	D_MP			/* Driver compatibility flag */
103 };
104 
105 static struct dev_ops drv_ops = {
106 	DEVO_REV,		/* devo_rev */
107 	0,			/* refcnt */
108 	drv_getinfo,		/* get_dev_info */
109 	nulldev,		/* identify */
110 	nulldev,		/* probe */
111 	drv_attach,		/* attach */
112 	drv_detach,		/* detach */
113 	nodev,			/* reset */
114 	&drv_cb_ops,		/* driver operations */
115 	NULL,			/* bus operations */
116 	nodev,			/* dev power */
117 	ddi_quiesce_not_supported,	/* dev quiesce */
118 };
119 
120 /*
121  * Module linkage information for the kernel.
122  */
123 static	struct modldrv		drv_modldrv = {
124 	&mod_driverops,
125 	DLD_INFO,
126 	&drv_ops
127 };
128 
129 static	struct modlinkage	drv_modlinkage = {
130 	MODREV_1,
131 	&drv_modldrv,
132 	NULL
133 };
134 
135 int
_init(void)136 _init(void)
137 {
138 	return (mod_install(&drv_modlinkage));
139 }
140 
141 int
_fini(void)142 _fini(void)
143 {
144 	return (mod_remove(&drv_modlinkage));
145 }
146 
147 int
_info(struct modinfo * modinfop)148 _info(struct modinfo *modinfop)
149 {
150 	return (mod_info(&drv_modlinkage, modinfop));
151 }
152 
153 /*
154  * Initialize component modules.
155  */
156 static void
drv_init(void)157 drv_init(void)
158 {
159 	drv_secobj_init();
160 	dld_str_init();
161 
162 	/*
163 	 * Create a hash table for autopush configuration.
164 	 */
165 	dld_ap_hashp = mod_hash_create_idhash("dld_autopush_hash",
166 	    NAUTOPUSH, mod_hash_null_valdtor);
167 
168 	ASSERT(dld_ap_hashp != NULL);
169 	rw_init(&dld_ap_hash_lock, NULL, RW_DRIVER, NULL);
170 }
171 
172 /* ARGSUSED */
173 static uint_t
drv_ap_exist(mod_hash_key_t key,mod_hash_val_t * val,void * arg)174 drv_ap_exist(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
175 {
176 	boolean_t *pexist = arg;
177 
178 	*pexist = B_TRUE;
179 	return (MH_WALK_TERMINATE);
180 }
181 
182 static int
drv_fini(void)183 drv_fini(void)
184 {
185 	int		err;
186 	boolean_t	exist = B_FALSE;
187 
188 	rw_enter(&dld_ap_hash_lock, RW_READER);
189 	mod_hash_walk(dld_ap_hashp, drv_ap_exist, &exist);
190 	rw_exit(&dld_ap_hash_lock);
191 	if (exist)
192 		return (EBUSY);
193 
194 	if ((err = dld_str_fini()) != 0)
195 		return (err);
196 
197 	drv_secobj_fini();
198 	mod_hash_destroy_idhash(dld_ap_hashp);
199 	rw_destroy(&dld_ap_hash_lock);
200 	return (0);
201 }
202 
203 /*
204  * devo_getinfo: getinfo(9e)
205  */
206 /*ARGSUSED*/
207 static int
drv_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** resp)208 drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp)
209 {
210 	if (dld_dip == NULL)
211 		return (DDI_FAILURE);
212 
213 	switch (cmd) {
214 	case DDI_INFO_DEVT2INSTANCE:
215 		*resp = 0;
216 		break;
217 	case DDI_INFO_DEVT2DEVINFO:
218 		*resp = dld_dip;
219 		break;
220 	default:
221 		return (DDI_FAILURE);
222 	}
223 
224 	return (DDI_SUCCESS);
225 }
226 
227 /*
228  * Check properties to set options. (See dld.h for property definitions).
229  */
230 static void
drv_set_opt(dev_info_t * dip)231 drv_set_opt(dev_info_t *dip)
232 {
233 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
234 	    DLD_PROP_NO_FASTPATH, 0) != 0) {
235 		dld_opt |= DLD_OPT_NO_FASTPATH;
236 	}
237 
238 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
239 	    DLD_PROP_NO_POLL, 0) != 0) {
240 		dld_opt |= DLD_OPT_NO_POLL;
241 	}
242 
243 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
244 	    DLD_PROP_NO_ZEROCOPY, 0) != 0) {
245 		dld_opt |= DLD_OPT_NO_ZEROCOPY;
246 	}
247 
248 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
249 	    DLD_PROP_NO_SOFTRING, 0) != 0) {
250 		dld_opt |= DLD_OPT_NO_SOFTRING;
251 	}
252 }
253 
254 /*
255  * devo_attach: attach(9e)
256  */
257 static int
drv_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)258 drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
259 {
260 	if (cmd != DDI_ATTACH)
261 		return (DDI_FAILURE);
262 
263 	ASSERT(ddi_get_instance(dip) == 0);
264 	drv_init();
265 	drv_set_opt(dip);
266 
267 	/*
268 	 * Create control node. DLPI provider nodes will be created on demand.
269 	 */
270 	if (ddi_create_minor_node(dip, DLD_CONTROL_MINOR_NAME, S_IFCHR,
271 	    DLD_CONTROL_MINOR, DDI_PSEUDO, 0) != DDI_SUCCESS)
272 		return (DDI_FAILURE);
273 
274 	dld_dip = dip;
275 
276 	/*
277 	 * Log the fact that the driver is now attached.
278 	 */
279 	ddi_report_dev(dip);
280 	return (DDI_SUCCESS);
281 }
282 
283 /*
284  * devo_detach: detach(9e)
285  */
286 static int
drv_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)287 drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
288 {
289 	if (cmd != DDI_DETACH)
290 		return (DDI_FAILURE);
291 
292 	ASSERT(dld_dip == dip);
293 	if (drv_fini() != 0)
294 		return (DDI_FAILURE);
295 
296 	/*
297 	 * Remove the control node.
298 	 */
299 	ddi_remove_minor_node(dip, DLD_CONTROL_MINOR_NAME);
300 	dld_dip = NULL;
301 
302 	return (DDI_SUCCESS);
303 }
304 
305 /*
306  * dld control node open procedure.
307  */
308 /*ARGSUSED*/
309 static int
drv_open(dev_t * devp,int flag,int sflag,cred_t * credp)310 drv_open(dev_t *devp, int flag, int sflag, cred_t *credp)
311 {
312 	/*
313 	 * Only the control node can be opened.
314 	 */
315 	if (getminor(*devp) != DLD_CONTROL_MINOR)
316 		return (ENODEV);
317 	return (0);
318 }
319 
320 /*
321  * Verify if the caller is allowed to modify a link of the given class.
322  */
323 static int
drv_ioc_checkprivs(datalink_class_t class,cred_t * cred)324 drv_ioc_checkprivs(datalink_class_t class, cred_t *cred)
325 {
326 	if (class == DATALINK_CLASS_IPTUN)
327 		return (secpolicy_iptun_config(cred));
328 	return (secpolicy_dl_config(cred));
329 }
330 
331 /*
332  * DLDIOC_ATTR
333  */
334 /* ARGSUSED */
335 static int
drv_ioc_attr(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)336 drv_ioc_attr(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
337 {
338 	dld_ioc_attr_t		*diap = karg;
339 	dls_dl_handle_t		dlh;
340 	dls_link_t		*dlp;
341 	zoneid_t		zoneid = crgetzoneid(cred);
342 	int			err;
343 	mac_perim_handle_t	mph;
344 
345 	if (zoneid != GLOBAL_ZONEID &&
346 	    zone_check_datalink(&zoneid, diap->dia_linkid) != 0)
347 		return (ENOENT);
348 
349 	if ((err = dls_devnet_hold_tmp(diap->dia_linkid, &dlh)) != 0)
350 		return (err);
351 
352 	if ((err = mac_perim_enter_by_macname(dls_devnet_mac(dlh),
353 	    &mph)) != 0) {
354 		dls_devnet_rele_tmp(dlh);
355 		return (err);
356 	}
357 
358 	if ((err = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0) {
359 		mac_perim_exit(mph);
360 		dls_devnet_rele_tmp(dlh);
361 		return (err);
362 	}
363 
364 	mac_sdu_get(dlp->dl_mh, NULL, &diap->dia_max_sdu);
365 	dls_link_rele(dlp);
366 	mac_perim_exit(mph);
367 	dls_devnet_rele_tmp(dlh);
368 
369 	return (0);
370 }
371 
372 /*
373  * DLDIOC_PHYS_ATTR
374  */
375 /* ARGSUSED */
376 static int
drv_ioc_phys_attr(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)377 drv_ioc_phys_attr(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
378 {
379 	dld_ioc_phys_attr_t	*dipp = karg;
380 	int			err;
381 	dls_dl_handle_t		dlh;
382 	dls_dev_handle_t	ddh;
383 	dev_t			phydev;
384 	zoneid_t		zoneid = crgetzoneid(cred);
385 
386 	if (zoneid != GLOBAL_ZONEID &&
387 	    zone_check_datalink(&zoneid, dipp->dip_linkid) != 0)
388 		return (ENOENT);
389 
390 	/*
391 	 * Every physical link should have its physical dev_t kept in the
392 	 * daemon. If not, it is not a valid physical link.
393 	 */
394 	if (dls_mgmt_get_phydev(dipp->dip_linkid, &phydev) != 0)
395 		return (EINVAL);
396 
397 	/*
398 	 * Although this is a valid physical link, it might already be removed
399 	 * by DR or during system shutdown. softmac_hold_device() would return
400 	 * ENOENT in this case.
401 	 */
402 	if ((err = softmac_hold_device(phydev, &ddh)) != 0)
403 		return (err);
404 
405 	if (dls_devnet_hold_tmp(dipp->dip_linkid, &dlh) != 0) {
406 		/*
407 		 * Although this is an active physical link, its link type is
408 		 * not supported by GLDv3, and therefore it does not have
409 		 * vanity naming support.
410 		 */
411 		dipp->dip_novanity = B_TRUE;
412 	} else {
413 		dipp->dip_novanity = B_FALSE;
414 		dls_devnet_rele_tmp(dlh);
415 	}
416 	/*
417 	 * Get the physical device name from the major number and the instance
418 	 * number derived from phydev.
419 	 */
420 	(void) snprintf(dipp->dip_dev, MAXLINKNAMELEN, "%s%d",
421 	    ddi_major_to_name(getmajor(phydev)), getminor(phydev) - 1);
422 
423 	softmac_rele_device(ddh);
424 	return (0);
425 }
426 
427 /* ARGSUSED */
428 static int
drv_ioc_hwgrpget(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)429 drv_ioc_hwgrpget(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
430 {
431 	dld_ioc_hwgrpget_t	*hwgrpp = karg;
432 	dld_hwgrpinfo_t		hwgrp, *hip;
433 	mac_handle_t		mh = NULL;
434 	int			i, err, rgrpnum, tgrpnum;
435 	uint_t			bytes_left;
436 	int			totgrps = 0;
437 	zoneid_t		zoneid = crgetzoneid(cred);
438 
439 	if (zoneid != GLOBAL_ZONEID &&
440 	    zone_check_datalink(&zoneid, hwgrpp->dih_linkid) != 0)
441 		return (ENOENT);
442 
443 	hwgrpp->dih_n_groups = 0;
444 	err = mac_open_by_linkid(hwgrpp->dih_linkid, &mh);
445 	if (err != 0)
446 		goto done;
447 
448 	hip = (dld_hwgrpinfo_t *)
449 	    ((uchar_t *)arg + sizeof (dld_ioc_hwgrpget_t));
450 	bytes_left = hwgrpp->dih_size;
451 
452 	rgrpnum = mac_hwgrp_num(mh, MAC_RING_TYPE_RX);
453 	/* display the default group information first */
454 	if (rgrpnum > 0) {
455 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
456 			err = ENOSPC;
457 			goto done;
458 		}
459 
460 		bzero(&hwgrp, sizeof (hwgrp));
461 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
462 		    sizeof (hwgrp.dhi_link_name));
463 		mac_get_hwrxgrp_info(mh, 0, &hwgrp.dhi_grp_num,
464 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
465 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
466 		if (hwgrp.dhi_n_rings != 0) {
467 			if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
468 				err = EFAULT;
469 				goto done;
470 			}
471 		}
472 		hip++;
473 		totgrps++;
474 		bytes_left -= sizeof (dld_hwgrpinfo_t);
475 	}
476 
477 	tgrpnum = mac_hwgrp_num(mh, MAC_RING_TYPE_TX);
478 	/* display the default group information first */
479 	if (tgrpnum > 0) {
480 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
481 			err = ENOSPC;
482 			goto done;
483 		}
484 
485 		bzero(&hwgrp, sizeof (hwgrp));
486 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
487 		    sizeof (hwgrp.dhi_link_name));
488 		mac_get_hwtxgrp_info(mh, tgrpnum - 1, &hwgrp.dhi_grp_num,
489 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
490 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
491 		if (hwgrp.dhi_n_rings != 0) {
492 			if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
493 				err = EFAULT;
494 				goto done;
495 			}
496 		}
497 		hip++;
498 		totgrps++;
499 		bytes_left -= sizeof (dld_hwgrpinfo_t);
500 	}
501 
502 	/* Rest of the rx groups */
503 	for (i = 1; i < rgrpnum; i++) {
504 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
505 			err = ENOSPC;
506 			goto done;
507 		}
508 
509 		bzero(&hwgrp, sizeof (hwgrp));
510 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
511 		    sizeof (hwgrp.dhi_link_name));
512 		mac_get_hwrxgrp_info(mh, i, &hwgrp.dhi_grp_num,
513 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
514 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
515 		if (hwgrp.dhi_n_rings == 0)
516 			continue;
517 		if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
518 			err = EFAULT;
519 			goto done;
520 		}
521 
522 		hip++;
523 		totgrps++;
524 		bytes_left -= sizeof (dld_hwgrpinfo_t);
525 	}
526 
527 	/* Rest of the tx group */
528 	tgrpnum = mac_hwgrp_num(mh, MAC_RING_TYPE_TX);
529 	for (i = 0; i < tgrpnum - 1; i++) {
530 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
531 			err = ENOSPC;
532 			goto done;
533 		}
534 
535 		bzero(&hwgrp, sizeof (hwgrp));
536 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
537 		    sizeof (hwgrp.dhi_link_name));
538 		mac_get_hwtxgrp_info(mh, i, &hwgrp.dhi_grp_num,
539 		    &hwgrp.dhi_n_rings, hwgrp.dhi_rings, &hwgrp.dhi_grp_type,
540 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
541 		if (hwgrp.dhi_n_rings == 0)
542 			continue;
543 		if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
544 			err = EFAULT;
545 			goto done;
546 		}
547 
548 		hip++;
549 		totgrps++;
550 		bytes_left -= sizeof (dld_hwgrpinfo_t);
551 	}
552 
553 done:
554 	if (mh != NULL)
555 		dld_mac_close(mh);
556 	if (err == 0)
557 		hwgrpp->dih_n_groups = totgrps;
558 	return (err);
559 }
560 
561 /* ARGSUSED */
562 static int
drv_ioc_macaddrget(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)563 drv_ioc_macaddrget(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
564 {
565 	dld_ioc_macaddrget_t	*magp = karg;
566 	dld_macaddrinfo_t	mai, *maip;
567 	mac_handle_t		mh = NULL;
568 	int			i, err;
569 	uint_t			bytes_left;
570 	boolean_t		is_used;
571 	zoneid_t		zoneid = crgetzoneid(cred);
572 
573 	if (zoneid != GLOBAL_ZONEID &&
574 	    zone_check_datalink(&zoneid, magp->dig_linkid) != 0)
575 		return (ENOENT);
576 
577 	magp->dig_count = 0;
578 	err = mac_open_by_linkid(magp->dig_linkid, &mh);
579 	if (err != 0)
580 		goto done;
581 
582 	maip = (dld_macaddrinfo_t *)
583 	    ((uchar_t *)arg + sizeof (dld_ioc_macaddrget_t));
584 	bytes_left = magp->dig_size;
585 
586 	for (i = 0; i < mac_addr_factory_num(mh) + 1; i++) {
587 		if (sizeof (dld_macaddrinfo_t) > bytes_left) {
588 			err = ENOSPC;
589 			goto done;
590 		}
591 
592 		bzero(&mai, sizeof (mai));
593 
594 		if (i == 0) {
595 			/* primary MAC address */
596 			mac_unicast_primary_get(mh, mai.dmi_addr);
597 			mai.dmi_addrlen = mac_addr_len(mh);
598 			mac_unicast_primary_info(mh, mai.dmi_client_name,
599 			    &is_used);
600 		} else {
601 			/* factory MAC address slot */
602 			mac_addr_factory_value(mh, i, mai.dmi_addr,
603 			    &mai.dmi_addrlen, mai.dmi_client_name, &is_used);
604 		}
605 
606 		mai.dmi_slot = i;
607 		if (is_used)
608 			mai.dmi_flags |= DLDIOCMACADDR_USED;
609 
610 		if (copyout(&mai, maip, sizeof (mai)) != 0) {
611 			err = EFAULT;
612 			goto done;
613 		}
614 
615 		maip++;
616 		bytes_left -= sizeof (dld_macaddrinfo_t);
617 	}
618 
619 done:
620 	if (mh != NULL)
621 		dld_mac_close(mh);
622 	if (err == 0)
623 		magp->dig_count = mac_addr_factory_num(mh) + 1;
624 	return (err);
625 }
626 
627 /*
628  * Calculate caller's allowed permissions to a property.
629  */
630 static uint_t
dld_macprop_perm_mask(dls_dl_handle_t dlh,const dld_ioc_macprop_t * prop,const cred_t * cred)631 dld_macprop_perm_mask(dls_dl_handle_t dlh, const dld_ioc_macprop_t *prop,
632     const cred_t *cred)
633 {
634 	const zoneid_t zid = crgetzoneid(cred);
635 	const boolean_t is_owner = zid == dls_devnet_getownerzid(dlh);
636 	const boolean_t is_in_zone = zid == dls_devnet_getzid(dlh);
637 	const boolean_t is_in_global = zid == GLOBAL_ZONEID;
638 	uint_t mask = 0;
639 
640 	/* If the zone owns the datalink device, it should have full access. */
641 	if (is_owner) {
642 		mask = MAC_PROP_PERM_RW;
643 	}
644 
645 	switch (prop->pr_num) {
646 	case MAC_PROP_MTU:
647 	case MAC_PROP_STATUS:
648 		/*
649 		 * These properties remain readable inside a zone which has been
650 		 * assigned (but does not own) a datalink device.
651 		 */
652 		if (is_in_zone) {
653 			mask |= MAC_PROP_PERM_READ;
654 		}
655 		break;
656 	case MAC_PROP_ZONE:
657 		/*
658 		 * Actors outside global zone are not allowed to change the zone
659 		 * ownership property.
660 		 */
661 		if (!is_in_global) {
662 			mask &= ~MAC_PROP_PERM_WRITE;
663 		}
664 		break;
665 	default:
666 		break;
667 	}
668 	return (mask);
669 }
670 
671 /*
672  * DLDIOC_SET/GETMACPROP
673  */
674 static int
drv_ioc_prop_common(dld_ioc_macprop_t * prop,intptr_t arg,boolean_t set,cred_t * cred,int mode)675 drv_ioc_prop_common(dld_ioc_macprop_t *prop, intptr_t arg, boolean_t set,
676     cred_t *cred, int mode)
677 {
678 	int			err = EINVAL;
679 	dls_dl_handle_t		dlh = NULL;
680 	dls_link_t		*dlp = NULL;
681 	mac_perim_handle_t	mph = NULL;
682 	dld_ioc_macprop_t	*kprop;
683 	datalink_id_t		linkid;
684 	datalink_class_t	class;
685 	uint_t			dsize;
686 
687 	/*
688 	 * We only use pr_valsize from prop, as the caller only did a
689 	 * copyin() for sizeof (dld_ioc_prop_t), which doesn't cover
690 	 * the property data.  We copyin the full dld_ioc_prop_t
691 	 * including the data into kprop down below.
692 	 */
693 	dsize = sizeof (dld_ioc_macprop_t) + prop->pr_valsize - 1;
694 	if (dsize < prop->pr_valsize)
695 		return (EINVAL);
696 
697 	/*
698 	 * The property data is variable size, so we need to allocate
699 	 * a buffer for kernel use as this data was not part of the
700 	 * prop allocation and copyin() done by the framework.
701 	 */
702 	if ((kprop = kmem_alloc(dsize, KM_NOSLEEP)) == NULL)
703 		return (ENOMEM);
704 
705 	if (ddi_copyin((void *)arg, kprop, dsize, mode) != 0) {
706 		err = EFAULT;
707 		goto done;
708 	}
709 
710 	linkid = kprop->pr_linkid;
711 
712 	if (set) {
713 		if ((err = dls_mgmt_get_linkinfo(linkid, NULL, &class, NULL,
714 		    NULL)) != 0 || (err = drv_ioc_checkprivs(class, cred)) != 0)
715 			goto done;
716 	}
717 
718 	if ((err = dls_devnet_hold_tmp(linkid, &dlh)) != 0)
719 		goto done;
720 	if ((err = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
721 		goto done;
722 	if ((err = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
723 		goto done;
724 
725 	const uint_t perm_req = set ? MAC_PROP_PERM_RW : MAC_PROP_PERM_READ;
726 	const uint_t perm_mask = dld_macprop_perm_mask(dlh, kprop, cred);
727 	if ((perm_req & perm_mask) != perm_req) {
728 		err = EACCES;
729 		goto done;
730 	}
731 
732 	if (!mac_prop_check_size(kprop->pr_num, kprop->pr_valsize,
733 	    kprop->pr_flags & DLD_PROP_POSSIBLE)) {
734 		err = ENOBUFS;
735 		goto done;
736 	}
737 
738 	switch (kprop->pr_num) {
739 	case MAC_PROP_ZONE:
740 		if (set) {
741 			dld_ioc_zid_t *dzp = (dld_ioc_zid_t *)kprop->pr_val;
742 
743 			err = dls_devnet_setzid(dlh, dzp->diz_zid);
744 		} else {
745 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
746 			(*(zoneid_t *)kprop->pr_val) = dls_devnet_getzid(dlh);
747 		}
748 		break;
749 	case MAC_PROP_AUTOPUSH: {
750 		struct dlautopush *dlap = (struct dlautopush *)kprop->pr_val;
751 
752 		if (set) {
753 			if (kprop->pr_valsize != 0)
754 				err = drv_ioc_setap(linkid, dlap);
755 			else
756 				err = drv_ioc_clrap(linkid);
757 		} else {
758 			/*
759 			 * You might think that the earlier call to
760 			 * mac_prop_check_size() should catch this but
761 			 * it can't. The autopush prop uses 0 as a
762 			 * sentinel value to clear the prop. This
763 			 * check ensures we don't allow a get with a
764 			 * valsize of 0.
765 			 */
766 			if (kprop->pr_valsize == 0) {
767 				err = ENOBUFS;
768 				goto done;
769 			}
770 
771 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
772 			err = drv_ioc_getap(linkid, dlap);
773 		}
774 		break;
775 	}
776 	case MAC_PROP_TAGMODE:
777 		if (set) {
778 			link_tagmode_t mode = *(link_tagmode_t *)kprop->pr_val;
779 
780 			if (mode != LINK_TAGMODE_VLANONLY &&
781 			    mode != LINK_TAGMODE_NORMAL) {
782 				err = EINVAL;
783 			} else {
784 				dlp->dl_tagmode = mode;
785 				err = 0;
786 			}
787 		} else {
788 			*(link_tagmode_t *)kprop->pr_val = dlp->dl_tagmode;
789 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
790 			err = 0;
791 		}
792 		break;
793 	default: {
794 		mac_propval_range_t *rangep = NULL;
795 		void *default_val = NULL;
796 		uint_t default_size = 0;
797 
798 		/* set a property value */
799 		if (set) {
800 			err = mac_set_prop(dlp->dl_mh, kprop->pr_num,
801 			    kprop->pr_name, kprop->pr_val, kprop->pr_valsize);
802 			break;
803 		}
804 
805 		/*
806 		 * Get the property value, default, or possible value
807 		 * depending on flags passed from the user.
808 		 */
809 
810 		/* a property has RW permissions by default */
811 		kprop->pr_perm_flags = MAC_PROP_PERM_RW;
812 
813 		if (kprop->pr_flags & DLD_PROP_POSSIBLE) {
814 			rangep = (mac_propval_range_t *)kprop->pr_val;
815 
816 			/*
817 			 * fail if rangep is not aligned to first
818 			 * member of mac_propval_range_t.
819 			 */
820 			ASSERT(IS_P2ALIGNED(rangep, sizeof (uint_t)));
821 		} else if (kprop->pr_flags & DLD_PROP_DEFAULT) {
822 			default_val = kprop->pr_val;
823 			default_size = kprop->pr_valsize;
824 		}
825 
826 		/*
827 		 * Always return the permissions, and optionally return
828 		 * the default value or possible values range.
829 		 */
830 		err = mac_prop_info(dlp->dl_mh, kprop->pr_num, kprop->pr_name,
831 		    default_val, default_size, rangep, &kprop->pr_perm_flags);
832 		if (err != 0)
833 			goto done;
834 
835 		if (default_val == NULL && rangep == NULL) {
836 			err = mac_get_prop(dlp->dl_mh, kprop->pr_num,
837 			    kprop->pr_name, kprop->pr_val, kprop->pr_valsize);
838 		}
839 	}
840 	}
841 
842 	/* Properly communicate allowed permissions */
843 	kprop->pr_perm_flags &= perm_mask;
844 
845 done:
846 	if (!set && ddi_copyout(kprop, (void *)arg, dsize, mode) != 0)
847 		err = EFAULT;
848 
849 	if (dlp != NULL)
850 		dls_link_rele(dlp);
851 
852 	if (mph != NULL) {
853 		int32_t	cpuid;
854 		void	*mdip = NULL;
855 
856 		if (dlp != NULL && set && err == 0) {
857 			cpuid = mac_client_intr_cpu(dlp->dl_mch);
858 			mdip = mac_get_devinfo(dlp->dl_mh);
859 		}
860 
861 		mac_perim_exit(mph);
862 
863 		if (mdip != NULL && cpuid != -1)
864 			mac_client_set_intr_cpu(mdip, dlp->dl_mch, cpuid);
865 	}
866 
867 	if (dlh != NULL)
868 		dls_devnet_rele_tmp(dlh);
869 
870 	if (kprop != NULL)
871 		kmem_free(kprop, dsize);
872 	return (err);
873 }
874 
875 /* ARGSUSED */
876 static int
drv_ioc_setprop(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)877 drv_ioc_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
878 {
879 	return (drv_ioc_prop_common(karg, arg, B_TRUE, cred, mode));
880 }
881 
882 /* ARGSUSED */
883 static int
drv_ioc_getprop(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)884 drv_ioc_getprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
885 {
886 	return (drv_ioc_prop_common(karg, arg, B_FALSE, cred, mode));
887 }
888 
889 /*
890  * DLDIOC_RENAME.
891  *
892  * This function handles two cases of link renaming. See more in comments above
893  * dls_datalink_rename().
894  */
895 /* ARGSUSED */
896 static int
drv_ioc_rename(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)897 drv_ioc_rename(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
898 {
899 	dld_ioc_rename_t	*dir = karg;
900 	mod_hash_key_t		key;
901 	mod_hash_val_t		val;
902 	zoneid_t		zoneid = crgetzoneid(cred);
903 	datalink_class_t	class;
904 	int			err;
905 
906 	if (zoneid != GLOBAL_ZONEID &&
907 	    (zone_check_datalink(&zoneid, dir->dir_linkid1) != 0 ||
908 	    dir->dir_linkid2 != DATALINK_INVALID_LINKID &&
909 	    zone_check_datalink(&zoneid, dir->dir_linkid2) != 0))
910 		return (ENOENT);
911 
912 	if ((err = dls_mgmt_get_linkinfo(dir->dir_linkid1, NULL, &class, NULL,
913 	    NULL)) != 0)
914 		return (err);
915 
916 	if ((err = drv_ioc_checkprivs(class, cred)) != 0)
917 		return (err);
918 
919 	if ((err = dls_devnet_rename(dir->dir_linkid1, dir->dir_linkid2,
920 	    dir->dir_link)) != 0)
921 		return (err);
922 
923 	if (dir->dir_linkid2 == DATALINK_INVALID_LINKID)
924 		return (0);
925 
926 	/*
927 	 * if dir_linkid2 is not DATALINK_INVALID_LINKID, it means this
928 	 * renaming request is to rename a valid physical link (dir_linkid1)
929 	 * to a "removed" physical link (dir_linkid2, which is removed by DR
930 	 * or during system shutdown). In this case, the link (specified by
931 	 * dir_linkid1) would inherit all the configuration of dir_linkid2,
932 	 * and dir_linkid1 and its configuration would be lost.
933 	 *
934 	 * Remove per-link autopush configuration of dir_linkid1 in this case.
935 	 */
936 	key = (mod_hash_key_t)(uintptr_t)dir->dir_linkid1;
937 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
938 	if (mod_hash_find(dld_ap_hashp, key, &val) != 0) {
939 		rw_exit(&dld_ap_hash_lock);
940 		return (0);
941 	}
942 
943 	VERIFY(mod_hash_remove(dld_ap_hashp, key, &val) == 0);
944 	kmem_free(val, sizeof (dld_ap_t));
945 	rw_exit(&dld_ap_hash_lock);
946 	return (0);
947 }
948 
949 static int
drv_ioc_setap(datalink_id_t linkid,struct dlautopush * dlap)950 drv_ioc_setap(datalink_id_t linkid, struct dlautopush *dlap)
951 {
952 	dld_ap_t	*dap;
953 	int		i;
954 	mod_hash_key_t	key;
955 
956 	if (dlap->dap_npush == 0 || dlap->dap_npush > MAXAPUSH)
957 		return (EINVAL);
958 
959 	/*
960 	 * Validate that the specified list of modules exist.
961 	 */
962 	for (i = 0; i < dlap->dap_npush; i++) {
963 		if (fmodsw_find(dlap->dap_aplist[i], FMODSW_LOAD) == NULL)
964 			return (EINVAL);
965 	}
966 
967 
968 	key = (mod_hash_key_t)(uintptr_t)linkid;
969 
970 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
971 	if (mod_hash_find(dld_ap_hashp, key, (mod_hash_val_t *)&dap) != 0) {
972 		dap = kmem_zalloc(sizeof (dld_ap_t), KM_NOSLEEP);
973 		if (dap == NULL) {
974 			rw_exit(&dld_ap_hash_lock);
975 			return (ENOMEM);
976 		}
977 
978 		dap->da_linkid = linkid;
979 		VERIFY(mod_hash_insert(dld_ap_hashp, key,
980 		    (mod_hash_val_t)dap) == 0);
981 	}
982 
983 	/*
984 	 * Update the configuration.
985 	 */
986 	dap->da_anchor = dlap->dap_anchor;
987 	dap->da_npush = dlap->dap_npush;
988 	for (i = 0; i < dlap->dap_npush; i++) {
989 		(void) strlcpy(dap->da_aplist[i], dlap->dap_aplist[i],
990 		    FMNAMESZ + 1);
991 	}
992 	rw_exit(&dld_ap_hash_lock);
993 
994 	return (0);
995 }
996 
997 static int
drv_ioc_getap(datalink_id_t linkid,struct dlautopush * dlap)998 drv_ioc_getap(datalink_id_t linkid, struct dlautopush *dlap)
999 {
1000 	dld_ap_t	*dap;
1001 	int		i;
1002 
1003 	rw_enter(&dld_ap_hash_lock, RW_READER);
1004 	if (mod_hash_find(dld_ap_hashp,
1005 	    (mod_hash_key_t)(uintptr_t)linkid,
1006 	    (mod_hash_val_t *)&dap) != 0) {
1007 		rw_exit(&dld_ap_hash_lock);
1008 		dlap->dap_npush = 0;
1009 		return (0);
1010 	}
1011 
1012 	/*
1013 	 * Retrieve the configuration.
1014 	 */
1015 	dlap->dap_anchor = dap->da_anchor;
1016 	dlap->dap_npush = dap->da_npush;
1017 	for (i = 0; i < dap->da_npush; i++) {
1018 		(void) strlcpy(dlap->dap_aplist[i], dap->da_aplist[i],
1019 		    FMNAMESZ + 1);
1020 	}
1021 	rw_exit(&dld_ap_hash_lock);
1022 
1023 	return (0);
1024 }
1025 
1026 static int
drv_ioc_clrap(datalink_id_t linkid)1027 drv_ioc_clrap(datalink_id_t linkid)
1028 {
1029 	mod_hash_val_t	val;
1030 	mod_hash_key_t	key;
1031 
1032 	key = (mod_hash_key_t)(uintptr_t)linkid;
1033 
1034 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
1035 	if (mod_hash_find(dld_ap_hashp, key, &val) != 0) {
1036 		rw_exit(&dld_ap_hash_lock);
1037 		return (0);
1038 	}
1039 
1040 	VERIFY(mod_hash_remove(dld_ap_hashp, key, &val) == 0);
1041 	kmem_free(val, sizeof (dld_ap_t));
1042 	rw_exit(&dld_ap_hash_lock);
1043 	return (0);
1044 }
1045 
1046 /*
1047  * DLDIOC_DOORSERVER
1048  */
1049 /* ARGSUSED */
1050 static int
drv_ioc_doorserver(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1051 drv_ioc_doorserver(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1052 {
1053 	dld_ioc_door_t	*did = karg;
1054 
1055 	return (dls_mgmt_door_set(did->did_start_door));
1056 }
1057 
1058 /*
1059  * DLDIOC_USAGELOG
1060  */
1061 /* ARGSUSED */
1062 static int
drv_ioc_usagelog(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1063 drv_ioc_usagelog(void *karg, intptr_t arg, int mode, cred_t *cred,
1064     int *rvalp)
1065 {
1066 	dld_ioc_usagelog_t	*log_info = (dld_ioc_usagelog_t *)karg;
1067 	int			err = 0;
1068 
1069 	if (log_info->ul_type < MAC_LOGTYPE_LINK ||
1070 	    log_info->ul_type > MAC_LOGTYPE_FLOW)
1071 		return (EINVAL);
1072 
1073 	if (log_info->ul_onoff) {
1074 		err = mac_start_logusage(log_info->ul_type,
1075 		    log_info->ul_interval);
1076 	} else {
1077 		mac_stop_logusage(log_info->ul_type);
1078 	}
1079 	return (err);
1080 }
1081 
1082 /*
1083  * Process a DLDIOC_ADDFLOW request.
1084  */
1085 /* ARGSUSED */
1086 static int
drv_ioc_addflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1087 drv_ioc_addflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1088 {
1089 	dld_ioc_addflow_t	*afp = karg;
1090 
1091 	return (dld_add_flow(afp->af_linkid, afp->af_name,
1092 	    &afp->af_flow_desc, &afp->af_resource_props));
1093 }
1094 
1095 /*
1096  * Process a DLDIOC_REMOVEFLOW request.
1097  */
1098 /* ARGSUSED */
1099 static int
drv_ioc_removeflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1100 drv_ioc_removeflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1101 {
1102 	dld_ioc_removeflow_t	*rfp = karg;
1103 
1104 	return (dld_remove_flow(rfp->rf_name));
1105 }
1106 
1107 /*
1108  * Process a DLDIOC_MODIFYFLOW request.
1109  */
1110 /* ARGSUSED */
1111 static int
drv_ioc_modifyflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1112 drv_ioc_modifyflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1113 {
1114 	dld_ioc_modifyflow_t	*mfp = karg;
1115 
1116 	return (dld_modify_flow(mfp->mf_name, &mfp->mf_resource_props));
1117 }
1118 
1119 /*
1120  * Process a DLDIOC_WALKFLOW request.
1121  */
1122 /* ARGSUSED */
1123 static int
drv_ioc_walkflow(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1124 drv_ioc_walkflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1125 {
1126 	dld_ioc_walkflow_t	*wfp = karg;
1127 
1128 	return (dld_walk_flow(wfp, arg, cred));
1129 }
1130 
1131 /*
1132  * Check for GLDv3 autopush information.  There are three cases:
1133  *
1134  *   1. If devp points to a GLDv3 datalink and it has autopush configuration,
1135  *	fill dlap in with that information and return 0.
1136  *
1137  *   2. If devp points to a GLDv3 datalink but it doesn't have autopush
1138  *	configuration, then replace devp with the physical device (if one
1139  *	exists) and return 1.  This allows stropen() to find the old-school
1140  *	per-driver autopush configuration.  (For softmac, the result is that
1141  *	the softmac dev_t is replaced with the legacy device's dev_t).
1142  *
1143  *   3. If neither of the above apply, don't touch the args and return -1.
1144  */
1145 int
dld_autopush(dev_t * devp,struct dlautopush * dlap)1146 dld_autopush(dev_t *devp, struct dlautopush *dlap)
1147 {
1148 	dld_ap_t	*dap;
1149 	datalink_id_t	linkid;
1150 	dev_t		phydev;
1151 
1152 	if (!GLDV3_DRV(getmajor(*devp)))
1153 		return (-1);
1154 
1155 	/*
1156 	 * Find the linkid by the link's dev_t.
1157 	 */
1158 	if (dls_devnet_dev2linkid(*devp, &linkid) != 0)
1159 		return (-1);
1160 
1161 	/*
1162 	 * Find the autopush configuration associated with the linkid.
1163 	 */
1164 	rw_enter(&dld_ap_hash_lock, RW_READER);
1165 	if (mod_hash_find(dld_ap_hashp, (mod_hash_key_t)(uintptr_t)linkid,
1166 	    (mod_hash_val_t *)&dap) == 0) {
1167 		*dlap = dap->da_ap;
1168 		rw_exit(&dld_ap_hash_lock);
1169 		return (0);
1170 	}
1171 	rw_exit(&dld_ap_hash_lock);
1172 
1173 	if (dls_devnet_phydev(linkid, &phydev) != 0)
1174 		return (-1);
1175 
1176 	*devp = phydev;
1177 	return (1);
1178 }
1179 
1180 /*
1181  * Secure objects implementation
1182  */
1183 
1184 /* ARGSUSED */
1185 static int
drv_secobj_ctor(void * buf,void * arg,int kmflag)1186 drv_secobj_ctor(void *buf, void *arg, int kmflag)
1187 {
1188 	bzero(buf, sizeof (dld_secobj_t));
1189 	return (0);
1190 }
1191 
1192 static void
drv_secobj_init(void)1193 drv_secobj_init(void)
1194 {
1195 	rw_init(&drv_secobj_lock, NULL, RW_DEFAULT, NULL);
1196 	drv_secobj_cachep = kmem_cache_create("drv_secobj_cache",
1197 	    sizeof (dld_secobj_t), 0, drv_secobj_ctor, NULL,
1198 	    NULL, NULL, NULL, 0);
1199 	drv_secobj_hash = mod_hash_create_extended("drv_secobj_hash",
1200 	    SECOBJ_WEP_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor,
1201 	    mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
1202 }
1203 
1204 static void
drv_secobj_fini(void)1205 drv_secobj_fini(void)
1206 {
1207 	mod_hash_destroy_hash(drv_secobj_hash);
1208 	kmem_cache_destroy(drv_secobj_cachep);
1209 	rw_destroy(&drv_secobj_lock);
1210 }
1211 
1212 /* ARGSUSED */
1213 static int
drv_ioc_secobj_set(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1214 drv_ioc_secobj_set(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1215 {
1216 	dld_ioc_secobj_set_t	*ssp = karg;
1217 	dld_secobj_t		*sobjp, *objp;
1218 	int			err;
1219 
1220 	sobjp = &ssp->ss_obj;
1221 
1222 	if (sobjp->so_class != DLD_SECOBJ_CLASS_WEP &&
1223 	    sobjp->so_class != DLD_SECOBJ_CLASS_WPA)
1224 		return (EINVAL);
1225 
1226 	if (sobjp->so_name[DLD_SECOBJ_NAME_MAX - 1] != '\0' ||
1227 	    sobjp->so_len > DLD_SECOBJ_VAL_MAX)
1228 		return (EINVAL);
1229 
1230 	rw_enter(&drv_secobj_lock, RW_WRITER);
1231 	err = mod_hash_find(drv_secobj_hash, (mod_hash_key_t)sobjp->so_name,
1232 	    (mod_hash_val_t *)&objp);
1233 	if (err == 0) {
1234 		if ((ssp->ss_flags & DLD_SECOBJ_OPT_CREATE) != 0) {
1235 			rw_exit(&drv_secobj_lock);
1236 			return (EEXIST);
1237 		}
1238 	} else {
1239 		ASSERT(err == MH_ERR_NOTFOUND);
1240 		if ((ssp->ss_flags & DLD_SECOBJ_OPT_CREATE) == 0) {
1241 			rw_exit(&drv_secobj_lock);
1242 			return (ENOENT);
1243 		}
1244 		objp = kmem_cache_alloc(drv_secobj_cachep, KM_SLEEP);
1245 		(void) strlcpy(objp->so_name, sobjp->so_name,
1246 		    DLD_SECOBJ_NAME_MAX);
1247 
1248 		VERIFY(mod_hash_insert(drv_secobj_hash,
1249 		    (mod_hash_key_t)objp->so_name, (mod_hash_val_t)objp) == 0);
1250 	}
1251 	bcopy(sobjp->so_val, objp->so_val, sobjp->so_len);
1252 	objp->so_len = sobjp->so_len;
1253 	objp->so_class = sobjp->so_class;
1254 	rw_exit(&drv_secobj_lock);
1255 	return (0);
1256 }
1257 
1258 typedef struct dld_secobj_state {
1259 	uint_t		ss_free;
1260 	uint_t		ss_count;
1261 	int		ss_rc;
1262 	int		ss_mode;
1263 	dld_secobj_t	*ss_objp;
1264 } dld_secobj_state_t;
1265 
1266 /* ARGSUSED */
1267 static uint_t
drv_secobj_walker(mod_hash_key_t key,mod_hash_val_t * val,void * arg)1268 drv_secobj_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
1269 {
1270 	dld_secobj_state_t	*statep = arg;
1271 	dld_secobj_t		*sobjp = (dld_secobj_t *)val;
1272 
1273 	if (statep->ss_free < sizeof (dld_secobj_t)) {
1274 		statep->ss_rc = ENOSPC;
1275 		return (MH_WALK_TERMINATE);
1276 	}
1277 	if (ddi_copyout(sobjp, statep->ss_objp, sizeof (*sobjp),
1278 	    statep->ss_mode) != 0) {
1279 		statep->ss_rc = EFAULT;
1280 		return (MH_WALK_TERMINATE);
1281 	}
1282 	statep->ss_objp++;
1283 	statep->ss_free -= sizeof (dld_secobj_t);
1284 	statep->ss_count++;
1285 	return (MH_WALK_CONTINUE);
1286 }
1287 
1288 /* ARGSUSED */
1289 static int
drv_ioc_secobj_get(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1290 drv_ioc_secobj_get(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1291 {
1292 	dld_ioc_secobj_get_t	*sgp = karg;
1293 	dld_secobj_t		*sobjp, *objp;
1294 	int			err;
1295 
1296 	sobjp = &sgp->sg_obj;
1297 	if (sobjp->so_name[DLD_SECOBJ_NAME_MAX - 1] != '\0')
1298 		return (EINVAL);
1299 
1300 	rw_enter(&drv_secobj_lock, RW_READER);
1301 	if (sobjp->so_name[0] != '\0') {
1302 		err = mod_hash_find(drv_secobj_hash,
1303 		    (mod_hash_key_t)sobjp->so_name, (mod_hash_val_t *)&objp);
1304 		if (err != 0) {
1305 			ASSERT(err == MH_ERR_NOTFOUND);
1306 			rw_exit(&drv_secobj_lock);
1307 			return (ENOENT);
1308 		}
1309 		bcopy(objp->so_val, sobjp->so_val, objp->so_len);
1310 		sobjp->so_len = objp->so_len;
1311 		sobjp->so_class = objp->so_class;
1312 		sgp->sg_count = 1;
1313 	} else {
1314 		dld_secobj_state_t	state;
1315 
1316 		state.ss_free = sgp->sg_size - sizeof (dld_ioc_secobj_get_t);
1317 		state.ss_count = 0;
1318 		state.ss_rc = 0;
1319 		state.ss_mode = mode;
1320 		state.ss_objp = (dld_secobj_t *)((uchar_t *)arg +
1321 		    sizeof (dld_ioc_secobj_get_t));
1322 
1323 		mod_hash_walk(drv_secobj_hash, drv_secobj_walker, &state);
1324 		if (state.ss_rc != 0) {
1325 			rw_exit(&drv_secobj_lock);
1326 			return (state.ss_rc);
1327 		}
1328 		sgp->sg_count = state.ss_count;
1329 	}
1330 	rw_exit(&drv_secobj_lock);
1331 	return (0);
1332 }
1333 
1334 /* ARGSUSED */
1335 static int
drv_ioc_secobj_unset(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1336 drv_ioc_secobj_unset(void *karg, intptr_t arg, int mode, cred_t *cred,
1337     int *rvalp)
1338 {
1339 	dld_ioc_secobj_unset_t	*sup = karg;
1340 	dld_secobj_t		*objp;
1341 	mod_hash_val_t		val;
1342 	int			err;
1343 
1344 	if (sup->su_name[DLD_SECOBJ_NAME_MAX - 1] != '\0')
1345 		return (EINVAL);
1346 
1347 	rw_enter(&drv_secobj_lock, RW_WRITER);
1348 	err = mod_hash_find(drv_secobj_hash, (mod_hash_key_t)sup->su_name,
1349 	    (mod_hash_val_t *)&objp);
1350 	if (err != 0) {
1351 		ASSERT(err == MH_ERR_NOTFOUND);
1352 		rw_exit(&drv_secobj_lock);
1353 		return (ENOENT);
1354 	}
1355 	VERIFY(mod_hash_remove(drv_secobj_hash, (mod_hash_key_t)sup->su_name,
1356 	    (mod_hash_val_t *)&val) == 0);
1357 	ASSERT(objp == (dld_secobj_t *)val);
1358 
1359 	kmem_cache_free(drv_secobj_cachep, objp);
1360 	rw_exit(&drv_secobj_lock);
1361 	return (0);
1362 }
1363 
1364 /* ARGSUSED */
1365 static int
drv_ioc_gettran(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1366 drv_ioc_gettran(void *karg, intptr_t arg, int mode, cred_t *cred,
1367     int *rvalp)
1368 {
1369 	int			ret = 0;
1370 	mac_perim_handle_t	mph = NULL;
1371 	dls_dl_handle_t		dlh = NULL;
1372 	dls_link_t		*dlp = NULL;
1373 	dld_ioc_gettran_t	*dgt = karg;
1374 
1375 	if ((ret = dls_devnet_hold_tmp(dgt->dgt_linkid, &dlh)) != 0)
1376 		goto done;
1377 
1378 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1379 		goto done;
1380 
1381 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1382 		goto done;
1383 
1384 	/*
1385 	 * Make sure that this link belongs to the zone.
1386 	 */
1387 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1388 		ret = ENOENT;
1389 		goto done;
1390 	}
1391 
1392 	if (dgt->dgt_tran_id == DLDIOC_GETTRAN_GETNTRAN) {
1393 		ret = mac_transceiver_count(dlp->dl_mh, &dgt->dgt_tran_id);
1394 	} else {
1395 		ret = mac_transceiver_info(dlp->dl_mh, dgt->dgt_tran_id,
1396 		    &dgt->dgt_present, &dgt->dgt_usable);
1397 	}
1398 
1399 done:
1400 	if (dlp != NULL)
1401 		dls_link_rele(dlp);
1402 
1403 	if (mph != NULL)
1404 		mac_perim_exit(mph);
1405 
1406 	if (dlh != NULL)
1407 		dls_devnet_rele_tmp(dlh);
1408 
1409 	return (ret);
1410 }
1411 
1412 /* ARGSUSED */
1413 static int
drv_ioc_readtran(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1414 drv_ioc_readtran(void *karg, intptr_t arg, int mode, cred_t *cred,
1415     int *rvalp)
1416 {
1417 	int			ret = 0;
1418 	mac_perim_handle_t	mph = NULL;
1419 	dls_dl_handle_t		dlh = NULL;
1420 	dls_link_t		*dlp = NULL;
1421 	dld_ioc_tranio_t	*dti = karg;
1422 	uint8_t			buf[256];
1423 	size_t			nr;
1424 
1425 	/*
1426 	 * Be strict for the moment
1427 	 */
1428 	if (dti->dti_nbytes != 256 || dti->dti_off != 0)
1429 		return (EINVAL);
1430 
1431 	if ((ret = dls_devnet_hold_tmp(dti->dti_linkid, &dlh)) != 0)
1432 		goto done;
1433 
1434 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1435 		goto done;
1436 
1437 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1438 		goto done;
1439 
1440 	/*
1441 	 * Make sure that this link belongs to the zone.
1442 	 */
1443 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1444 		ret = ENOENT;
1445 		goto done;
1446 	}
1447 
1448 	bzero(buf, sizeof (buf));
1449 	if ((ret = mac_transceiver_read(dlp->dl_mh, dti->dti_tran_id,
1450 	    dti->dti_page, buf, dti->dti_nbytes, dti->dti_off, &nr)) == 0) {
1451 		dti->dti_nbytes = nr;
1452 		ret = ddi_copyout(buf, (void *)(uintptr_t)dti->dti_buf,
1453 		    sizeof (buf), mode);
1454 	}
1455 
1456 done:
1457 	if (dlp != NULL)
1458 		dls_link_rele(dlp);
1459 
1460 	if (mph != NULL)
1461 		mac_perim_exit(mph);
1462 
1463 	if (dlh != NULL)
1464 		dls_devnet_rele_tmp(dlh);
1465 
1466 	return (ret);
1467 }
1468 
1469 /* ARGSUSED */
1470 static int
drv_ioc_getled(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1471 drv_ioc_getled(void *karg, intptr_t arg, int mode, cred_t *cred,
1472     int *rvalp)
1473 {
1474 	int			ret = 0;
1475 	mac_perim_handle_t	mph = NULL;
1476 	dls_dl_handle_t		dlh = NULL;
1477 	dls_link_t		*dlp = NULL;
1478 	dld_ioc_led_t		*dil = karg;
1479 
1480 	if ((mode & FREAD) == 0)
1481 		return (EBADF);
1482 
1483 	if ((ret = dls_devnet_hold_tmp(dil->dil_linkid, &dlh)) != 0)
1484 		goto done;
1485 
1486 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1487 		goto done;
1488 
1489 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1490 		goto done;
1491 
1492 	/*
1493 	 * Make sure that this link belongs to the zone.
1494 	 */
1495 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1496 		ret = ENOENT;
1497 		goto done;
1498 	}
1499 
1500 	ret = mac_led_get(dlp->dl_mh, &dil->dil_supported, &dil->dil_active);
1501 
1502 done:
1503 	if (dlp != NULL)
1504 		dls_link_rele(dlp);
1505 
1506 	if (mph != NULL)
1507 		mac_perim_exit(mph);
1508 
1509 	if (dlh != NULL)
1510 		dls_devnet_rele_tmp(dlh);
1511 
1512 	return (ret);
1513 }
1514 
1515 /* ARGSUSED */
1516 static int
drv_ioc_setled(void * karg,intptr_t arg,int mode,cred_t * cred,int * rvalp)1517 drv_ioc_setled(void *karg, intptr_t arg, int mode, cred_t *cred,
1518     int *rvalp)
1519 {
1520 	int			ret = 0;
1521 	mac_perim_handle_t	mph = NULL;
1522 	dls_dl_handle_t		dlh = NULL;
1523 	dls_link_t		*dlp = NULL;
1524 	dld_ioc_led_t		*dil = karg;
1525 
1526 	if ((mode & FWRITE) == 0)
1527 		return (EBADF);
1528 
1529 	if ((ret = dls_devnet_hold_tmp(dil->dil_linkid, &dlh)) != 0)
1530 		goto done;
1531 
1532 	if ((ret = mac_perim_enter_by_macname(dls_devnet_mac(dlh), &mph)) != 0)
1533 		goto done;
1534 
1535 	if ((ret = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
1536 		goto done;
1537 
1538 	/*
1539 	 * Make sure that this link belongs to the zone.
1540 	 */
1541 	if (crgetzoneid(cred) != dls_devnet_getownerzid(dlh)) {
1542 		ret = ENOENT;
1543 		goto done;
1544 	}
1545 
1546 	ret = mac_led_set(dlp->dl_mh, dil->dil_active);
1547 
1548 done:
1549 	if (dlp != NULL)
1550 		dls_link_rele(dlp);
1551 
1552 	if (mph != NULL)
1553 		mac_perim_exit(mph);
1554 
1555 	if (dlh != NULL)
1556 		dls_devnet_rele_tmp(dlh);
1557 
1558 	return (ret);
1559 }
1560 
1561 
1562 /*
1563  * Note that ioctls that modify links have a NULL di_priv_func(), as
1564  * privileges can only be checked after we know the class of the link being
1565  * modified (due to class-specific fine-grained privileges such as
1566  * sys_iptun_config).
1567  */
1568 static dld_ioc_info_t drv_ioc_list[] = {
1569 	{DLDIOC_ATTR, DLDCOPYINOUT, sizeof (dld_ioc_attr_t),
1570 	    drv_ioc_attr, NULL},
1571 	{DLDIOC_PHYS_ATTR, DLDCOPYINOUT, sizeof (dld_ioc_phys_attr_t),
1572 	    drv_ioc_phys_attr, NULL},
1573 	{DLDIOC_SECOBJ_SET, DLDCOPYIN, sizeof (dld_ioc_secobj_set_t),
1574 	    drv_ioc_secobj_set, secpolicy_dl_config},
1575 	{DLDIOC_SECOBJ_GET, DLDCOPYINOUT, sizeof (dld_ioc_secobj_get_t),
1576 	    drv_ioc_secobj_get, secpolicy_dl_config},
1577 	{DLDIOC_SECOBJ_UNSET, DLDCOPYIN, sizeof (dld_ioc_secobj_unset_t),
1578 	    drv_ioc_secobj_unset, secpolicy_dl_config},
1579 	{DLDIOC_DOORSERVER, DLDCOPYIN, sizeof (dld_ioc_door_t),
1580 	    drv_ioc_doorserver, secpolicy_dl_config},
1581 	{DLDIOC_RENAME, DLDCOPYIN, sizeof (dld_ioc_rename_t),
1582 	    drv_ioc_rename, NULL},
1583 	{DLDIOC_MACADDRGET, DLDCOPYINOUT, sizeof (dld_ioc_macaddrget_t),
1584 	    drv_ioc_macaddrget, NULL},
1585 	{DLDIOC_ADDFLOW, DLDCOPYIN, sizeof (dld_ioc_addflow_t),
1586 	    drv_ioc_addflow, secpolicy_dl_config},
1587 	{DLDIOC_REMOVEFLOW, DLDCOPYIN, sizeof (dld_ioc_removeflow_t),
1588 	    drv_ioc_removeflow, secpolicy_dl_config},
1589 	{DLDIOC_MODIFYFLOW, DLDCOPYIN, sizeof (dld_ioc_modifyflow_t),
1590 	    drv_ioc_modifyflow, secpolicy_dl_config},
1591 	{DLDIOC_WALKFLOW, DLDCOPYINOUT, sizeof (dld_ioc_walkflow_t),
1592 	    drv_ioc_walkflow, NULL},
1593 	{DLDIOC_USAGELOG, DLDCOPYIN, sizeof (dld_ioc_usagelog_t),
1594 	    drv_ioc_usagelog, secpolicy_dl_config},
1595 	{DLDIOC_SETMACPROP, DLDCOPYIN, sizeof (dld_ioc_macprop_t),
1596 	    drv_ioc_setprop, NULL},
1597 	{DLDIOC_GETMACPROP, DLDCOPYIN, sizeof (dld_ioc_macprop_t),
1598 	    drv_ioc_getprop, NULL},
1599 	{DLDIOC_GETHWGRP, DLDCOPYINOUT, sizeof (dld_ioc_hwgrpget_t),
1600 	    drv_ioc_hwgrpget, NULL},
1601 	{DLDIOC_GETTRAN, DLDCOPYINOUT, sizeof (dld_ioc_gettran_t),
1602 	    drv_ioc_gettran, NULL },
1603 	{DLDIOC_READTRAN, DLDCOPYINOUT, sizeof (dld_ioc_tranio_t),
1604 	    drv_ioc_readtran, NULL },
1605 	{DLDIOC_GETLED, DLDCOPYINOUT, sizeof (dld_ioc_led_t),
1606 	    drv_ioc_getled, NULL },
1607 	{DLDIOC_SETLED, DLDCOPYIN, sizeof (dld_ioc_led_t),
1608 	    drv_ioc_setled, secpolicy_dl_config}
1609 };
1610 
1611 typedef struct dld_ioc_modentry {
1612 	uint16_t	dim_modid;	/* Top 16 bits of ioctl command */
1613 	char		*dim_modname;	/* Module to be loaded */
1614 	int		ctrl_node_inst;	/* Ctrl node instance */
1615 	dld_ioc_info_t	*dim_list;	/* array of ioctl structures */
1616 	uint_t		dim_count;	/* number of elements in dim_list */
1617 } dld_ioc_modentry_t;
1618 
1619 /*
1620  * For all modules except for dld, dim_list and dim_count are assigned
1621  * when the modules register their ioctls in dld_ioc_register().  We
1622  * can statically initialize dld's ioctls in-line here; there's no
1623  * need for it to call dld_ioc_register() itself. ctrl_node_inst controls
1624  * whether an instance of the device will be held or the driver. If set to
1625  * a non-negative integer, device instance specified in ctrl_node_inst will
1626  * be held; so dld_ioc_register() _must_ be called in xxx_attach() routine of
1627  * the driver. If set to -1, driver will be held; so dld_ioc_register() _must_
1628  * be called in xxx_init() routine of the driver.
1629  */
1630 static dld_ioc_modentry_t dld_ioc_modtable[] = {
1631 	{DLD_IOC,	"dld", 0, drv_ioc_list, DLDIOCCNT(drv_ioc_list)},
1632 	{AGGR_IOC,	"aggr", 0, NULL, 0},
1633 	{VNIC_IOC,	"vnic",	0, NULL, 0},
1634 	{SIMNET_IOC,	"simnet", 0, NULL, 0},
1635 	{BRIDGE_IOC,	"bridge", 0, NULL, 0},
1636 	{IPTUN_IOC,	"iptun", 0, NULL, 0},
1637 	{IBPART_IOC,	"ibp", -1, NULL, 0},
1638 	{OVERLAY_IOC,	"overlay", 0, NULL, 0}
1639 };
1640 #define	DLDIOC_CNT	\
1641 	(sizeof (dld_ioc_modtable) / sizeof (dld_ioc_modentry_t))
1642 
1643 static dld_ioc_modentry_t *
dld_ioc_findmod(uint16_t modid)1644 dld_ioc_findmod(uint16_t modid)
1645 {
1646 	int	i;
1647 
1648 	for (i = 0; i < DLDIOC_CNT; i++) {
1649 		if (modid == dld_ioc_modtable[i].dim_modid)
1650 			return (&dld_ioc_modtable[i]);
1651 	}
1652 	return (NULL);
1653 }
1654 
1655 int
dld_ioc_register(uint16_t modid,dld_ioc_info_t * list,uint_t count)1656 dld_ioc_register(uint16_t modid, dld_ioc_info_t *list, uint_t count)
1657 {
1658 	dld_ioc_modentry_t *dim = dld_ioc_findmod(modid);
1659 
1660 	if (dim == NULL)
1661 		return (ENOENT);
1662 
1663 	dim->dim_list = list;
1664 	dim->dim_count = count;
1665 	return (0);
1666 }
1667 
1668 void
dld_ioc_unregister(uint16_t modid)1669 dld_ioc_unregister(uint16_t modid)
1670 {
1671 	VERIFY(dld_ioc_register(modid, NULL, 0) == 0);
1672 }
1673 
1674 /*
1675  * The general design with GLDv3 ioctls is that all ioctls issued
1676  * through /dev/dld go through this drv_ioctl() function.  This
1677  * function handles all ioctls on behalf of modules listed in
1678  * dld_ioc_modtable.
1679  *
1680  * When an ioctl is received, this function looks for the associated
1681  * module-id-specific ioctl information using dld_ioc_findmod(). The
1682  * call to ddi_hold_driver() or ddi_hold_devi_by_instance() on the
1683  * associated device will cause the kernel module responsible for the
1684  * ioctl to be loaded if it's not already loaded, which should result
1685  * in that module calling dld_ioc_register(), thereby filling in the
1686  * dim_list containing the details for the ioctl being processed.
1687  *
1688  * This function can then perform operations such as copyin() data and
1689  * do credential checks based on the registered ioctl information,
1690  * then issue the callback function di_func() registered by the
1691  * responsible module.  Upon return, the appropriate copyout()
1692  * operation can be performed and the operation completes.
1693  */
1694 /* ARGSUSED */
1695 static int
drv_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * cred,int * rvalp)1696 drv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1697 {
1698 	dld_ioc_modentry_t *dim;
1699 	dld_ioc_info_t	*info;
1700 	dev_info_t	*dip = NULL;
1701 	struct dev_ops	*dops = NULL;
1702 	major_t		major;
1703 	void		*buf = NULL;
1704 	size_t		sz;
1705 	int		i, err;
1706 
1707 	if ((dim = dld_ioc_findmod(DLD_IOC_MODID(cmd))) == NULL)
1708 		return (ENOTSUP);
1709 
1710 	major = ddi_name_to_major(dim->dim_modname);
1711 
1712 	if (dim->ctrl_node_inst == -1) {
1713 		/*
1714 		 * No dedicated instance to process ioctls.
1715 		 * dld_ioc_register() is called in xxx_init().
1716 		 */
1717 		dops = ddi_hold_driver(major);
1718 	} else {
1719 		/*
1720 		 * Dedicated instance to handle ioctl.
1721 		 * dld_ioc_register() is called in xxx_attach().
1722 		 */
1723 		dip = ddi_hold_devi_by_instance(major, dim->ctrl_node_inst, 0);
1724 	}
1725 
1726 	if ((dip == NULL && dops == NULL) || dim->dim_list == NULL) {
1727 		err = ENODEV;
1728 		goto done;
1729 	}
1730 
1731 	for (i = 0; i < dim->dim_count; i++) {
1732 		if (cmd == dim->dim_list[i].di_cmd)
1733 			break;
1734 	}
1735 	if (i == dim->dim_count) {
1736 		err = ENOTSUP;
1737 		goto done;
1738 	}
1739 
1740 	info = &dim->dim_list[i];
1741 
1742 	if (info->di_priv_func != NULL &&
1743 	    (err = info->di_priv_func(cred)) != 0)
1744 		goto done;
1745 
1746 	sz = info->di_argsize;
1747 	if ((buf = kmem_zalloc(sz, KM_NOSLEEP)) == NULL) {
1748 		err = ENOMEM;
1749 		goto done;
1750 	}
1751 
1752 	if ((info->di_flags & DLDCOPYIN) &&
1753 	    ddi_copyin((void *)arg, buf, sz, mode) != 0) {
1754 		err = EFAULT;
1755 		goto done;
1756 	}
1757 
1758 	err = info->di_func(buf, arg, mode, cred, rvalp);
1759 
1760 	if ((info->di_flags & DLDCOPYOUT) &&
1761 	    ddi_copyout(buf, (void *)arg, sz, mode) != 0 && err == 0)
1762 		err = EFAULT;
1763 
1764 done:
1765 	if (buf != NULL)
1766 		kmem_free(buf, sz);
1767 	if (dip != NULL)
1768 		ddi_release_devi(dip);
1769 	if (dops != NULL)
1770 		ddi_rele_driver(major);
1771 	return (err);
1772 }
1773