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