Home | History | Annotate | Download | only in io
      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 *
    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
    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 *
    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
    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
    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 *
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    696 _init(void)
    697 {
    698 	return (mod_install(&modlinkage));
    699 }
    700 
    701 int
    702 _fini(void)
    703 {
    704 	return (EBUSY);
    705 }
    706 
    707 int
    708 _info(struct modinfo *modinfop)
    709 {
    710 	return (mod_info(&modlinkage, modinfop));
    711 }
    712