xref: /titanic_41/usr/src/uts/common/io/sad.c (revision 7cdcb8abb9b4700219e82028dac3d78699314b6d)
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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 
32 /*
33  * STREAMS Administrative Driver
34  *
35  * Currently only handles autopush and module name verification.
36  */
37 
38 #include <sys/types.h>
39 #include <sys/param.h>
40 #include <sys/errno.h>
41 #include <sys/stream.h>
42 #include <sys/stropts.h>
43 #include <sys/strsubr.h>
44 #include <sys/strsun.h>
45 #include <sys/conf.h>
46 #include <sys/sad.h>
47 #include <sys/cred.h>
48 #include <sys/debug.h>
49 #include <sys/ddi.h>
50 #include <sys/sunddi.h>
51 #include <sys/stat.h>
52 #include <sys/cmn_err.h>
53 #include <sys/systm.h>
54 #include <sys/modctl.h>
55 #include <sys/priv_names.h>
56 #include <sys/sysmacros.h>
57 #include <sys/zone.h>
58 
59 static int sadopen(queue_t *, dev_t *, int, int, cred_t *);
60 static int sadclose(queue_t *, int, cred_t *);
61 static int sadwput(queue_t *qp, mblk_t *mp);
62 
63 static int sad_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
64 static int sad_attach(dev_info_t *, ddi_attach_cmd_t);
65 
66 static void apush_ioctl(), apush_iocdata();
67 static void vml_ioctl(), vml_iocdata();
68 static int valid_major(major_t);
69 
70 static dev_info_t *sad_dip;		/* private copy of devinfo pointer */
71 
72 static struct module_info sad_minfo = {
73 	0x7361, "sad", 0, INFPSZ, 0, 0
74 };
75 
76 static struct qinit sad_rinit = {
77 	NULL, NULL, sadopen, sadclose, NULL, &sad_minfo, NULL
78 };
79 
80 static struct qinit sad_winit = {
81 	sadwput, NULL, NULL, NULL, NULL, &sad_minfo, NULL
82 };
83 
84 struct streamtab sadinfo = {
85 	&sad_rinit, &sad_winit, NULL, NULL
86 };
87 
88 DDI_DEFINE_STREAM_OPS(sad_ops, nulldev, nulldev, sad_attach,
89     nodev, nodev, sad_info,
90     D_MP | D_MTPERQ | D_MTOUTPERIM | D_MTOCEXCL, &sadinfo);
91 
92 /*
93  * Module linkage information for the kernel.
94  */
95 
96 static struct modldrv modldrv = {
97 	&mod_driverops, /* Type of module.  This one is a pseudo driver */
98 	"STREAMS Administrative Driver 'sad' %I%",
99 	&sad_ops,	/* driver ops */
100 };
101 
102 static struct modlinkage modlinkage = {
103 	MODREV_1, &modldrv, NULL
104 };
105 
106 int
107 _init(void)
108 {
109 	return (mod_install(&modlinkage));
110 }
111 
112 int
113 _fini(void)
114 {
115 	return (mod_remove(&modlinkage));
116 }
117 
118 int
119 _info(struct modinfo *modinfop)
120 {
121 	return (mod_info(&modlinkage, modinfop));
122 }
123 
124 static int
125 sad_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
126 {
127 	int instance = ddi_get_instance(devi);
128 
129 	if (cmd != DDI_ATTACH)
130 		return (DDI_FAILURE);
131 
132 	ASSERT(instance == 0);
133 	if (instance != 0)
134 		return (DDI_FAILURE);
135 
136 	if (ddi_create_minor_node(devi, "user", S_IFCHR,
137 	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
138 		return (DDI_FAILURE);
139 	}
140 	if (ddi_create_priv_minor_node(devi, "admin", S_IFCHR,
141 	    1, DDI_PSEUDO, PRIVONLY_DEV, PRIV_SYS_CONFIG,
142 	    PRIV_SYS_CONFIG, 0666) == DDI_FAILURE) {
143 		ddi_remove_minor_node(devi, NULL);
144 		return (DDI_FAILURE);
145 	}
146 	sad_dip = devi;
147 	return (DDI_SUCCESS);
148 }
149 
150 /* ARGSUSED */
151 static int
152 sad_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
153 {
154 	int error;
155 
156 	switch (infocmd) {
157 	case DDI_INFO_DEVT2DEVINFO:
158 		if (sad_dip == NULL) {
159 			error = DDI_FAILURE;
160 		} else {
161 			*result = sad_dip;
162 			error = DDI_SUCCESS;
163 		}
164 		break;
165 	case DDI_INFO_DEVT2INSTANCE:
166 		*result = (void *)0;
167 		error = DDI_SUCCESS;
168 		break;
169 	default:
170 		error = DDI_FAILURE;
171 	}
172 	return (error);
173 }
174 
175 
176 /*
177  * sadopen() -
178  * Allocate a sad device.  Only one
179  * open at a time allowed per device.
180  */
181 /* ARGSUSED */
182 static int
183 sadopen(
184 	queue_t *qp,	/* pointer to read queue */
185 	dev_t *devp,	/* major/minor device of stream */
186 	int flag,	/* file open flags */
187 	int sflag,	/* stream open flags */
188 	cred_t *credp)	/* user credentials */
189 {
190 	int i;
191 	netstack_t *ns;
192 	str_stack_t *ss;
193 
194 	if (sflag)		/* no longer called from clone driver */
195 		return (EINVAL);
196 
197 	ns = netstack_find_by_cred(credp);
198 	ASSERT(ns != NULL);
199 	ss = ns->netstack_str;
200 	ASSERT(ss != NULL);
201 
202 	/*
203 	 * Both USRMIN and ADMMIN are clone interfaces.
204 	 */
205 	for (i = 0; i < ss->ss_sadcnt; i++)
206 		if (ss->ss_saddev[i].sa_qp == NULL)
207 			break;
208 	if (i >= ss->ss_sadcnt) {		/* no such device */
209 		netstack_rele(ss->ss_netstack);
210 		return (ENXIO);
211 	}
212 	switch (getminor(*devp)) {
213 	case USRMIN:			/* mere mortal */
214 		ss->ss_saddev[i].sa_flags = 0;
215 		break;
216 
217 	case ADMMIN:			/* privileged user */
218 		ss->ss_saddev[i].sa_flags = SADPRIV;
219 		break;
220 
221 	default:
222 		netstack_rele(ss->ss_netstack);
223 		return (EINVAL);
224 	}
225 
226 	ss->ss_saddev[i].sa_qp = qp;
227 	ss->ss_saddev[i].sa_ss = ss;
228 	qp->q_ptr = (caddr_t)&ss->ss_saddev[i];
229 	WR(qp)->q_ptr = (caddr_t)&ss->ss_saddev[i];
230 
231 	/*
232 	 * NOTE: should the ADMMIN or USRMIN minors change
233 	 * then so should the offset of 2 below
234 	 * Both USRMIN and ADMMIN are clone interfaces and
235 	 * therefore their minor numbers (0 and 1) are reserved.
236 	 */
237 	*devp = makedevice(getemajor(*devp), i + 2);
238 	qprocson(qp);
239 	return (0);
240 }
241 
242 /*
243  * sadclose() -
244  * Clean up the data structures.
245  */
246 /* ARGSUSED */
247 static int
248 sadclose(
249 	queue_t *qp,	/* pointer to read queue */
250 	int flag,	/* file open flags */
251 	cred_t *credp)	/* user credentials */
252 {
253 	struct saddev *sadp;
254 
255 	qprocsoff(qp);
256 	sadp = (struct saddev *)qp->q_ptr;
257 	sadp->sa_qp = NULL;
258 	sadp->sa_addr = NULL;
259 	netstack_rele(sadp->sa_ss->ss_netstack);
260 	sadp->sa_ss = NULL;
261 	qp->q_ptr = NULL;
262 	WR(qp)->q_ptr = NULL;
263 	return (0);
264 }
265 
266 /*
267  * sadwput() -
268  * Write side put procedure.
269  */
270 static int
271 sadwput(
272 	queue_t *qp,	/* pointer to write queue */
273 	mblk_t *mp)	/* message pointer */
274 {
275 	struct iocblk *iocp;
276 
277 	switch (mp->b_datap->db_type) {
278 	case M_FLUSH:
279 		if (*mp->b_rptr & FLUSHR) {
280 			*mp->b_rptr &= ~FLUSHW;
281 			qreply(qp, mp);
282 		} else
283 			freemsg(mp);
284 		break;
285 
286 	case M_IOCTL:
287 		iocp = (struct iocblk *)mp->b_rptr;
288 		switch (SAD_CMD(iocp->ioc_cmd)) {
289 		case SAD_CMD(SAD_SAP):
290 		case SAD_CMD(SAD_GAP):
291 			apush_ioctl(qp, mp);
292 			break;
293 
294 		case SAD_VML:
295 			vml_ioctl(qp, mp);
296 			break;
297 
298 		default:
299 			miocnak(qp, mp, 0, EINVAL);
300 			break;
301 		}
302 		break;
303 
304 	case M_IOCDATA:
305 		iocp = (struct iocblk *)mp->b_rptr;
306 		switch (SAD_CMD(iocp->ioc_cmd)) {
307 		case SAD_CMD(SAD_SAP):
308 		case SAD_CMD(SAD_GAP):
309 			apush_iocdata(qp, mp);
310 			break;
311 
312 		case SAD_VML:
313 			vml_iocdata(qp, mp);
314 			break;
315 
316 		default:
317 			cmn_err(CE_WARN,
318 			    "sadwput: invalid ioc_cmd in case M_IOCDATA: %d",
319 			    iocp->ioc_cmd);
320 			freemsg(mp);
321 			break;
322 		}
323 		break;
324 
325 	default:
326 		freemsg(mp);
327 		break;
328 	} /* switch (db_type) */
329 	return (0);
330 }
331 
332 /*
333  * apush_ioctl() -
334  * Handle the M_IOCTL messages associated with
335  * the autopush feature.
336  */
337 static void
338 apush_ioctl(
339 	queue_t *qp,	/* pointer to write queue */
340 	mblk_t *mp)	/* message pointer */
341 {
342 	struct iocblk	*iocp;
343 	struct saddev	*sadp;
344 	uint_t		size;
345 
346 	iocp = (struct iocblk *)mp->b_rptr;
347 	if (iocp->ioc_count != TRANSPARENT) {
348 		miocnak(qp, mp, 0, EINVAL);
349 		return;
350 	}
351 	if (SAD_VER(iocp->ioc_cmd) > AP_VERSION) {
352 		miocnak(qp, mp, 0, EINVAL);
353 		return;
354 	}
355 
356 	sadp = (struct saddev *)qp->q_ptr;
357 	switch (SAD_CMD(iocp->ioc_cmd)) {
358 	case SAD_CMD(SAD_SAP):
359 		if (!(sadp->sa_flags & SADPRIV)) {
360 			miocnak(qp, mp, 0, EPERM);
361 			break;
362 		}
363 		/* FALLTHRU */
364 
365 	case SAD_CMD(SAD_GAP):
366 		sadp->sa_addr = (caddr_t)*(uintptr_t *)mp->b_cont->b_rptr;
367 		if (SAD_VER(iocp->ioc_cmd) == 1)
368 			size = STRAPUSH_V1_LEN;
369 		else
370 			size = STRAPUSH_V0_LEN;
371 		mcopyin(mp, (void *)GETSTRUCT, size, NULL);
372 		qreply(qp, mp);
373 		break;
374 
375 	default:
376 		ASSERT(0);
377 		miocnak(qp, mp, 0, EINVAL);
378 		break;
379 	} /* switch (ioc_cmd) */
380 }
381 
382 /*
383  * apush_iocdata() -
384  * Handle the M_IOCDATA messages associated with
385  * the autopush feature.
386  */
387 static void
388 apush_iocdata(
389 	queue_t *qp,	/* pointer to write queue */
390 	mblk_t *mp)	/* message pointer */
391 {
392 	int i, ret;
393 	struct copyresp *csp;
394 	struct strapush *sap = NULL;
395 	struct autopush *ap, *ap_tmp;
396 	struct saddev *sadp;
397 	uint_t size;
398 	dev_t dev;
399 	str_stack_t *ss;
400 
401 	sadp = (struct saddev *)qp->q_ptr;
402 	ss = sadp->sa_ss;
403 
404 	csp = (struct copyresp *)mp->b_rptr;
405 	if (csp->cp_rval) {	/* if there was an error */
406 		freemsg(mp);
407 		return;
408 	}
409 	if (mp->b_cont) {
410 		/*
411 		 * sap needed only if mp->b_cont is set.  figure out the
412 		 * size of the expected sap structure and make sure
413 		 * enough data was supplied.
414 		 */
415 		if (SAD_VER(csp->cp_cmd) == 1)
416 			size = STRAPUSH_V1_LEN;
417 		else
418 			size = STRAPUSH_V0_LEN;
419 		if (MBLKL(mp->b_cont) < size) {
420 			miocnak(qp, mp, 0, EINVAL);
421 			return;
422 		}
423 		sap = (struct strapush *)mp->b_cont->b_rptr;
424 		dev = makedevice(sap->sap_major, sap->sap_minor);
425 	}
426 	switch (SAD_CMD(csp->cp_cmd)) {
427 	case SAD_CMD(SAD_SAP):
428 
429 		/* currently we only support one SAD_SAP command */
430 		if (((long)csp->cp_private) != GETSTRUCT) {
431 			cmn_err(CE_WARN,
432 			    "apush_iocdata: cp_private bad in SAD_SAP: %p",
433 			    (void *)csp->cp_private);
434 			miocnak(qp, mp, 0, EINVAL);
435 			return;
436 		}
437 
438 		switch (sap->sap_cmd) {
439 		default:
440 			miocnak(qp, mp, 0, EINVAL);
441 			return;
442 		case SAP_ONE:
443 		case SAP_RANGE:
444 		case SAP_ALL:
445 			/* allocate and initialize a new config */
446 			ap = sad_ap_alloc();
447 			ap->ap_common = sap->sap_common;
448 			if (SAD_VER(csp->cp_cmd) > 0)
449 				ap->ap_anchor = sap->sap_anchor;
450 			for (i = 0; i < MIN(sap->sap_npush, MAXAPUSH); i++)
451 				(void) strncpy(ap->ap_list[i],
452 				    sap->sap_list[i], FMNAMESZ);
453 
454 			/* sanity check the request */
455 			if (((ret = sad_ap_verify(ap)) != 0) ||
456 			    ((ret = valid_major(ap->ap_major)) != 0)) {
457 				sad_ap_rele(ap, ss);
458 				miocnak(qp, mp, 0, ret);
459 				return;
460 			}
461 
462 			/* check for overlapping configs */
463 			mutex_enter(&ss->ss_sad_lock);
464 			ap_tmp = sad_ap_find(&ap->ap_common, ss);
465 			if (ap_tmp != NULL) {
466 				/* already configured */
467 				mutex_exit(&ss->ss_sad_lock);
468 				sad_ap_rele(ap_tmp, ss);
469 				sad_ap_rele(ap, ss);
470 				miocnak(qp, mp, 0, EEXIST);
471 				return;
472 			}
473 
474 			/* add the new config to our hash */
475 			sad_ap_insert(ap, ss);
476 			mutex_exit(&ss->ss_sad_lock);
477 			miocack(qp, mp, 0, 0);
478 			return;
479 
480 		case SAP_CLEAR:
481 			/* sanity check the request */
482 			if (ret = valid_major(sap->sap_major)) {
483 				miocnak(qp, mp, 0, ret);
484 				return;
485 			}
486 
487 			/* search for a matching config */
488 			if ((ap = sad_ap_find_by_dev(dev, ss)) == NULL) {
489 				/* no config found */
490 				miocnak(qp, mp, 0, ENODEV);
491 				return;
492 			}
493 
494 			/*
495 			 * If we matched a SAP_RANGE config
496 			 * the minor passed in must match the
497 			 * beginning of the range exactly.
498 			 */
499 			if ((ap->ap_type == SAP_RANGE) &&
500 			    (ap->ap_minor != sap->sap_minor)) {
501 				sad_ap_rele(ap, ss);
502 				miocnak(qp, mp, 0, ERANGE);
503 				return;
504 			}
505 
506 			/*
507 			 * If we matched a SAP_ALL config
508 			 * the minor passed in must be 0.
509 			 */
510 			if ((ap->ap_type == SAP_ALL) &&
511 			    (sap->sap_minor != 0)) {
512 				sad_ap_rele(ap, ss);
513 				miocnak(qp, mp, 0, EINVAL);
514 				return;
515 			}
516 
517 			/*
518 			 * make sure someone else hasn't already
519 			 * removed this config from the hash.
520 			 */
521 			mutex_enter(&ss->ss_sad_lock);
522 			ap_tmp = sad_ap_find(&ap->ap_common, ss);
523 			if (ap_tmp != ap) {
524 				mutex_exit(&ss->ss_sad_lock);
525 				sad_ap_rele(ap_tmp, ss);
526 				sad_ap_rele(ap, ss);
527 				miocnak(qp, mp, 0, ENODEV);
528 				return;
529 			}
530 
531 			/* remove the config from the hash and return */
532 			sad_ap_remove(ap, ss);
533 			mutex_exit(&ss->ss_sad_lock);
534 
535 			/*
536 			 * Release thrice, once for sad_ap_find_by_dev(),
537 			 * once for sad_ap_find(), and once to free.
538 			 */
539 			sad_ap_rele(ap, ss);
540 			sad_ap_rele(ap, ss);
541 			sad_ap_rele(ap, ss);
542 			miocack(qp, mp, 0, 0);
543 			return;
544 		} /* switch (sap_cmd) */
545 		/*NOTREACHED*/
546 
547 	case SAD_CMD(SAD_GAP):
548 		switch ((long)csp->cp_private) {
549 
550 		case GETSTRUCT:
551 			/* sanity check the request */
552 			if (ret = valid_major(sap->sap_major)) {
553 				miocnak(qp, mp, 0, ret);
554 				return;
555 			}
556 
557 			/* search for a matching config */
558 			if ((ap = sad_ap_find_by_dev(dev, ss)) == NULL) {
559 				/* no config found */
560 				miocnak(qp, mp, 0, ENODEV);
561 				return;
562 			}
563 
564 			/* copy out the contents of the config */
565 			sap->sap_common = ap->ap_common;
566 			if (SAD_VER(csp->cp_cmd) > 0)
567 				sap->sap_anchor = ap->ap_anchor;
568 			for (i = 0; i < ap->ap_npush; i++)
569 				(void) strcpy(sap->sap_list[i], ap->ap_list[i]);
570 			for (; i < MAXAPUSH; i++)
571 				bzero(sap->sap_list[i], FMNAMESZ + 1);
572 
573 			/* release our hold on the config */
574 			sad_ap_rele(ap, ss);
575 
576 			/* copyout the results */
577 			if (SAD_VER(csp->cp_cmd) == 1)
578 				size = STRAPUSH_V1_LEN;
579 			else
580 				size = STRAPUSH_V0_LEN;
581 
582 			mcopyout(mp, (void *)GETRESULT, size, sadp->sa_addr,
583 			    NULL);
584 			qreply(qp, mp);
585 			return;
586 		case GETRESULT:
587 			miocack(qp, mp, 0, 0);
588 			return;
589 
590 		default:
591 			cmn_err(CE_WARN,
592 			    "apush_iocdata: cp_private bad case SAD_GAP: %p",
593 			    (void *)csp->cp_private);
594 			freemsg(mp);
595 			return;
596 		} /* switch (cp_private) */
597 		/*NOTREACHED*/
598 	default:	/* can't happen */
599 		ASSERT(0);
600 		freemsg(mp);
601 		return;
602 	} /* switch (cp_cmd) */
603 }
604 
605 /*
606  * vml_ioctl() -
607  * Handle the M_IOCTL message associated with a request
608  * to validate a module list.
609  */
610 static void
611 vml_ioctl(
612 	queue_t *qp,	/* pointer to write queue */
613 	mblk_t *mp)	/* message pointer */
614 {
615 	struct iocblk *iocp;
616 
617 	iocp = (struct iocblk *)mp->b_rptr;
618 	if (iocp->ioc_count != TRANSPARENT) {
619 		miocnak(qp, mp, 0, EINVAL);
620 		return;
621 	}
622 	ASSERT(SAD_CMD(iocp->ioc_cmd) == SAD_VML);
623 	mcopyin(mp, (void *)GETSTRUCT,
624 	    SIZEOF_STRUCT(str_list, iocp->ioc_flag), NULL);
625 	qreply(qp, mp);
626 }
627 
628 /*
629  * vml_iocdata() -
630  * Handle the M_IOCDATA messages associated with
631  * a request to validate a module list.
632  */
633 static void
634 vml_iocdata(
635 	queue_t *qp,	/* pointer to write queue */
636 	mblk_t *mp)	/* message pointer */
637 {
638 	long i;
639 	int	nmods;
640 	struct copyresp *csp;
641 	struct str_mlist *lp;
642 	STRUCT_HANDLE(str_list, slp);
643 	struct saddev *sadp;
644 
645 	csp = (struct copyresp *)mp->b_rptr;
646 	if (csp->cp_rval) {	/* if there was an error */
647 		freemsg(mp);
648 		return;
649 	}
650 
651 	ASSERT(SAD_CMD(csp->cp_cmd) == SAD_VML);
652 	sadp = (struct saddev *)qp->q_ptr;
653 	switch ((long)csp->cp_private) {
654 	case GETSTRUCT:
655 		STRUCT_SET_HANDLE(slp, csp->cp_flag,
656 		    (struct str_list *)mp->b_cont->b_rptr);
657 		nmods = STRUCT_FGET(slp, sl_nmods);
658 		if (nmods <= 0) {
659 			miocnak(qp, mp, 0, EINVAL);
660 			break;
661 		}
662 		sadp->sa_addr = (caddr_t)(uintptr_t)nmods;
663 
664 		mcopyin(mp, (void *)GETLIST, nmods * sizeof (struct str_mlist),
665 		    STRUCT_FGETP(slp, sl_modlist));
666 		qreply(qp, mp);
667 		break;
668 
669 	case GETLIST:
670 		lp = (struct str_mlist *)mp->b_cont->b_rptr;
671 		for (i = 0; i < (long)sadp->sa_addr; i++, lp++) {
672 			lp->l_name[FMNAMESZ] = '\0';
673 			if (fmodsw_find(lp->l_name, FMODSW_LOAD) == NULL) {
674 				miocack(qp, mp, 0, 1);
675 				return;
676 			}
677 		}
678 		miocack(qp, mp, 0, 0);
679 		break;
680 
681 	default:
682 		cmn_err(CE_WARN, "vml_iocdata: invalid cp_private value: %p",
683 		    (void *)csp->cp_private);
684 		freemsg(mp);
685 		break;
686 	} /* switch (cp_private) */
687 }
688 
689 /*
690  * Validate a major number and also verify if
691  * it is a STREAMS device.
692  * Return values: 0 if a valid STREAMS dev
693  *		  error code otherwise
694  */
695 static int
696 valid_major(major_t major)
697 {
698 	int ret = 0;
699 
700 	if (etoimajor(major) == -1)
701 		return (EINVAL);
702 
703 	/*
704 	 * attempt to load the driver 'major' and verify that
705 	 * it is a STREAMS driver.
706 	 */
707 	if (ddi_hold_driver(major) == NULL)
708 		return (EINVAL);
709 
710 	if (!STREAMSTAB(major))
711 		ret = ENOSTR;
712 
713 	ddi_rele_driver(major);
714 
715 	return (ret);
716 }
717