xref: /titanic_44/usr/src/uts/common/io/iwscons.c (revision 193974072f41a843678abf5f61979c748687e66b)
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 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 
28 /*
29  * workstation console redirecting driver
30  *
31  * Redirects all I/O through a given device instance to the device designated
32  * as the current target, as given by the vnode associated with the first
33  * entry in the list of redirections for the given device instance.  The
34  * implementation assumes that this vnode denotes a STREAMS device; this is
35  * perhaps a bug.
36  *
37  * Supports the SRIOCSREDIR ioctl for designating a new redirection target.
38  * The new target is added to the front of a list of potentially active
39  * designees.  Should the device at the front of this list be closed, the new
40  * front entry assumes active duty.  (Stated differently, redirection targets
41  * stack, except that it's possible for entries in the interior of the stack
42  * to go away.)
43  *
44  * Supports the SRIOCISREDIR ioctl for inquiring whether the descriptor given
45  * as argument is the current front of the redirection list associated with
46  * the descriptor on which the ioctl was issued.
47  */
48 
49 #include <sys/types.h>
50 #include <sys/sysmacros.h>
51 #include <sys/open.h>
52 #include <sys/param.h>
53 #include <sys/systm.h>
54 #include <sys/signal.h>
55 #include <sys/cred.h>
56 #include <sys/user.h>
57 #include <sys/proc.h>
58 #include <sys/vnode.h>
59 #include <sys/uio.h>
60 #include <sys/file.h>
61 #include <sys/kmem.h>
62 #include <sys/stat.h>
63 #include <sys/stream.h>
64 #include <sys/stropts.h>
65 #include <sys/strsubr.h>
66 #include <sys/poll.h>
67 #include <sys/debug.h>
68 #include <sys/strredir.h>
69 #include <sys/conf.h>
70 #include <sys/ddi.h>
71 #include <sys/sunddi.h>
72 #include <sys/errno.h>
73 #include <sys/modctl.h>
74 #include <sys/sunldi.h>
75 #include <sys/consdev.h>
76 #include <sys/fs/snode.h>
77 
78 /*
79  * Global data
80  */
81 static dev_info_t	*iwscn_dip;
82 
83 /*
84  * We record the list of redirections as a linked list of iwscn_list_t
85  * structures.  We need to keep track of the target's vp, so that
86  * we can vector reads, writes, etc. off to the current designee.
87  */
88 typedef struct _iwscn_list {
89 	struct _iwscn_list	*wl_next;	/* next entry */
90 	vnode_t			*wl_vp;		/* target's vnode */
91 	int			wl_ref_cnt;	/* operation in progress */
92 	boolean_t		wl_is_console;	/* is the real console */
93 } iwscn_list_t;
94 static iwscn_list_t	*iwscn_list;
95 
96 /*
97  * iwscn_list_lock serializes modifications to the global iwscn_list list.
98  *
99  * iwscn_list_cv is used when freeing an entry from iwscn_list to allow
100  * the caller to wait till the wl_ref_cnt field is zero.
101  *
102  * iwscn_redirect_lock is used to serialize redirection requests.  This
103  * is required to ensure that all active redirection streams have
104  * the redirection streams module (redirmod) pushed on them.
105  *
106  * If both iwscn_redirect_lock and iwscn_list_lock must be held then
107  * iwscn_redirect_lock must be acquired first.
108  */
109 static kcondvar_t	iwscn_list_cv;
110 static kmutex_t		iwscn_list_lock;
111 static kmutex_t		iwscn_redirect_lock;
112 
113 /*
114  * Routines for managing iwscn_list
115  */
116 static vnode_t *
str_vp(vnode_t * vp)117 str_vp(vnode_t *vp)
118 {
119 	/*
120 	 * Here we switch to using the vnode that is linked
121 	 * to from the stream queue.  (In the case of device
122 	 * streams this will correspond to the common vnode
123 	 * for the device.)  The reason we use this vnode
124 	 * is that when wcmclose() calls srpop(), this is the
125 	 * only vnode that it has access to.
126 	 */
127 	ASSERT(vp->v_stream != NULL);
128 	return (vp->v_stream->sd_vnode);
129 }
130 
131 /*
132  * Interrupt any operations that may be outstanding against this vnode.
133  * optionally, wait for them to complete.
134  */
135 static void
srinterrupt(iwscn_list_t * lp,boolean_t wait)136 srinterrupt(iwscn_list_t *lp, boolean_t wait)
137 {
138 	ASSERT(MUTEX_HELD(&iwscn_list_lock));
139 
140 	while (lp->wl_ref_cnt != 0) {
141 		strsetrerror(lp->wl_vp, EINTR, 0, NULL);
142 		strsetwerror(lp->wl_vp, EINTR, 0, NULL);
143 		if (!wait)
144 			break;
145 		cv_wait(&iwscn_list_cv, &iwscn_list_lock);
146 	}
147 }
148 
149 /*
150  * Remove vp from the redirection list rooted at iwscn_list, should it
151  * be there. Return a pointer to the removed entry.
152  */
153 static iwscn_list_t *
srrm(vnode_t * vp)154 srrm(vnode_t *vp)
155 {
156 	iwscn_list_t	*lp, **lpp;
157 
158 	ASSERT(MUTEX_HELD(&iwscn_list_lock));
159 
160 	/* Get the stream vnode */
161 	vp = str_vp(vp);
162 	ASSERT(vp);
163 
164 	/* Look for this vnode on the redirection list */
165 	for (lpp = &iwscn_list; (lp = *lpp) != NULL; lpp = &lp->wl_next) {
166 		if (lp->wl_vp == vp)
167 			break;
168 	}
169 	if (lp != NULL)
170 		/* Found it, remove this entry from the redirection list */
171 		*lpp = lp->wl_next;
172 
173 	return (lp);
174 }
175 
176 /*
177  * Push vp onto the redirection list.
178  * If it's already there move it to the front position.
179  */
180 static void
srpush(vnode_t * vp,boolean_t is_console)181 srpush(vnode_t *vp, boolean_t is_console)
182 {
183 	iwscn_list_t	*lp;
184 
185 	ASSERT(MUTEX_HELD(&iwscn_list_lock));
186 
187 	/* Get the stream vnode */
188 	vp = str_vp(vp);
189 	ASSERT(vp);
190 
191 	/* Check if it's already on the redirection list */
192 	if ((lp = srrm(vp)) == NULL) {
193 		lp = kmem_zalloc(sizeof (*lp), KM_SLEEP);
194 		lp->wl_vp = vp;
195 		lp->wl_is_console = is_console;
196 	}
197 	/*
198 	 * Note that if this vnode was already somewhere on the redirection
199 	 * list then we removed it above and are now bumping it up to the
200 	 * front of the redirection list.
201 	 */
202 	lp->wl_next = iwscn_list;
203 	iwscn_list = lp;
204 }
205 
206 /*
207  * This vnode is no longer a valid redirection target. Terminate any current
208  * operations. If closing, wait for them to complete, then free the entry.
209  * If called because a hangup has occurred, just deprecate the entry to ensure
210  * it won't become the target again.
211  */
212 void
srpop(vnode_t * vp,boolean_t close)213 srpop(vnode_t *vp, boolean_t close)
214 {
215 	iwscn_list_t	*tlp;		/* This target's entry */
216 	iwscn_list_t	*lp, **lpp;
217 
218 	mutex_enter(&iwscn_list_lock);
219 
220 	/*
221 	 * Ensure no further operations are directed at the target
222 	 * by removing it from the redirection list.
223 	 */
224 	if ((tlp = srrm(vp)) == NULL) {
225 		/* vnode wasn't in the list */
226 		mutex_exit(&iwscn_list_lock);
227 		return;
228 	}
229 	/*
230 	 * Terminate any current operations.
231 	 * If we're closing, wait until they complete.
232 	 */
233 	srinterrupt(tlp, close);
234 
235 	if (close) {
236 		/* We're finished with this target */
237 		kmem_free(tlp, sizeof (*tlp));
238 	} else {
239 		/*
240 		 * Deprecate the entry. There's no need for a flag to indicate
241 		 * this state, it just needs to be moved to the back of the list
242 		 * behind the underlying console device. Since the underlying
243 		 * device anchors the list and is never removed, this entry can
244 		 * never return to the front again to become the target.
245 		 */
246 		for (lpp = &iwscn_list; (lp = *lpp) != NULL; )
247 			lpp = &lp->wl_next;
248 		tlp->wl_next = NULL;
249 		*lpp = tlp;
250 	}
251 	mutex_exit(&iwscn_list_lock);
252 }
253 
254 /* Get a hold on the current target */
255 static iwscn_list_t *
srhold()256 srhold()
257 {
258 	iwscn_list_t	*lp;
259 
260 	mutex_enter(&iwscn_list_lock);
261 	ASSERT(iwscn_list != NULL);
262 	lp = iwscn_list;
263 	ASSERT(lp->wl_ref_cnt >= 0);
264 	lp->wl_ref_cnt++;
265 	mutex_exit(&iwscn_list_lock);
266 
267 	return (lp);
268 }
269 
270 /* Release a hold on an entry from the redirection list */
271 static void
srrele(iwscn_list_t * lp)272 srrele(iwscn_list_t *lp)
273 {
274 	ASSERT(lp != NULL);
275 	mutex_enter(&iwscn_list_lock);
276 	ASSERT(lp->wl_ref_cnt > 0);
277 	lp->wl_ref_cnt--;
278 	cv_broadcast(&iwscn_list_cv);
279 	mutex_exit(&iwscn_list_lock);
280 }
281 
282 static int
iwscnread(dev_t dev,uio_t * uio,cred_t * cred)283 iwscnread(dev_t dev, uio_t *uio, cred_t *cred)
284 {
285 	iwscn_list_t	*lp;
286 	int		error;
287 
288 	ASSERT(getminor(dev) == 0);
289 
290 	lp = srhold();
291 	error = strread(lp->wl_vp, uio, cred);
292 	srrele(lp);
293 
294 	return (error);
295 }
296 
297 static int
iwscnwrite(dev_t dev,uio_t * uio,cred_t * cred)298 iwscnwrite(dev_t dev, uio_t *uio, cred_t *cred)
299 {
300 	iwscn_list_t	*lp;
301 	int		error;
302 
303 	ASSERT(getminor(dev) == 0);
304 
305 	lp = srhold();
306 	error = strwrite(lp->wl_vp, uio, cred);
307 	srrele(lp);
308 
309 	return (error);
310 }
311 
312 static int
iwscnpoll(dev_t dev,short events,int anyyet,short * reventsp,struct pollhead ** phpp)313 iwscnpoll(dev_t dev, short events, int anyyet, short *reventsp,
314     struct pollhead **phpp)
315 {
316 	iwscn_list_t	*lp;
317 	int		error;
318 
319 	ASSERT(getminor(dev) == 0);
320 
321 	lp = srhold();
322 	error = VOP_POLL(lp->wl_vp, events, anyyet, reventsp, phpp, NULL);
323 	srrele(lp);
324 
325 	return (error);
326 }
327 
328 static int
iwscnioctl(dev_t dev,int cmd,intptr_t arg,int flag,cred_t * cred,int * rvalp)329 iwscnioctl(dev_t dev, int cmd, intptr_t arg, int flag,
330     cred_t *cred, int *rvalp)
331 {
332 	iwscn_list_t	*lp;
333 	file_t		*f;
334 	char		modname[FMNAMESZ + 1] = " ";
335 	int		error = 0;
336 
337 	ASSERT(getminor(dev) == 0);
338 
339 	switch (cmd) {
340 	case SRIOCSREDIR:
341 		/* Serialize all pushes of the redirection module */
342 		mutex_enter(&iwscn_redirect_lock);
343 
344 		/*
345 		 * Find the vnode corresponding to the file descriptor
346 		 * argument and verify that it names a stream.
347 		 */
348 		if ((f = getf((int)arg)) == NULL) {
349 			mutex_exit(&iwscn_redirect_lock);
350 			return (EBADF);
351 		}
352 		if (f->f_vnode->v_stream == NULL) {
353 			releasef((int)arg);
354 			mutex_exit(&iwscn_redirect_lock);
355 			return (ENOSTR);
356 		}
357 
358 		/*
359 		 * If the user is trying to redirect console output
360 		 * back to the underlying console via SRIOCSREDIR
361 		 * then they are evil and we'll stop them here.
362 		 */
363 		if (str_vp(f->f_vnode) == str_vp(rwsconsvp)) {
364 			releasef((int)arg);
365 			mutex_exit(&iwscn_redirect_lock);
366 			return (EINVAL);
367 		}
368 
369 		/*
370 		 * Check if this stream already has the redirection
371 		 * module pushed onto it.  I_LOOK returns an error
372 		 * if there are no modules pushed onto the stream.
373 		 */
374 		(void) strioctl(f->f_vnode, I_LOOK, (intptr_t)modname,
375 		    FKIOCTL, K_TO_K, cred, rvalp);
376 		if (strcmp(modname, STRREDIR_MOD) != 0) {
377 
378 			/*
379 			 * Push a new instance of the redirecting module onto
380 			 * the stream, so that its close routine can notify
381 			 * us when the overall stream is closed.  (In turn,
382 			 * we'll then remove it from the redirection list.)
383 			 */
384 			error = strioctl(f->f_vnode, I_PUSH,
385 			    (intptr_t)STRREDIR_MOD, FKIOCTL, K_TO_K,
386 			    cred, rvalp);
387 
388 			if (error != 0) {
389 				releasef((int)arg);
390 				mutex_exit(&iwscn_redirect_lock);
391 				return (error);
392 			}
393 		}
394 
395 		/* Push it onto the redirection stack */
396 		mutex_enter(&iwscn_list_lock);
397 		srpush(f->f_vnode, B_FALSE);
398 		mutex_exit(&iwscn_list_lock);
399 
400 		releasef((int)arg);
401 		mutex_exit(&iwscn_redirect_lock);
402 		return (0);
403 
404 	case SRIOCISREDIR:
405 		/*
406 		 * Find the vnode corresponding to the file descriptor
407 		 * argument and verify that it names a stream.
408 		 */
409 		if ((f = getf((int)arg)) == NULL) {
410 			return (EBADF);
411 		}
412 		if (f->f_vnode->v_stream == NULL) {
413 			releasef((int)arg);
414 			return (ENOSTR);
415 		}
416 
417 		lp = srhold();
418 		*rvalp = (str_vp(f->f_vnode) == lp->wl_vp);
419 		srrele(lp);
420 		releasef((int)arg);
421 		return (0);
422 
423 	case I_POP:
424 		/*
425 		 * We need to serialize I_POP operations with
426 		 * SRIOCSREDIR operations so we don't accidently
427 		 * remove the redirection module from a stream.
428 		 */
429 		mutex_enter(&iwscn_redirect_lock);
430 		lp = srhold();
431 
432 		/*
433 		 * Here we need to protect against process that might
434 		 * try to pop off the redirection module from the
435 		 * redirected stream.  Popping other modules is allowed.
436 		 *
437 		 * It's ok to hold iwscn_list_lock while doing the
438 		 * I_LOOK since it's such a simple operation.
439 		 */
440 		(void) strioctl(lp->wl_vp, I_LOOK, (intptr_t)modname,
441 		    FKIOCTL, K_TO_K, cred, rvalp);
442 
443 		if (strcmp(STRREDIR_MOD, modname) == 0) {
444 			srrele(lp);
445 			mutex_exit(&iwscn_redirect_lock);
446 			return (EINVAL);
447 		}
448 
449 		/* Process the ioctl normally */
450 		error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL);
451 
452 		srrele(lp);
453 		mutex_exit(&iwscn_redirect_lock);
454 		return (error);
455 	}
456 
457 	/* Process the ioctl normally */
458 	lp = srhold();
459 	error = VOP_IOCTL(lp->wl_vp, cmd, arg, flag, cred, rvalp, NULL);
460 	srrele(lp);
461 	return (error);
462 }
463 
464 /* ARGSUSED */
465 static int
iwscnopen(dev_t * devp,int flag,int state,cred_t * cred)466 iwscnopen(dev_t *devp, int flag, int state, cred_t *cred)
467 {
468 	iwscn_list_t	*lp;
469 	vnode_t		*vp = rwsconsvp;
470 
471 	if (state != OTYP_CHR)
472 		return (ENXIO);
473 
474 	if (getminor(*devp) != 0)
475 		return (ENXIO);
476 
477 	/*
478 	 * You can't really open us until the console subsystem
479 	 * has been configured.
480 	 */
481 	if (rwsconsvp == NULL)
482 		return (ENXIO);
483 
484 	/*
485 	 * Check if this is the first open of this device or if
486 	 * there is currently no redirection going on.  (Ie, we're
487 	 * sending output to underlying console device.)
488 	 */
489 	mutex_enter(&iwscn_list_lock);
490 	if ((iwscn_list == NULL) || (iwscn_list->wl_vp == str_vp(vp))) {
491 		int		error = 0;
492 
493 		/* Don't hold the list lock across an VOP_OPEN */
494 		mutex_exit(&iwscn_list_lock);
495 
496 		/*
497 		 * There is currently no redirection going on.
498 		 * pass this open request onto the console driver
499 		 */
500 		error = VOP_OPEN(&vp, flag, cred, NULL);
501 		if (error != 0)
502 			return (error);
503 
504 		/* Re-acquire the list lock */
505 		mutex_enter(&iwscn_list_lock);
506 
507 		if (iwscn_list == NULL) {
508 			/* Save this vnode on the redirection list */
509 			srpush(vp, B_TRUE);
510 		} else {
511 			/*
512 			 * In this case there must already be a copy of
513 			 * this vnode on the list, so we can free up this one.
514 			 */
515 			(void) VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL);
516 		}
517 	}
518 
519 	/*
520 	 * XXX This is an ugly legacy hack that has been around
521 	 * forever.  This code is here because this driver (the
522 	 * iwscn driver) is a character driver layered over a
523 	 * streams driver.
524 	 *
525 	 * Normally streams recieve notification whenever a process
526 	 * closes its last reference to that stream so that it can
527 	 * clean up any signal handling related configuration.  (Ie,
528 	 * when a stream is configured to deliver a signal to a
529 	 * process upon certain events.)  This is a feature supported
530 	 * by the streams framework.
531 	 *
532 	 * But character/block drivers don't recieve this type
533 	 * of notification.  A character/block driver's close routine
534 	 * is only invoked upon the last close of the device.  This
535 	 * is an artifact of the multiple open/single close driver
536 	 * model currently supported by solaris.
537 	 *
538 	 * So a problem occurs when a character driver layers itself
539 	 * on top of a streams driver.  Since this driver doesn't always
540 	 * receive a close notification when a process closes its
541 	 * last reference to it, this driver can't tell the stream
542 	 * it's layered upon to clean up any signal handling
543 	 * configuration for that process.
544 	 *
545 	 * So here we hack around that by manually cleaning up the
546 	 * signal handling list upon each open.  It doesn't guarantee
547 	 * that the signaling handling data stored in the stream will
548 	 * always be up to date, but it'll be more up to date than
549 	 * it would be if we didn't do this.
550 	 *
551 	 * The real way to solve this problem would be to change
552 	 * the device framework from an multiple open/single close
553 	 * model to a multiple open/multiple close model.  Then
554 	 * character/block drivers could pass on close requests
555 	 * to streams layered underneath.
556 	 */
557 	str_cn_clean(VTOS(rwsconsvp)->s_commonvp);
558 	for (lp = iwscn_list; lp != NULL; lp = lp->wl_next) {
559 		ASSERT(lp->wl_vp->v_stream != NULL);
560 		str_cn_clean(lp->wl_vp);
561 	}
562 
563 	mutex_exit(&iwscn_list_lock);
564 	return (0);
565 }
566 
567 /* ARGSUSED */
568 static int
iwscnclose(dev_t dev,int flag,int state,cred_t * cred)569 iwscnclose(dev_t dev, int flag, int state, cred_t *cred)
570 {
571 	iwscn_list_t	*lp;
572 
573 	ASSERT(getminor(dev) == 0);
574 
575 	if (state != OTYP_CHR)
576 		return (ENXIO);
577 
578 	mutex_enter(&iwscn_list_lock);
579 	/*
580 	 * Remove each entry from the redirection list, terminate any
581 	 * current operations, wait for them to finish, then free the entry.
582 	 */
583 	while (iwscn_list != NULL) {
584 		lp = srrm(iwscn_list->wl_vp);
585 		ASSERT(lp != NULL);
586 		srinterrupt(lp, B_TRUE);
587 
588 		if (lp->wl_is_console == B_TRUE)
589 			/* Close the underlying console device. */
590 			(void) VOP_CLOSE(lp->wl_vp, 0, 1, (offset_t)0, kcred,
591 			    NULL);
592 
593 		kmem_free(lp, sizeof (*lp));
594 	}
595 	mutex_exit(&iwscn_list_lock);
596 	return (0);
597 }
598 
599 /*ARGSUSED*/
600 static int
iwscnattach(dev_info_t * devi,ddi_attach_cmd_t cmd)601 iwscnattach(dev_info_t *devi, ddi_attach_cmd_t cmd)
602 {
603 	/*
604 	 * This is a pseudo device so there will never be more than
605 	 * one instance attached at a time
606 	 */
607 	ASSERT(iwscn_dip == NULL);
608 
609 	if (ddi_create_minor_node(devi, "iwscn", S_IFCHR,
610 	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
611 		return (DDI_FAILURE);
612 	}
613 
614 	iwscn_dip = devi;
615 	mutex_init(&iwscn_list_lock, NULL, MUTEX_DRIVER, NULL);
616 	mutex_init(&iwscn_redirect_lock, NULL, MUTEX_DRIVER, NULL);
617 	cv_init(&iwscn_list_cv, NULL, CV_DRIVER, NULL);
618 
619 	return (DDI_SUCCESS);
620 }
621 
622 /* ARGSUSED */
623 static int
iwscninfo(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)624 iwscninfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
625 {
626 	int error;
627 
628 	switch (infocmd) {
629 	case DDI_INFO_DEVT2DEVINFO:
630 		if (iwscn_dip == NULL) {
631 			error = DDI_FAILURE;
632 		} else {
633 			*result = (void *)iwscn_dip;
634 			error = DDI_SUCCESS;
635 		}
636 		break;
637 	case DDI_INFO_DEVT2INSTANCE:
638 		*result = (void *)0;
639 		error = DDI_SUCCESS;
640 		break;
641 	default:
642 		error = DDI_FAILURE;
643 	}
644 	return (error);
645 }
646 
647 struct cb_ops	iwscn_cb_ops = {
648 	iwscnopen,		/* open */
649 	iwscnclose,		/* close */
650 	nodev,			/* strategy */
651 	nodev,			/* print */
652 	nodev,			/* dump */
653 	iwscnread,		/* read */
654 	iwscnwrite,		/* write */
655 	iwscnioctl,		/* ioctl */
656 	nodev,			/* devmap */
657 	nodev,			/* mmap */
658 	nodev, 			/* segmap */
659 	iwscnpoll,		/* poll */
660 	ddi_prop_op,		/* cb_prop_op */
661 	NULL,			/* streamtab  */
662 	D_MP			/* Driver compatibility flag */
663 };
664 
665 struct dev_ops	iwscn_ops = {
666 	DEVO_REV,		/* devo_rev, */
667 	0,			/* refcnt  */
668 	iwscninfo,		/* info */
669 	nulldev,		/* identify */
670 	nulldev,		/* probe */
671 	iwscnattach,		/* attach */
672 	nodev,			/* detach */
673 	nodev,			/* reset */
674 	&iwscn_cb_ops,		/* driver operations */
675 	NULL,			/* bus operations */
676 	NULL,			/* power */
677 	ddi_quiesce_not_needed,		/* quiesce */
678 };
679 
680 /*
681  * Module linkage information for the kernel.
682  */
683 static struct modldrv modldrv = {
684 	&mod_driverops, /* Type of module.  This one is a pseudo driver */
685 	"Workstation Redirection driver",
686 	&iwscn_ops,	/* driver ops */
687 };
688 
689 static struct modlinkage modlinkage = {
690 	MODREV_1,
691 	&modldrv,
692 	NULL
693 };
694 
695 int
_init(void)696 _init(void)
697 {
698 	return (mod_install(&modlinkage));
699 }
700 
701 int
_fini(void)702 _fini(void)
703 {
704 	return (EBUSY);
705 }
706 
707 int
_info(struct modinfo * modinfop)708 _info(struct modinfo *modinfop)
709 {
710 	return (mod_info(&modlinkage, modinfop));
711 }
712