xref: /illumos-gate/usr/src/uts/common/io/dld/dld_drv.c (revision 8ce3ffdfd4c1bd6be03a31b5019c67a6c920ca54)
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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * Data-Link Driver
28  */
29 
30 #include	<sys/conf.h>
31 #include	<sys/mkdev.h>
32 #include	<sys/modctl.h>
33 #include	<sys/stat.h>
34 #include	<sys/dld_impl.h>
35 #include	<sys/dls_impl.h>
36 #include	<sys/softmac.h>
37 #include	<sys/mac.h>
38 #include	<sys/mac_ether.h>
39 #include	<sys/mac_client.h>
40 #include	<sys/mac_client_impl.h>
41 #include	<sys/mac_client_priv.h>
42 #include	<inet/common.h>
43 #include	<sys/policy.h>
44 #include	<sys/priv_names.h>
45 
46 static void	drv_init(void);
47 static int	drv_fini(void);
48 
49 static int	drv_getinfo(dev_info_t	*, ddi_info_cmd_t, void *, void **);
50 static int	drv_attach(dev_info_t *, ddi_attach_cmd_t);
51 static int	drv_detach(dev_info_t *, ddi_detach_cmd_t);
52 
53 /*
54  * Secure objects declarations
55  */
56 #define	SECOBJ_WEP_HASHSZ	67
57 static krwlock_t	drv_secobj_lock;
58 static kmem_cache_t	*drv_secobj_cachep;
59 static mod_hash_t	*drv_secobj_hash;
60 static void		drv_secobj_init(void);
61 static void		drv_secobj_fini(void);
62 static int		drv_ioc_setap(datalink_id_t, struct dlautopush *);
63 static int		drv_ioc_getap(datalink_id_t, struct dlautopush *);
64 static int		drv_ioc_clrap(datalink_id_t);
65 
66 
67 /*
68  * The following entry points are private to dld and are used for control
69  * operations only. The entry points exported to mac drivers are defined
70  * in dld_str.c. Refer to the comment on top of dld_str.c for details.
71  */
72 static int	drv_open(dev_t *, int, int, cred_t *);
73 static int	drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
74 
75 static dev_info_t	*dld_dip;	/* dev_info_t for the driver */
76 uint32_t		dld_opt = 0;	/* Global options */
77 
78 #define	NAUTOPUSH 32
79 static mod_hash_t *dld_ap_hashp;
80 static krwlock_t dld_ap_hash_lock;
81 
82 static struct cb_ops drv_cb_ops = {
83 	drv_open,		/* open */
84 	nulldev,		/* close */
85 	nulldev,		/* strategy */
86 	nulldev,		/* print */
87 	nodev,			/* dump */
88 	nodev,			/* read */
89 	nodev,			/* write */
90 	drv_ioctl,		/* ioctl */
91 	nodev,			/* devmap */
92 	nodev,			/* mmap */
93 	nodev,			/* segmap */
94 	nochpoll,		/* poll */
95 	ddi_prop_op,		/* cb_prop_op */
96 	0,			/* streamtab  */
97 	D_MP			/* Driver compatibility flag */
98 };
99 
100 static struct dev_ops drv_ops = {
101 	DEVO_REV,		/* devo_rev */
102 	0,			/* refcnt */
103 	drv_getinfo,		/* get_dev_info */
104 	nulldev,		/* identify */
105 	nulldev,		/* probe */
106 	drv_attach,		/* attach */
107 	drv_detach,		/* detach */
108 	nodev,			/* reset */
109 	&drv_cb_ops,		/* driver operations */
110 	NULL,			/* bus operations */
111 	nodev,			/* dev power */
112 	ddi_quiesce_not_supported,	/* dev quiesce */
113 };
114 
115 /*
116  * Module linkage information for the kernel.
117  */
118 static	struct modldrv		drv_modldrv = {
119 	&mod_driverops,
120 	DLD_INFO,
121 	&drv_ops
122 };
123 
124 static	struct modlinkage	drv_modlinkage = {
125 	MODREV_1,
126 	&drv_modldrv,
127 	NULL
128 };
129 
130 int
131 _init(void)
132 {
133 	return (mod_install(&drv_modlinkage));
134 }
135 
136 int
137 _fini(void)
138 {
139 	return (mod_remove(&drv_modlinkage));
140 }
141 
142 int
143 _info(struct modinfo *modinfop)
144 {
145 	return (mod_info(&drv_modlinkage, modinfop));
146 }
147 
148 /*
149  * Initialize component modules.
150  */
151 static void
152 drv_init(void)
153 {
154 	drv_secobj_init();
155 	dld_str_init();
156 
157 	/*
158 	 * Create a hash table for autopush configuration.
159 	 */
160 	dld_ap_hashp = mod_hash_create_idhash("dld_autopush_hash",
161 	    NAUTOPUSH, mod_hash_null_valdtor);
162 
163 	ASSERT(dld_ap_hashp != NULL);
164 	rw_init(&dld_ap_hash_lock, NULL, RW_DRIVER, NULL);
165 }
166 
167 /* ARGSUSED */
168 static uint_t
169 drv_ap_exist(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
170 {
171 	boolean_t *pexist = arg;
172 
173 	*pexist = B_TRUE;
174 	return (MH_WALK_TERMINATE);
175 }
176 
177 static int
178 drv_fini(void)
179 {
180 	int		err;
181 	boolean_t	exist = B_FALSE;
182 
183 	rw_enter(&dld_ap_hash_lock, RW_READER);
184 	mod_hash_walk(dld_ap_hashp, drv_ap_exist, &exist);
185 	rw_exit(&dld_ap_hash_lock);
186 	if (exist)
187 		return (EBUSY);
188 
189 	if ((err = dld_str_fini()) != 0)
190 		return (err);
191 
192 	drv_secobj_fini();
193 	mod_hash_destroy_idhash(dld_ap_hashp);
194 	rw_destroy(&dld_ap_hash_lock);
195 	return (0);
196 }
197 
198 /*
199  * devo_getinfo: getinfo(9e)
200  */
201 /*ARGSUSED*/
202 static int
203 drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp)
204 {
205 	if (dld_dip == NULL)
206 		return (DDI_FAILURE);
207 
208 	switch (cmd) {
209 	case DDI_INFO_DEVT2INSTANCE:
210 		*resp = 0;
211 		break;
212 	case DDI_INFO_DEVT2DEVINFO:
213 		*resp = dld_dip;
214 		break;
215 	default:
216 		return (DDI_FAILURE);
217 	}
218 
219 	return (DDI_SUCCESS);
220 }
221 
222 /*
223  * Check properties to set options. (See dld.h for property definitions).
224  */
225 static void
226 drv_set_opt(dev_info_t *dip)
227 {
228 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
229 	    DLD_PROP_NO_FASTPATH, 0) != 0) {
230 		dld_opt |= DLD_OPT_NO_FASTPATH;
231 	}
232 
233 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
234 	    DLD_PROP_NO_POLL, 0) != 0) {
235 		dld_opt |= DLD_OPT_NO_POLL;
236 	}
237 
238 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
239 	    DLD_PROP_NO_ZEROCOPY, 0) != 0) {
240 		dld_opt |= DLD_OPT_NO_ZEROCOPY;
241 	}
242 
243 	if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
244 	    DLD_PROP_NO_SOFTRING, 0) != 0) {
245 		dld_opt |= DLD_OPT_NO_SOFTRING;
246 	}
247 }
248 
249 /*
250  * devo_attach: attach(9e)
251  */
252 static int
253 drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
254 {
255 	if (cmd != DDI_ATTACH)
256 		return (DDI_FAILURE);
257 
258 	ASSERT(ddi_get_instance(dip) == 0);
259 	drv_init();
260 	drv_set_opt(dip);
261 
262 	/*
263 	 * Create control node. DLPI provider nodes will be created on demand.
264 	 */
265 	if (ddi_create_minor_node(dip, DLD_CONTROL_MINOR_NAME, S_IFCHR,
266 	    DLD_CONTROL_MINOR, DDI_PSEUDO, 0) != DDI_SUCCESS)
267 		return (DDI_FAILURE);
268 
269 	dld_dip = dip;
270 
271 	/*
272 	 * Log the fact that the driver is now attached.
273 	 */
274 	ddi_report_dev(dip);
275 	return (DDI_SUCCESS);
276 }
277 
278 /*
279  * devo_detach: detach(9e)
280  */
281 static int
282 drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
283 {
284 	if (cmd != DDI_DETACH)
285 		return (DDI_FAILURE);
286 
287 	ASSERT(dld_dip == dip);
288 	if (drv_fini() != 0)
289 		return (DDI_FAILURE);
290 
291 	/*
292 	 * Remove the control node.
293 	 */
294 	ddi_remove_minor_node(dip, DLD_CONTROL_MINOR_NAME);
295 	dld_dip = NULL;
296 
297 	return (DDI_SUCCESS);
298 }
299 
300 /*
301  * dld control node open procedure.
302  */
303 /*ARGSUSED*/
304 static int
305 drv_open(dev_t *devp, int flag, int sflag, cred_t *credp)
306 {
307 	/*
308 	 * Only the control node can be opened.
309 	 */
310 	if (getminor(*devp) != DLD_CONTROL_MINOR)
311 		return (ENODEV);
312 	return (0);
313 }
314 
315 /*
316  * DLDIOC_ATTR
317  */
318 /* ARGSUSED */
319 static int
320 drv_ioc_attr(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
321 {
322 	dld_ioc_attr_t		*diap = karg;
323 	dls_dl_handle_t		dlh;
324 	dls_link_t		*dlp;
325 	int			err;
326 	mac_perim_handle_t	mph;
327 
328 	if ((err = dls_devnet_hold_tmp(diap->dia_linkid, &dlh)) != 0)
329 		return (err);
330 
331 	if ((err = mac_perim_enter_by_macname(
332 	    dls_devnet_mac(dlh), &mph)) != 0) {
333 		dls_devnet_rele_tmp(dlh);
334 		return (err);
335 	}
336 
337 	if ((err = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0) {
338 		mac_perim_exit(mph);
339 		dls_devnet_rele_tmp(dlh);
340 		return (err);
341 	}
342 
343 	mac_sdu_get(dlp->dl_mh, NULL, &diap->dia_max_sdu);
344 
345 	dls_link_rele(dlp);
346 	mac_perim_exit(mph);
347 	dls_devnet_rele_tmp(dlh);
348 
349 	return (0);
350 }
351 
352 /*
353  * DLDIOC_PHYS_ATTR
354  */
355 /* ARGSUSED */
356 static int
357 drv_ioc_phys_attr(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
358 {
359 	dld_ioc_phys_attr_t	*dipp = karg;
360 	int			err;
361 	dls_dl_handle_t		dlh;
362 	dls_dev_handle_t	ddh;
363 	dev_t			phydev;
364 
365 	/*
366 	 * Every physical link should have its physical dev_t kept in the
367 	 * daemon. If not, it is not a valid physical link.
368 	 */
369 	if (dls_mgmt_get_phydev(dipp->dip_linkid, &phydev) != 0)
370 		return (EINVAL);
371 
372 	/*
373 	 * Although this is a valid physical link, it might already be removed
374 	 * by DR or during system shutdown. softmac_hold_device() would return
375 	 * ENOENT in this case.
376 	 */
377 	if ((err = softmac_hold_device(phydev, &ddh)) != 0)
378 		return (err);
379 
380 	if (dls_devnet_hold_tmp(dipp->dip_linkid, &dlh) != 0) {
381 		/*
382 		 * Although this is an active physical link, its link type is
383 		 * not supported by GLDv3, and therefore it does not have
384 		 * vanity naming support.
385 		 */
386 		dipp->dip_novanity = B_TRUE;
387 	} else {
388 		dipp->dip_novanity = B_FALSE;
389 		dls_devnet_rele_tmp(dlh);
390 	}
391 	/*
392 	 * Get the physical device name from the major number and the instance
393 	 * number derived from phydev.
394 	 */
395 	(void) snprintf(dipp->dip_dev, MAXLINKNAMELEN, "%s%d",
396 	    ddi_major_to_name(getmajor(phydev)), getminor(phydev) - 1);
397 
398 	softmac_rele_device(ddh);
399 	return (0);
400 }
401 
402 /* ARGSUSED */
403 static int
404 drv_ioc_hwgrpget(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
405 {
406 	dld_ioc_hwgrpget_t	*hwgrpp = karg;
407 	dld_hwgrpinfo_t		hwgrp, *hip;
408 	mac_handle_t		mh = NULL;
409 	int			i, err, grpnum;
410 	uint_t			bytes_left;
411 
412 	hwgrpp->dih_n_groups = 0;
413 	err = mac_open_by_linkid(hwgrpp->dih_linkid, &mh);
414 	if (err != 0)
415 		goto done;
416 
417 	hip = (dld_hwgrpinfo_t *)
418 	    ((uchar_t *)arg + sizeof (dld_ioc_hwgrpget_t));
419 	bytes_left = hwgrpp->dih_size;
420 	grpnum = mac_hwgrp_num(mh);
421 	for (i = 0; i < grpnum; i++) {
422 		if (sizeof (dld_hwgrpinfo_t) > bytes_left) {
423 			err = ENOSPC;
424 			goto done;
425 		}
426 
427 		bzero(&hwgrp, sizeof (hwgrp));
428 		bcopy(mac_name(mh), hwgrp.dhi_link_name,
429 		    sizeof (hwgrp.dhi_link_name));
430 		mac_get_hwgrp_info(mh, i, &hwgrp.dhi_grp_num,
431 		    &hwgrp.dhi_n_rings, &hwgrp.dhi_grp_type,
432 		    &hwgrp.dhi_n_clnts, hwgrp.dhi_clnts);
433 		if (copyout(&hwgrp, hip, sizeof (hwgrp)) != 0) {
434 			err = EFAULT;
435 			goto done;
436 		}
437 
438 		hip++;
439 		bytes_left -= sizeof (dld_hwgrpinfo_t);
440 	}
441 
442 done:
443 	if (mh != NULL)
444 		dld_mac_close(mh);
445 	if (err == 0)
446 		hwgrpp->dih_n_groups = grpnum;
447 	return (err);
448 }
449 
450 /* ARGSUSED */
451 static int
452 drv_ioc_macaddrget(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
453 {
454 	dld_ioc_macaddrget_t	*magp = karg;
455 	dld_macaddrinfo_t	mai, *maip;
456 	mac_handle_t		mh = NULL;
457 	int			i, err;
458 	uint_t			bytes_left;
459 	boolean_t		is_used;
460 
461 	magp->dig_count = 0;
462 	err = mac_open_by_linkid(magp->dig_linkid, &mh);
463 	if (err != 0)
464 		goto done;
465 
466 	maip = (dld_macaddrinfo_t *)
467 	    ((uchar_t *)arg + sizeof (dld_ioc_macaddrget_t));
468 	bytes_left = magp->dig_size;
469 
470 	for (i = 0; i < mac_addr_factory_num(mh) + 1; i++) {
471 		if (sizeof (dld_macaddrinfo_t) > bytes_left) {
472 			err = ENOSPC;
473 			goto done;
474 		}
475 
476 		bzero(&mai, sizeof (mai));
477 
478 		if (i == 0) {
479 			/* primary MAC address */
480 			mac_unicast_primary_get(mh, mai.dmi_addr);
481 			mai.dmi_addrlen = mac_addr_len(mh);
482 			mac_unicast_primary_info(mh, mai.dmi_client_name,
483 			    &is_used);
484 		} else {
485 			/* factory MAC address slot */
486 			mac_addr_factory_value(mh, i, mai.dmi_addr,
487 			    &mai.dmi_addrlen, mai.dmi_client_name, &is_used);
488 		}
489 
490 		mai.dmi_slot = i;
491 		if (is_used)
492 			mai.dmi_flags |= DLDIOCMACADDR_USED;
493 
494 		if (copyout(&mai, maip, sizeof (mai)) != 0) {
495 			err = EFAULT;
496 			goto done;
497 		}
498 
499 		maip++;
500 		bytes_left -= sizeof (dld_macaddrinfo_t);
501 	}
502 
503 done:
504 	if (mh != NULL)
505 		dld_mac_close(mh);
506 	if (err == 0)
507 		magp->dig_count = mac_addr_factory_num(mh) + 1;
508 	return (err);
509 }
510 
511 /*
512  * DLDIOC_SET/GETPROP
513  */
514 static int
515 drv_ioc_prop_common(dld_ioc_macprop_t *prop, intptr_t arg, boolean_t set,
516     int mode)
517 {
518 	int			err = EINVAL;
519 	dls_dl_handle_t 	dlh = NULL;
520 	dls_link_t		*dlp = NULL;
521 	mac_perim_handle_t	mph = NULL;
522 	mac_prop_t		macprop;
523 	dld_ioc_macprop_t	*kprop;
524 	datalink_id_t		linkid;
525 	uint_t			dsize;
526 
527 
528 	/*
529 	 * We only use pr_valsize from prop, as the caller only did a
530 	 * copyin() for sizeof (dld_ioc_prop_t), which doesn't cover
531 	 * the property data.  We copyin the full dld_ioc_prop_t
532 	 * including the data into kprop down below.
533 	 */
534 	dsize = sizeof (dld_ioc_macprop_t) + prop->pr_valsize - 1;
535 	if (dsize < prop->pr_valsize)
536 		return (EINVAL);
537 
538 	/*
539 	 * The property data is variable size, so we need to allocate
540 	 * a buffer for kernel use as this data was not part of the
541 	 * prop allocation and copyin() done by the framework.
542 	 */
543 	if ((kprop = kmem_alloc(dsize, KM_NOSLEEP)) == NULL)
544 		return (ENOMEM);
545 
546 	if (ddi_copyin((void *)arg, kprop, dsize, mode) != 0) {
547 		err = EFAULT;
548 		goto done;
549 	}
550 
551 	linkid = kprop->pr_linkid;
552 	if ((err = dls_devnet_hold_tmp(linkid, &dlh)) != 0)
553 		goto done;
554 
555 	if ((err = mac_perim_enter_by_macname(dls_devnet_mac(dlh),
556 	    &mph)) != 0) {
557 		goto done;
558 	}
559 
560 	if ((err = dls_link_hold(dls_devnet_mac(dlh), &dlp)) != 0)
561 		goto done;
562 
563 	switch (kprop->pr_num) {
564 	case MAC_PROP_ZONE:
565 		if (set) {
566 			dld_ioc_zid_t *dzp = (dld_ioc_zid_t *)kprop->pr_val;
567 
568 			err = dls_devnet_setzid(dzp->diz_link, dzp->diz_zid);
569 		} else {
570 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
571 			err = dls_devnet_getzid(linkid,
572 			    (zoneid_t *)kprop->pr_val);
573 		}
574 		break;
575 	case MAC_PROP_AUTOPUSH: {
576 		struct dlautopush *dlap = (struct dlautopush *)kprop->pr_val;
577 
578 		if (set) {
579 			if (kprop->pr_valsize != 0)
580 				err = drv_ioc_setap(linkid, dlap);
581 			else
582 				err = drv_ioc_clrap(linkid);
583 		} else {
584 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
585 			err = drv_ioc_getap(linkid, dlap);
586 		}
587 		break;
588 	}
589 	case MAC_PROP_TAGMODE:
590 		if (set) {
591 			link_tagmode_t mode = *(link_tagmode_t *)kprop->pr_val;
592 
593 			if (mode != LINK_TAGMODE_VLANONLY &&
594 			    mode != LINK_TAGMODE_NORMAL) {
595 				err = EINVAL;
596 			} else {
597 				dlp->dl_tagmode = mode;
598 				err = 0;
599 			}
600 		} else {
601 			*(link_tagmode_t *)kprop->pr_val = dlp->dl_tagmode;
602 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
603 			err = 0;
604 		}
605 		break;
606 	default:
607 		macprop.mp_name = kprop->pr_name;
608 		macprop.mp_id = kprop->pr_num;
609 		macprop.mp_flags = kprop->pr_flags;
610 
611 		if (set) {
612 			err = mac_set_prop(dlp->dl_mh, &macprop, kprop->pr_val,
613 			    kprop->pr_valsize);
614 		} else {
615 			kprop->pr_perm_flags = MAC_PROP_PERM_RW;
616 			err = mac_get_prop(dlp->dl_mh, &macprop, kprop->pr_val,
617 			    kprop->pr_valsize, &kprop->pr_perm_flags);
618 		}
619 	}
620 
621 done:
622 	if (!set && err == 0 &&
623 	    ddi_copyout(kprop, (void *)arg, dsize, mode) != 0)
624 		err = EFAULT;
625 
626 	if (dlp != NULL)
627 		dls_link_rele(dlp);
628 
629 	if (mph != NULL) {
630 		int32_t	cpuid;
631 		void	*mdip = NULL;
632 
633 		if (dlp != NULL && set && err == 0) {
634 			cpuid = mac_client_intr_cpu(dlp->dl_mch);
635 			mdip = mac_get_devinfo(dlp->dl_mh);
636 		}
637 
638 		mac_perim_exit(mph);
639 
640 		if (mdip != NULL)
641 			mac_client_set_intr_cpu(mdip, dlp->dl_mch, cpuid);
642 	}
643 	if (dlh != NULL)
644 		dls_devnet_rele_tmp(dlh);
645 
646 	if (kprop != NULL)
647 		kmem_free(kprop, dsize);
648 	return (err);
649 }
650 
651 /* ARGSUSED */
652 static int
653 drv_ioc_setprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
654 {
655 	return (drv_ioc_prop_common(karg, arg, B_TRUE, mode));
656 }
657 
658 /* ARGSUSED */
659 static int
660 drv_ioc_getprop(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
661 {
662 	return (drv_ioc_prop_common(karg, arg, B_FALSE, mode));
663 }
664 
665 /*
666  * DLDIOC_RENAME.
667  *
668  * This function handles two cases of link renaming. See more in comments above
669  * dls_datalink_rename().
670  */
671 /* ARGSUSED */
672 static int
673 drv_ioc_rename(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
674 {
675 	dld_ioc_rename_t	*dir = karg;
676 	mod_hash_key_t		key;
677 	mod_hash_val_t		val;
678 	int			err;
679 
680 	if ((err = dls_devnet_rename(dir->dir_linkid1, dir->dir_linkid2,
681 	    dir->dir_link)) != 0)
682 		return (err);
683 
684 	if (dir->dir_linkid2 == DATALINK_INVALID_LINKID)
685 		return (0);
686 
687 	/*
688 	 * if dir_linkid2 is not DATALINK_INVALID_LINKID, it means this
689 	 * renaming request is to rename a valid physical link (dir_linkid1)
690 	 * to a "removed" physical link (dir_linkid2, which is removed by DR
691 	 * or during system shutdown). In this case, the link (specified by
692 	 * dir_linkid1) would inherit all the configuration of dir_linkid2,
693 	 * and dir_linkid1 and its configuration would be lost.
694 	 *
695 	 * Remove per-link autopush configuration of dir_linkid1 in this case.
696 	 */
697 	key = (mod_hash_key_t)(uintptr_t)dir->dir_linkid1;
698 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
699 	if (mod_hash_find(dld_ap_hashp, key, &val) != 0) {
700 		rw_exit(&dld_ap_hash_lock);
701 		return (0);
702 	}
703 
704 	VERIFY(mod_hash_remove(dld_ap_hashp, key, &val) == 0);
705 	kmem_free(val, sizeof (dld_ap_t));
706 	rw_exit(&dld_ap_hash_lock);
707 	return (0);
708 }
709 
710 static int
711 drv_ioc_setap(datalink_id_t linkid, struct dlautopush *dlap)
712 {
713 	dld_ap_t	*dap;
714 	int		i;
715 	mod_hash_key_t	key;
716 
717 	if (dlap->dap_npush == 0 || dlap->dap_npush > MAXAPUSH)
718 		return (EINVAL);
719 
720 	/*
721 	 * Validate that the specified list of modules exist.
722 	 */
723 	for (i = 0; i < dlap->dap_npush; i++) {
724 		if (fmodsw_find(dlap->dap_aplist[i], FMODSW_LOAD) == NULL)
725 			return (EINVAL);
726 	}
727 
728 
729 	key = (mod_hash_key_t)(uintptr_t)linkid;
730 
731 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
732 	if (mod_hash_find(dld_ap_hashp, key, (mod_hash_val_t *)&dap) != 0) {
733 		dap = kmem_zalloc(sizeof (dld_ap_t), KM_NOSLEEP);
734 		if (dap == NULL) {
735 			rw_exit(&dld_ap_hash_lock);
736 			return (ENOMEM);
737 		}
738 
739 		dap->da_linkid = linkid;
740 		VERIFY(mod_hash_insert(dld_ap_hashp, key,
741 		    (mod_hash_val_t)dap) == 0);
742 	}
743 
744 	/*
745 	 * Update the configuration.
746 	 */
747 	dap->da_anchor = dlap->dap_anchor;
748 	dap->da_npush = dlap->dap_npush;
749 	for (i = 0; i < dlap->dap_npush; i++) {
750 		(void) strlcpy(dap->da_aplist[i], dlap->dap_aplist[i],
751 		    FMNAMESZ + 1);
752 	}
753 	rw_exit(&dld_ap_hash_lock);
754 
755 	return (0);
756 }
757 
758 static int
759 drv_ioc_getap(datalink_id_t linkid, struct dlautopush *dlap)
760 {
761 	dld_ap_t	*dap;
762 	int		i;
763 
764 	rw_enter(&dld_ap_hash_lock, RW_READER);
765 	if (mod_hash_find(dld_ap_hashp,
766 	    (mod_hash_key_t)(uintptr_t)linkid,
767 	    (mod_hash_val_t *)&dap) != 0) {
768 		rw_exit(&dld_ap_hash_lock);
769 		return (ENOENT);
770 	}
771 
772 	/*
773 	 * Retrieve the configuration.
774 	 */
775 	dlap->dap_anchor = dap->da_anchor;
776 	dlap->dap_npush = dap->da_npush;
777 	for (i = 0; i < dap->da_npush; i++) {
778 		(void) strlcpy(dlap->dap_aplist[i], dap->da_aplist[i],
779 		    FMNAMESZ + 1);
780 	}
781 	rw_exit(&dld_ap_hash_lock);
782 
783 	return (0);
784 }
785 
786 static int
787 drv_ioc_clrap(datalink_id_t linkid)
788 {
789 	mod_hash_val_t	val;
790 	mod_hash_key_t	key;
791 
792 	key = (mod_hash_key_t)(uintptr_t)linkid;
793 
794 	rw_enter(&dld_ap_hash_lock, RW_WRITER);
795 	if (mod_hash_find(dld_ap_hashp, key, &val) != 0) {
796 		rw_exit(&dld_ap_hash_lock);
797 		return (0);
798 	}
799 
800 	VERIFY(mod_hash_remove(dld_ap_hashp, key, &val) == 0);
801 	kmem_free(val, sizeof (dld_ap_t));
802 	rw_exit(&dld_ap_hash_lock);
803 	return (0);
804 }
805 
806 /*
807  * DLDIOC_DOORSERVER
808  */
809 /* ARGSUSED */
810 static int
811 drv_ioc_doorserver(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
812 {
813 	dld_ioc_door_t	*did = karg;
814 
815 	return (dls_mgmt_door_set(did->did_start_door));
816 }
817 
818 /*
819  * DLDIOC_USAGELOG
820  */
821 /* ARGSUSED */
822 static int
823 drv_ioc_usagelog(void *karg, intptr_t arg, int mode, cred_t *cred,
824     int *rvalp)
825 {
826 	dld_ioc_usagelog_t	*log_info = (dld_ioc_usagelog_t *)karg;
827 
828 	if (log_info->ul_type < MAC_LOGTYPE_LINK ||
829 	    log_info->ul_type > MAC_LOGTYPE_FLOW)
830 		return (EINVAL);
831 
832 	if (log_info->ul_onoff)
833 		mac_start_logusage(log_info->ul_type, log_info->ul_interval);
834 	else
835 		mac_stop_logusage(log_info->ul_type);
836 	return (0);
837 }
838 
839 /*
840  * Process a DLDIOC_ADDFLOW request.
841  */
842 /* ARGSUSED */
843 static int
844 drv_ioc_addflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
845 {
846 	dld_ioc_addflow_t	*afp = karg;
847 
848 	return (dld_add_flow(afp->af_linkid, afp->af_name,
849 	    &afp->af_flow_desc, &afp->af_resource_props));
850 }
851 
852 /*
853  * Process a DLDIOC_REMOVEFLOW request.
854  */
855 /* ARGSUSED */
856 static int
857 drv_ioc_removeflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
858 {
859 	dld_ioc_removeflow_t	*rfp = karg;
860 
861 	return (dld_remove_flow(rfp->rf_name));
862 }
863 
864 /*
865  * Process a DLDIOC_MODIFYFLOW request.
866  */
867 /* ARGSUSED */
868 static int
869 drv_ioc_modifyflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
870 {
871 	dld_ioc_modifyflow_t	*mfp = karg;
872 
873 	return (dld_modify_flow(mfp->mf_name, &mfp->mf_resource_props));
874 }
875 
876 /*
877  * Process a DLDIOC_WALKFLOW request.
878  */
879 /* ARGSUSED */
880 static int
881 drv_ioc_walkflow(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
882 {
883 	dld_ioc_walkflow_t	*wfp = karg;
884 
885 	return (dld_walk_flow(wfp, arg));
886 }
887 
888 /*
889  * Check for GLDv3 autopush information.  There are three cases:
890  *
891  *   1. If devp points to a GLDv3 datalink and it has autopush configuration,
892  *	fill dlap in with that information and return 0.
893  *
894  *   2. If devp points to a GLDv3 datalink but it doesn't have autopush
895  *	configuration, then replace devp with the physical device (if one
896  *	exists) and return 1.  This allows stropen() to find the old-school
897  *	per-driver autopush configuration.  (For softmac, the result is that
898  *	the softmac dev_t is replaced with the legacy device's dev_t).
899  *
900  *   3. If neither of the above apply, don't touch the args and return -1.
901  */
902 int
903 dld_autopush(dev_t *devp, struct dlautopush *dlap)
904 {
905 	dld_ap_t	*dap;
906 	datalink_id_t	linkid;
907 	dev_t		phydev;
908 
909 	if (!GLDV3_DRV(getmajor(*devp)))
910 		return (-1);
911 
912 	/*
913 	 * Find the linkid by the link's dev_t.
914 	 */
915 	if (dls_devnet_dev2linkid(*devp, &linkid) != 0)
916 		return (-1);
917 
918 	/*
919 	 * Find the autopush configuration associated with the linkid.
920 	 */
921 	rw_enter(&dld_ap_hash_lock, RW_READER);
922 	if (mod_hash_find(dld_ap_hashp, (mod_hash_key_t)(uintptr_t)linkid,
923 	    (mod_hash_val_t *)&dap) == 0) {
924 		*dlap = dap->da_ap;
925 		rw_exit(&dld_ap_hash_lock);
926 		return (0);
927 	}
928 	rw_exit(&dld_ap_hash_lock);
929 
930 	if (dls_devnet_phydev(linkid, &phydev) != 0)
931 		return (-1);
932 
933 	*devp = phydev;
934 	return (1);
935 }
936 
937 /*
938  * Secure objects implementation
939  */
940 
941 /* ARGSUSED */
942 static int
943 drv_secobj_ctor(void *buf, void *arg, int kmflag)
944 {
945 	bzero(buf, sizeof (dld_secobj_t));
946 	return (0);
947 }
948 
949 static void
950 drv_secobj_init(void)
951 {
952 	rw_init(&drv_secobj_lock, NULL, RW_DEFAULT, NULL);
953 	drv_secobj_cachep = kmem_cache_create("drv_secobj_cache",
954 	    sizeof (dld_secobj_t), 0, drv_secobj_ctor, NULL,
955 	    NULL, NULL, NULL, 0);
956 	drv_secobj_hash = mod_hash_create_extended("drv_secobj_hash",
957 	    SECOBJ_WEP_HASHSZ, mod_hash_null_keydtor, mod_hash_null_valdtor,
958 	    mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
959 }
960 
961 static void
962 drv_secobj_fini(void)
963 {
964 	mod_hash_destroy_hash(drv_secobj_hash);
965 	kmem_cache_destroy(drv_secobj_cachep);
966 	rw_destroy(&drv_secobj_lock);
967 }
968 
969 /* ARGSUSED */
970 static int
971 drv_ioc_secobj_set(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
972 {
973 	dld_ioc_secobj_set_t	*ssp = karg;
974 	dld_secobj_t		*sobjp, *objp;
975 	int			err;
976 
977 	sobjp = &ssp->ss_obj;
978 
979 	if (sobjp->so_class != DLD_SECOBJ_CLASS_WEP &&
980 	    sobjp->so_class != DLD_SECOBJ_CLASS_WPA)
981 		return (EINVAL);
982 
983 	if (sobjp->so_name[DLD_SECOBJ_NAME_MAX - 1] != '\0' ||
984 	    sobjp->so_len > DLD_SECOBJ_VAL_MAX)
985 		return (EINVAL);
986 
987 	rw_enter(&drv_secobj_lock, RW_WRITER);
988 	err = mod_hash_find(drv_secobj_hash, (mod_hash_key_t)sobjp->so_name,
989 	    (mod_hash_val_t *)&objp);
990 	if (err == 0) {
991 		if ((ssp->ss_flags & DLD_SECOBJ_OPT_CREATE) != 0) {
992 			rw_exit(&drv_secobj_lock);
993 			return (EEXIST);
994 		}
995 	} else {
996 		ASSERT(err == MH_ERR_NOTFOUND);
997 		if ((ssp->ss_flags & DLD_SECOBJ_OPT_CREATE) == 0) {
998 			rw_exit(&drv_secobj_lock);
999 			return (ENOENT);
1000 		}
1001 		objp = kmem_cache_alloc(drv_secobj_cachep, KM_SLEEP);
1002 		(void) strlcpy(objp->so_name, sobjp->so_name,
1003 		    DLD_SECOBJ_NAME_MAX);
1004 
1005 		VERIFY(mod_hash_insert(drv_secobj_hash,
1006 		    (mod_hash_key_t)objp->so_name, (mod_hash_val_t)objp) == 0);
1007 	}
1008 	bcopy(sobjp->so_val, objp->so_val, sobjp->so_len);
1009 	objp->so_len = sobjp->so_len;
1010 	objp->so_class = sobjp->so_class;
1011 	rw_exit(&drv_secobj_lock);
1012 	return (0);
1013 }
1014 
1015 typedef struct dld_secobj_state {
1016 	uint_t		ss_free;
1017 	uint_t		ss_count;
1018 	int		ss_rc;
1019 	int		ss_mode;
1020 	dld_secobj_t	*ss_objp;
1021 } dld_secobj_state_t;
1022 
1023 /* ARGSUSED */
1024 static uint_t
1025 drv_secobj_walker(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
1026 {
1027 	dld_secobj_state_t	*statep = arg;
1028 	dld_secobj_t		*sobjp = (dld_secobj_t *)val;
1029 
1030 	if (statep->ss_free < sizeof (dld_secobj_t)) {
1031 		statep->ss_rc = ENOSPC;
1032 		return (MH_WALK_TERMINATE);
1033 	}
1034 	if (ddi_copyout(sobjp, statep->ss_objp, sizeof (*sobjp),
1035 	    statep->ss_mode) != 0) {
1036 		statep->ss_rc = EFAULT;
1037 		return (MH_WALK_TERMINATE);
1038 	}
1039 	statep->ss_objp++;
1040 	statep->ss_free -= sizeof (dld_secobj_t);
1041 	statep->ss_count++;
1042 	return (MH_WALK_CONTINUE);
1043 }
1044 
1045 /* ARGSUSED */
1046 static int
1047 drv_ioc_secobj_get(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1048 {
1049 	dld_ioc_secobj_get_t	*sgp = karg;
1050 	dld_secobj_t		*sobjp, *objp;
1051 	int			err;
1052 
1053 	sobjp = &sgp->sg_obj;
1054 	if (sobjp->so_name[DLD_SECOBJ_NAME_MAX - 1] != '\0')
1055 		return (EINVAL);
1056 
1057 	rw_enter(&drv_secobj_lock, RW_READER);
1058 	if (sobjp->so_name[0] != '\0') {
1059 		err = mod_hash_find(drv_secobj_hash,
1060 		    (mod_hash_key_t)sobjp->so_name, (mod_hash_val_t *)&objp);
1061 		if (err != 0) {
1062 			ASSERT(err == MH_ERR_NOTFOUND);
1063 			rw_exit(&drv_secobj_lock);
1064 			return (ENOENT);
1065 		}
1066 		bcopy(objp->so_val, sobjp->so_val, objp->so_len);
1067 		sobjp->so_len = objp->so_len;
1068 		sobjp->so_class = objp->so_class;
1069 		sgp->sg_count = 1;
1070 	} else {
1071 		dld_secobj_state_t	state;
1072 
1073 		state.ss_free = sgp->sg_size - sizeof (dld_ioc_secobj_get_t);
1074 		state.ss_count = 0;
1075 		state.ss_rc = 0;
1076 		state.ss_mode = mode;
1077 		state.ss_objp = (dld_secobj_t *)((uchar_t *)arg +
1078 		    sizeof (dld_ioc_secobj_get_t));
1079 
1080 		mod_hash_walk(drv_secobj_hash, drv_secobj_walker, &state);
1081 		if (state.ss_rc != 0) {
1082 			rw_exit(&drv_secobj_lock);
1083 			return (state.ss_rc);
1084 		}
1085 		sgp->sg_count = state.ss_count;
1086 	}
1087 	rw_exit(&drv_secobj_lock);
1088 	return (0);
1089 }
1090 
1091 /* ARGSUSED */
1092 static int
1093 drv_ioc_secobj_unset(void *karg, intptr_t arg, int mode, cred_t *cred,
1094     int *rvalp)
1095 {
1096 	dld_ioc_secobj_unset_t	*sup = karg;
1097 	dld_secobj_t		*objp;
1098 	mod_hash_val_t		val;
1099 	int			err;
1100 
1101 	if (sup->su_name[DLD_SECOBJ_NAME_MAX - 1] != '\0')
1102 		return (EINVAL);
1103 
1104 	rw_enter(&drv_secobj_lock, RW_WRITER);
1105 	err = mod_hash_find(drv_secobj_hash, (mod_hash_key_t)sup->su_name,
1106 	    (mod_hash_val_t *)&objp);
1107 	if (err != 0) {
1108 		ASSERT(err == MH_ERR_NOTFOUND);
1109 		rw_exit(&drv_secobj_lock);
1110 		return (ENOENT);
1111 	}
1112 	VERIFY(mod_hash_remove(drv_secobj_hash, (mod_hash_key_t)sup->su_name,
1113 	    (mod_hash_val_t *)&val) == 0);
1114 	ASSERT(objp == (dld_secobj_t *)val);
1115 
1116 	kmem_cache_free(drv_secobj_cachep, objp);
1117 	rw_exit(&drv_secobj_lock);
1118 	return (0);
1119 }
1120 
1121 static int
1122 drv_check_policy(dld_ioc_info_t *info, cred_t *cred)
1123 {
1124 	int	i, err = 0;
1125 
1126 	for (i = 0; info->di_priv[i] != NULL && i < DLD_MAX_PRIV; i++) {
1127 		if ((err = secpolicy_dld_ioctl(cred, info->di_priv[i],
1128 		    "dld ioctl")) != 0) {
1129 			break;
1130 		}
1131 	}
1132 	if (err == 0)
1133 		return (0);
1134 
1135 	return (secpolicy_net_config(cred, B_FALSE));
1136 }
1137 
1138 static dld_ioc_info_t drv_ioc_list[] = {
1139 	{DLDIOC_ATTR, DLDCOPYINOUT, sizeof (dld_ioc_attr_t),
1140 	    drv_ioc_attr, {NULL}},
1141 	{DLDIOC_PHYS_ATTR, DLDCOPYINOUT, sizeof (dld_ioc_phys_attr_t),
1142 	    drv_ioc_phys_attr, {NULL}},
1143 	{DLDIOC_SECOBJ_SET, DLDCOPYIN, sizeof (dld_ioc_secobj_set_t),
1144 	    drv_ioc_secobj_set, {PRIV_SYS_DL_CONFIG}},
1145 	{DLDIOC_SECOBJ_GET, DLDCOPYINOUT, sizeof (dld_ioc_secobj_get_t),
1146 	    drv_ioc_secobj_get, {PRIV_SYS_DL_CONFIG}},
1147 	{DLDIOC_SECOBJ_UNSET, DLDCOPYIN, sizeof (dld_ioc_secobj_unset_t),
1148 	    drv_ioc_secobj_unset, {PRIV_SYS_DL_CONFIG}},
1149 	{DLDIOC_DOORSERVER, DLDCOPYIN, sizeof (dld_ioc_door_t),
1150 	    drv_ioc_doorserver, {PRIV_SYS_DL_CONFIG}},
1151 	{DLDIOC_RENAME, DLDCOPYIN, sizeof (dld_ioc_rename_t),
1152 	    drv_ioc_rename, {PRIV_SYS_DL_CONFIG}},
1153 	{DLDIOC_MACADDRGET, DLDCOPYINOUT, sizeof (dld_ioc_macaddrget_t),
1154 	    drv_ioc_macaddrget, {PRIV_SYS_DL_CONFIG}},
1155 	{DLDIOC_ADDFLOW, DLDCOPYIN, sizeof (dld_ioc_addflow_t),
1156 	    drv_ioc_addflow, {PRIV_SYS_DL_CONFIG}},
1157 	{DLDIOC_REMOVEFLOW, DLDCOPYIN, sizeof (dld_ioc_removeflow_t),
1158 	    drv_ioc_removeflow, {PRIV_SYS_DL_CONFIG}},
1159 	{DLDIOC_MODIFYFLOW, DLDCOPYIN, sizeof (dld_ioc_modifyflow_t),
1160 	    drv_ioc_modifyflow, {PRIV_SYS_DL_CONFIG}},
1161 	{DLDIOC_WALKFLOW, DLDCOPYINOUT, sizeof (dld_ioc_walkflow_t),
1162 	    drv_ioc_walkflow, {NULL}},
1163 	{DLDIOC_USAGELOG, DLDCOPYIN, sizeof (dld_ioc_usagelog_t),
1164 	    drv_ioc_usagelog, {PRIV_SYS_DL_CONFIG}},
1165 	{DLDIOC_SETMACPROP, DLDCOPYIN, sizeof (dld_ioc_macprop_t),
1166 	    drv_ioc_setprop, {PRIV_SYS_DL_CONFIG}},
1167 	{DLDIOC_GETMACPROP, DLDCOPYIN, sizeof (dld_ioc_macprop_t),
1168 	    drv_ioc_getprop, {NULL}},
1169 	{DLDIOC_GETHWGRP, DLDCOPYINOUT, sizeof (dld_ioc_hwgrpget_t),
1170 	    drv_ioc_hwgrpget, {PRIV_SYS_DL_CONFIG}},
1171 };
1172 
1173 typedef struct dld_ioc_modentry {
1174 	uint16_t	dim_modid;	/* Top 16 bits of ioctl command */
1175 	char		*dim_modname;	/* Module to be loaded */
1176 	dld_ioc_info_t	*dim_list;	/* array of ioctl structures */
1177 	uint_t		dim_count;	/* number of elements in dim_list */
1178 } dld_ioc_modentry_t;
1179 
1180 /*
1181  * For all modules except for dld, dim_list and dim_count are assigned
1182  * when the modules register their ioctls in dld_ioc_register().  We
1183  * can statically initialize dld's ioctls in-line here; there's no
1184  * need for it to call dld_ioc_register() itself.
1185  */
1186 static dld_ioc_modentry_t dld_ioc_modtable[] = {
1187 	{DLD_IOC,	"dld",	drv_ioc_list, DLDIOCCNT(drv_ioc_list)},
1188 	{AGGR_IOC,	"aggr",	NULL, 0},
1189 	{VNIC_IOC,	"vnic",	NULL, 0}
1190 };
1191 #define	DLDIOC_CNT	\
1192 	(sizeof (dld_ioc_modtable) / sizeof (dld_ioc_modentry_t))
1193 
1194 static dld_ioc_modentry_t *
1195 dld_ioc_findmod(uint16_t modid)
1196 {
1197 	int	i;
1198 
1199 	for (i = 0; i < DLDIOC_CNT; i++) {
1200 		if (modid == dld_ioc_modtable[i].dim_modid)
1201 			return (&dld_ioc_modtable[i]);
1202 	}
1203 	return (NULL);
1204 }
1205 
1206 int
1207 dld_ioc_register(uint16_t modid, dld_ioc_info_t *list, uint_t count)
1208 {
1209 	dld_ioc_modentry_t *dim = dld_ioc_findmod(modid);
1210 
1211 	if (dim == NULL)
1212 		return (ENOENT);
1213 
1214 	dim->dim_list = list;
1215 	dim->dim_count = count;
1216 	return (0);
1217 }
1218 
1219 void
1220 dld_ioc_unregister(uint16_t modid)
1221 {
1222 	VERIFY(dld_ioc_register(modid, NULL, 0) == 0);
1223 }
1224 
1225 /*
1226  * The general design with GLDv3 ioctls is that all ioctls issued
1227  * through /dev/dld go through this drv_ioctl() function.  This
1228  * function handles all ioctls on behalf of modules listed in
1229  * dld_ioc_modtable.
1230  *
1231  * When an ioctl is received, this function looks for the associated
1232  * module-id-specific ioctl information using dld_ioc_findmod().  The
1233  * call to ddi_hold_devi_by_instance() on the associated device will
1234  * cause the kernel module responsible for the ioctl to be loaded if
1235  * it's not already loaded, which should result in that module calling
1236  * dld_ioc_register(), thereby filling in the dim_list containing the
1237  * details for the ioctl being processed.
1238  *
1239  * This function can then perform operations such as copyin() data and
1240  * do credential checks based on the registered ioctl information,
1241  * then issue the callback function di_func() registered by the
1242  * responsible module.  Upon return, the appropriate copyout()
1243  * operation can be performed and the operation completes.
1244  */
1245 /* ARGSUSED */
1246 static int
1247 drv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred, int *rvalp)
1248 {
1249 	dld_ioc_modentry_t *dim;
1250 	dld_ioc_info_t	*info;
1251 	dev_info_t	*dip = NULL;
1252 	void		*buf = NULL;
1253 	size_t		sz;
1254 	int		i, err;
1255 
1256 	if ((dim = dld_ioc_findmod(DLD_IOC_MODID(cmd))) == NULL)
1257 		return (ENOTSUP);
1258 
1259 	dip = ddi_hold_devi_by_instance(ddi_name_to_major(dim->dim_modname),
1260 	    0, 0);
1261 	if (dip == NULL || dim->dim_list == NULL) {
1262 		err = ENODEV;
1263 		goto done;
1264 	}
1265 
1266 	for (i = 0; i < dim->dim_count; i++) {
1267 		if (cmd == dim->dim_list[i].di_cmd)
1268 			break;
1269 	}
1270 	if (i == dim->dim_count) {
1271 		err = ENOTSUP;
1272 		goto done;
1273 	}
1274 
1275 	info = &dim->dim_list[i];
1276 	if ((err = drv_check_policy(info, cred)) != 0)
1277 		goto done;
1278 
1279 	sz = info->di_argsize;
1280 	if ((buf = kmem_zalloc(sz, KM_NOSLEEP)) == NULL) {
1281 		err = ENOMEM;
1282 		goto done;
1283 	}
1284 
1285 	if ((info->di_flags & DLDCOPYIN) &&
1286 	    ddi_copyin((void *)arg, buf, sz, mode) != 0) {
1287 		err = EFAULT;
1288 		goto done;
1289 	}
1290 
1291 	err = info->di_func(buf, arg, mode, cred, rvalp);
1292 
1293 	if ((info->di_flags & DLDCOPYOUT) &&
1294 	    ddi_copyout(buf, (void *)arg, sz, mode) != 0 && err == 0)
1295 		err = EFAULT;
1296 
1297 done:
1298 	if (buf != NULL)
1299 		kmem_free(buf, sz);
1300 	if (dip != NULL)
1301 		ddi_release_devi(dip);
1302 	return (err);
1303 }
1304