OpenSolaris_b135/uts/common/io/consms.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * Console mouse driver for Sun.
 * The console "zs" port is linked under us, with the "ms" module pushed
 * on top of it.
 *
 * This device merely provides a way to have "/dev/mouse" automatically
 * have the "ms" module present. Due to problems with the way the "specfs"
 * file system works, you can't use an indirect device (a "stat" on
 * "/dev/mouse" won't get the right snode, so you won't get the right time
 * of last access), and due to problems with the kernel window system code,
 * you can't use a "cons"-like driver ("/dev/mouse" won't be a streams device,
 * even though operations on it get turned into operations on the real stream).
 *
 * This module supports multiple mice connected to the system at the same time.
 * All the mice are linked under consms, and act as a mouse with replicated
 * clicks. Only USB and PS/2 mouse are supported to be virtual mouse now.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/modctl.h>
#include <sys/consdev.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kstat.h>
#include <sys/vuid_wheel.h>
#include <sys/msio.h>
#include <sys/consms.h>

static void consms_plink(queue_t *, mblk_t *);
static int consms_punlink(queue_t *, mblk_t *);
static void
consms_lqs_ack_complete(consms_lq_t *, mblk_t *);
static void consms_add_lq(consms_lq_t *);
static void consms_check_caps(void);
static mblk_t *consms_new_firm_event(ushort_t, int);

static void consms_mux_max_wheel_report(mblk_t *);
static void consms_mux_cache_states(mblk_t *);
static void consms_mux_link_msg(consms_msg_t *);
static consms_msg_t *consms_mux_unlink_msg(uint_t);
static consms_msg_t *consms_mux_find_msg(uint_t);

static void consms_mux_iocdata(consms_msg_t *, mblk_t *);
static void consms_mux_disp_iocdata(consms_response_t *, mblk_t *);
static int consms_mux_disp_ioctl(queue_t *, mblk_t *);
static void consms_mux_copyreq(queue_t *, consms_msg_t *, mblk_t *);
static void consms_mux_ack(consms_msg_t *, mblk_t *);
static void consms_mux_disp_data(mblk_t *);


static int	consmsopen();
static int	consmsclose();
static void	consmsuwput();
static void	consmslrput();
static void	consmslwserv();

static struct module_info consmsm_info = {
	0,
	"consms",
	0,
	1024,
	2048,
	128
};

static struct qinit consmsurinit = {
	putq,
	(int (*)())NULL,
	consmsopen,
	consmsclose,
	(int (*)())NULL,
	&consmsm_info,
	NULL
};

static struct qinit consmsuwinit = {
	(int (*)())consmsuwput,
	(int (*)())NULL,
	consmsopen,
	consmsclose,
	(int (*)())NULL,
	&consmsm_info,
	NULL
};

static struct qinit consmslrinit = {
	(int (*)())consmslrput,
	(int (*)())NULL,
	(int (*)())NULL,
	(int (*)())NULL,
	(int (*)())NULL,
	&consmsm_info,
	NULL
};

static struct qinit consmslwinit = {
	putq,
	(int (*)())consmslwserv,
	(int (*)())NULL,
	(int (*)())NULL,
	(int (*)())NULL,
	&consmsm_info,
	NULL
};

static struct streamtab consms_str_info = {
	&consmsurinit,
	&consmsuwinit,
	&consmslrinit,
	&consmslwinit,
};

static void consmsioctl(queue_t *q, mblk_t *mp);
static int consms_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
		void **result);
static int consms_attach(dev_info_t *devi, ddi_attach_cmd_t cmd);
static int consms_detach(dev_info_t *devi, ddi_detach_cmd_t cmd);
static int consms_kstat_update(kstat_t *, int);

/*
 * Module global data are protected by the per-module inner perimeter.
 */
static queue_t		*upperqueue;	/* regular mouse queue above us */
static dev_info_t	*consms_dip;	/* private copy of devinfo pointer */
static long	consms_idle_stamp;	/* seconds tstamp of latest mouse op */

static consms_msg_t	*consms_mux_msg; /* ioctl messages being processed */
static	kmutex_t	consms_msg_lock; /* protect ioctl messages list */

static consms_state_t	consms_state;	/* the global virtual mouse state */
static	kmutex_t	consmslock;


/*
 * Normally, kstats of type KSTAT_TYPE_NAMED have multiple elements.  In
 * this case we use this type for a single element because the ioctl code
 * for it knows how to handle mixed kernel/user data models.  Also, it
 * will be easier to add new statistics later.
 */
static struct {
	kstat_named_t idle_sec;		/* seconds since last user op */
} consms_kstat = {
	{ "idle_sec", KSTAT_DATA_LONG, }
};


static 	struct cb_ops cb_consms_ops = {
	nulldev,		/* cb_open */
	nulldev,		/* cb_close */
	nodev,			/* cb_strategy */
	nodev,			/* cb_print */
	nodev,			/* cb_dump */
	nodev,			/* cb_read */
	nodev,			/* cb_write */
	nodev,			/* cb_ioctl */
	nodev,			/* cb_devmap */
	nodev,			/* cb_mmap */
	nodev,			/* cb_segmap */
	nochpoll,		/* cb_chpoll */
	ddi_prop_op,		/* cb_prop_op */
	&consms_str_info,	/* cb_stream */
	D_MP | D_MTPERMOD	/* cb_flag */
};

static struct dev_ops consms_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	consms_info,		/* devo_getinfo */
	nulldev,		/* devo_identify */
	nulldev,		/* devo_probe */
	consms_attach,		/* devo_attach */
	consms_detach,		/* devo_detach */
	nodev,			/* devo_reset */
	&(cb_consms_ops),	/* devo_cb_ops */
	(struct bus_ops *)NULL,	/* devo_bus_ops */
	NULL,			/* devo_power */
	ddi_quiesce_not_needed,		/* devo_quiesce */
};


/*
 * Module linkage information for the kernel.
 */

static struct modldrv modldrv = {
	&mod_driverops, /* Type of module.  This one is a pseudo driver */
	"Mouse Driver for Sun 'consms' 5.57",
	&consms_ops,	/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1,
	(void *)&modldrv,
	NULL
};

int
_init(void)
{
	int	error;

	mutex_init(&consmslock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&consms_msg_lock, NULL, MUTEX_DRIVER, NULL);
	error = mod_install(&modlinkage);
	if (error != 0) {
		mutex_destroy(&consmslock);
		mutex_destroy(&consms_msg_lock);
	}
	return (error);
}

int
_fini(void)
{
	int	error;

	error = mod_remove(&modlinkage);
	if (error != 0)
		return (error);
	mutex_destroy(&consmslock);
	mutex_destroy(&consms_msg_lock);
	return (0);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

static int
consms_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	kstat_t	*ksp;

	switch (cmd) {
	case DDI_ATTACH:
		break;
	default:
		return (DDI_FAILURE);
	}

	if (ddi_create_minor_node(devi, "mouse", S_IFCHR,
	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
		ddi_remove_minor_node(devi, NULL);
		return (-1);
	}
	consms_dip = devi;
	(void) ddi_prop_update_int(DDI_DEV_T_NONE, devi, DDI_NO_AUTODETACH, 1);

	ksp = kstat_create("consms", 0, "activity", "misc", KSTAT_TYPE_NAMED,
	    sizeof (consms_kstat) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL);
	if (ksp) {
		ksp->ks_data = (void *)&consms_kstat;
		ksp->ks_update = consms_kstat_update;
		kstat_install(ksp);
		consms_idle_stamp = gethrestime_sec();	/* initial value */
	}

	consms_state.consms_lqs = NULL;
	consms_state.consms_num_lqs = 0;

	/* default consms state values */
	consms_state.consms_vuid_format = VUID_FIRM_EVENT;
	consms_state.consms_num_buttons = 0;
	consms_state.consms_num_wheels = 0;
	consms_state.consms_wheel_state_bf |= VUID_WHEEL_STATE_ENABLED;
	consms_state.consms_ms_parms.jitter_thresh =
	    CONSMS_PARMS_DEFAULT_JITTER;
	consms_state.consms_ms_parms.speed_limit =
	    CONSMS_PARMS_DEFAULT_SPEED_LIMIT;
	consms_state.consms_ms_parms.speed_law =
	    CONSMS_PARMS_DEFAULT_SPEED_LAW;
	consms_state.consms_ms_sr.height = CONSMS_SR_DEFAULT_HEIGHT;
	consms_state.consms_ms_sr.width = CONSMS_SR_DEFAULT_WIDTH;

	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
consms_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	switch (cmd) {
	case DDI_DETACH:
	default:
		return (DDI_FAILURE);
	}
}

/*ARGSUSED*/
static int
consms_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
	void **result)
{
	register int error;

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if (consms_dip == NULL) {
			error = DDI_FAILURE;
		} else {
			*result = (void *) consms_dip;
			error = DDI_SUCCESS;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)0;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}


/*ARGSUSED*/
static int
consmsopen(q, devp, flag, sflag, crp)
	queue_t *q;
	dev_t	*devp;
	int	flag, sflag;
	cred_t	*crp;
{
	upperqueue = q;
	qprocson(q);
	return (0);
}

/*ARGSUSED*/
static int
consmsclose(q, flag, crp)
	queue_t *q;
	int	flag;
	cred_t	*crp;
{
	qprocsoff(q);
	upperqueue = NULL;
	return (0);
}

/*
 * Put procedure for upper write queue.
 */
static void
consmsuwput(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	struct iocblk		*iocbp = (struct iocblk *)mp->b_rptr;
	consms_msg_t		*msg;
	int			error = 0;

	switch (mp->b_datap->db_type) {

	case M_IOCTL:
		consmsioctl(q, mp);
		break;

	case M_FLUSH:
		if (*mp->b_rptr & FLUSHW)
			flushq(q, FLUSHDATA);
		if (*mp->b_rptr & FLUSHR)
			flushq(RD(q), FLUSHDATA);
		if (consms_state.consms_num_lqs > 0) {
			consms_mux_disp_data(mp);
		} else {
			/*
			 * No lower queue; just reflect this back upstream.
			 */
			*mp->b_rptr &= ~FLUSHW;
			if (*mp->b_rptr & FLUSHR)
				qreply(q, mp);
			else
				freemsg(mp);
		}
		break;

	case M_DATA:
		if (consms_state.consms_num_lqs > 0) {
			consms_mux_disp_data(mp);
		} else {
			freemsg(mp);
		}
		break;

	case M_IOCDATA:
		if ((msg = consms_mux_find_msg(iocbp->ioc_id)) != NULL) {
			consms_mux_iocdata(msg, mp);
		} else {
			error = EINVAL;
		}
		break;

	default:
		error = EINVAL;
		break;
	}

	if (error) {
		/*
		 * Pass an error message up.
		 */
		mp->b_datap->db_type = M_ERROR;
		if (mp->b_cont) {
			freemsg(mp->b_cont);
			mp->b_cont = NULL;
		}
		mp->b_rptr = mp->b_datap->db_base;
		mp->b_wptr = mp->b_rptr + sizeof (char);
		*mp->b_rptr = (char)error;
		qreply(q, mp);
	}
}

static void
consmsioctl(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	register struct iocblk *iocp;
	int		error;
	mblk_t		*datap;

	iocp = (struct iocblk *)mp->b_rptr;

	switch (iocp->ioc_cmd) {

	case I_LINK:
	case I_PLINK:
		mutex_enter(&consmslock);
		consms_plink(q, mp);
		mutex_exit(&consmslock);
		return;

	case I_UNLINK:
	case I_PUNLINK:
		mutex_enter(&consmslock);
		if ((error = consms_punlink(q, mp)) != 0) {
			mutex_exit(&consmslock);
			miocnak(q, mp, 0, error);
			return;
		}
		mutex_exit(&consmslock);
		iocp->ioc_count = 0;
		break;

	case MSIOBUTTONS:	/* query the number of buttons */
		if ((consms_state.consms_num_lqs <= 0) ||
		    ((datap = allocb(sizeof (int), BPRI_HI)) == NULL)) {
			miocnak(q, mp, 0, ENOMEM);
			return;
		}
		*(int *)datap->b_wptr = consms_state.consms_num_buttons;
		datap->b_wptr += sizeof (int);
		if (mp->b_cont) {
			freemsg(mp->b_cont);
		}
		mp->b_cont = datap;
		iocp->ioc_count = sizeof (int);
		break;

	default:
		/*
		 * Pass this through, if there's something to pass it
		 * through to; otherwise, reject it.
		 */
		if (consms_state.consms_num_lqs <= 0) {
			miocnak(q, mp, 0, EINVAL);
			return;
		}
		if ((error = consms_mux_disp_ioctl(q, mp)) != 0)
			miocnak(q, mp, 0, error);

		return;
	}

	/*
	 * Common exit path for calls that return a positive
	 * acknowledgment with a return value of 0.
	 */
	miocack(q, mp, iocp->ioc_count, 0);
}

/*
 * Service procedure for lower write queue.
 * Puts things on the queue below us, if it lets us.
 */
static void
consmslwserv(q)
	register queue_t *q;
{
	register mblk_t *mp;

	while (canput(q->q_next) && (mp = getq(q)) != NULL)
		putnext(q, mp);
}

/*
 * Put procedure for lower read queue.
 */
static void
consmslrput(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	struct iocblk		*iocbp = (struct iocblk *)mp->b_rptr;
	struct copyreq		*copyreq = (struct copyreq *)mp->b_rptr;
	consms_msg_t		*msg;
	consms_lq_t		*lq = (consms_lq_t *)q->q_ptr;

	ASSERT(lq != NULL);

	switch (mp->b_datap->db_type) {
	case M_FLUSH:
		if (*mp->b_rptr & FLUSHW)
			flushq(WR(q), FLUSHDATA);
		if (*mp->b_rptr & FLUSHR)
			flushq(q, FLUSHDATA);
		if (upperqueue != NULL)
			putnext(upperqueue, mp);	/* pass it through */
		else {
			/*
			 * No upper queue; just reflect this back downstream.
			 */
			*mp->b_rptr &= ~FLUSHR;
			if (*mp->b_rptr & FLUSHW)
				qreply(q, mp);
			else
				freemsg(mp);
		}
		break;

	case M_DATA:
		if (upperqueue != NULL)
			putnext(upperqueue, mp);
		else
			freemsg(mp);
		consms_idle_stamp = gethrestime_sec();
		break;

	case M_IOCACK:
	case M_IOCNAK:
		/*
		 * First, check to see if this device
		 * is still being initialized.
		 */
		if (lq->lq_ioc_reply_func != NULL) {
			mutex_enter(&consmslock);
			lq->lq_ioc_reply_func(lq, mp);
			mutex_exit(&consmslock);
			freemsg(mp);
			break;
		}

		/*
		 * This is normal ioctl ack for upper layer.
		 */
		if ((msg = consms_mux_find_msg(iocbp->ioc_id)) != NULL) {
			consms_mux_ack(msg, mp);
		} else {
			freemsg(mp);
		}
		consms_idle_stamp = gethrestime_sec();
		break;

	case M_COPYIN:
	case M_COPYOUT:
		if ((msg = consms_mux_find_msg(copyreq->cq_id)) != NULL) {
			consms_mux_copyreq(q, msg, mp);
		} else
			freemsg(mp);
		consms_idle_stamp = gethrestime_sec();
		break;

	case M_ERROR:
	case M_HANGUP:
	default:
		freemsg(mp);	/* anything useful here? */
		break;
	}
}

/* ARGSUSED */
static int
consms_kstat_update(kstat_t *ksp, int rw)
{
	if (rw == KSTAT_WRITE)
		return (EACCES);

	consms_kstat.idle_sec.value.l = gethrestime_sec() - consms_idle_stamp;
	return (0);
}

/*ARGSUSED*/
static int
consms_punlink(queue_t *q, mblk_t *mp)
{
	struct linkblk	*linkp;
	consms_lq_t	*lq;
	consms_lq_t	*prev_lq;

	ASSERT(MUTEX_HELD(&consmslock));

	linkp = (struct linkblk *)mp->b_cont->b_rptr;

	prev_lq = NULL;
	for (lq = consms_state.consms_lqs; lq != NULL; lq = lq->lq_next) {
		if (lq->lq_queue == linkp->l_qbot) {
			if (prev_lq)
				prev_lq->lq_next = lq->lq_next;
			else
				consms_state.consms_lqs = lq->lq_next;
			kmem_free(lq, sizeof (*lq));
			consms_state.consms_num_lqs--;

			/*
			 * Check to see if mouse capabilities
			 * have changed.
			 */
			consms_check_caps();

			return (0);
		}
		prev_lq = lq;
	}

	return (EINVAL);
}

/*
 * Link a specific mouse into our mouse list.
 */
static void
consms_plink(queue_t *q, mblk_t *mp)
{
	struct	linkblk	*linkp;
	consms_lq_t	*lq;
	queue_t		*lowq;

	ASSERT(MUTEX_HELD(&consmslock));

	linkp = (struct linkblk *)mp->b_cont->b_rptr;
	lowq = linkp->l_qbot;

	lq = kmem_zalloc(sizeof (*lq), KM_SLEEP);

	lowq->q_ptr = (void *)lq;
	OTHERQ(lowq)->q_ptr = (void *)lq;
	lq->lq_queue = lowq;
	lq->lq_pending_plink = mp;
	lq->lq_pending_queue = q;

	/*
	 * Set the number of buttons to 3 by default
	 * in case the following MSIOBUTTONS ioctl fails.
	 */
	lq->lq_num_buttons = 3;

	/*
	 * Begin to initialize this mouse.
	 */
	lq->lq_state = LQS_START;
	consms_lqs_ack_complete(lq, NULL);
}

/*
 * Initialize the newly hotplugged-in mouse,
 * e.g. get the number of buttons, set event
 * format. Then we add it into our list.
 */
static void
consms_lqs_ack_complete(consms_lq_t *lq, mblk_t *mp)
{
	mblk_t			*req = NULL;
	boolean_t		skipped = B_FALSE;
	wheel_state		*ws;
	Ms_screen_resolution	*sr;
	Ms_parms		*params;

	ASSERT(MUTEX_HELD(&consmslock));

	/*
	 * We try each ioctl even if the previous one fails
	 * until we reach LQS_DONE, and then add this lq
	 * into our lq list.
	 *
	 * If the message allocation fails, we skip this ioctl,
	 * set skipped flag to B_TRUE in order to skip the ioctl
	 * result, then we try next ioctl, go to next state.
	 */
	while ((lq->lq_state < LQS_DONE) && (req == NULL)) {
		switch (lq->lq_state) {
		case LQS_START:
			/*
			 * First, issue MSIOBUTTONS ioctl
			 * to get the number of buttons.
			 */
			req = mkiocb(MSIOBUTTONS);
			if (req && ((req->b_cont = allocb(sizeof (int),
			    BPRI_MED)) == NULL)) {
				freemsg(req);
				req = NULL;
			}
			if (req == NULL)
				skipped = B_TRUE;
			lq->lq_state++;
			break;

		case LQS_BUTTON_COUNT_PENDING:
			if (!skipped && mp && mp->b_cont &&
			    (mp->b_datap->db_type == M_IOCACK))
				lq->lq_num_buttons =
				    *(int *)mp->b_cont->b_rptr;

			/*
			 * Second, issue VUIDGWHEELCOUNT ioctl
			 * to get the count of wheels.
			 */
			req = mkiocb(VUIDGWHEELCOUNT);
			if (req && ((req->b_cont = allocb(sizeof (int),
			    BPRI_MED)) == NULL)) {
				freemsg(req);
				req = NULL;
			}
			if (req == NULL)
				skipped = B_TRUE;
			lq->lq_state++;
			break;

		case LQS_WHEEL_COUNT_PENDING:
			if (!skipped && mp && mp->b_cont &&
			    (mp->b_datap->db_type == M_IOCACK))
				lq->lq_num_wheels =
				    *(int *)mp->b_cont->b_rptr;

			/*
			 * Third, issue VUIDSFORMAT ioctl
			 * to set the event format.
			 */
			req = mkiocb(VUIDSFORMAT);
			if (req && ((req->b_cont = allocb(sizeof (int),
			    BPRI_MED)) == NULL)) {
				freemsg(req);
				req = NULL;
			}
			if (req) {
				*(int *)req->b_cont->b_wptr =
				    consms_state.consms_vuid_format;
				req->b_cont->b_wptr += sizeof (int);
			}
			lq->lq_state++;
			break;

		case LQS_SET_VUID_FORMAT_PENDING:
			/*
			 * Fourth, issue VUIDSWHEELSTATE ioctl
			 * to set the wheel state (enable or disable).
			 */
			req = mkiocb(VUIDSWHEELSTATE);
			if (req && ((req->b_cont = allocb(sizeof (wheel_state),
			    BPRI_MED)) == NULL)) {
				freemsg(req);
				req = NULL;
			}
			if (req) {
				ws = (wheel_state *)req->b_cont->b_wptr;
				ws->vers = VUID_WHEEL_STATE_VERS;
				ws->id = 0;	/* the first wheel */
				ws->stateflags =
				    consms_state.consms_wheel_state_bf & 1;
				req->b_cont->b_wptr += sizeof (wheel_state);
			}
			lq->lq_state++;
			break;

		case LQS_SET_WHEEL_STATE_PENDING:
			/*
			 * Fifth,  issue MSIOSETPARMS ioctl
			 * to set the parameters for USB mouse.
			 */
			req = mkiocb(MSIOSETPARMS);
			if (req && ((req->b_cont = allocb(sizeof (Ms_parms),
			    BPRI_MED)) == NULL)) {
				freemsg(req);
				req = NULL;
			}
			if (req) {
				params = (Ms_parms *)req->b_cont->b_wptr;
				*params = consms_state.consms_ms_parms;
				req->b_cont->b_wptr += sizeof (Ms_parms);
			}
			lq->lq_state++;
			break;

		case LQS_SET_PARMS_PENDING:
			/*
			 * Sixth, issue MSIOSRESOLUTION ioctl
			 * to set the screen resolution for absolute mouse.
			 */
			req = mkiocb(MSIOSRESOLUTION);
			if (req && ((req->b_cont =
			    allocb(sizeof (Ms_screen_resolution),
			    BPRI_MED)) == NULL)) {
				freemsg(req);
				req = NULL;
			}
			if (req) {
				sr =
				    (Ms_screen_resolution *)req->b_cont->b_wptr;
				*sr = consms_state.consms_ms_sr;
				req->b_cont->b_wptr +=
				    sizeof (Ms_screen_resolution);
			}
			lq->lq_state++;
			break;

		case LQS_SET_RESOLUTION_PENDING:
			/*
			 * All jobs are done, lq->lq_state is turned into
			 * LQS_DONE, and this lq is added into our list.
			 */
			lq->lq_state++;
			consms_add_lq(lq);
			break;
		}
	}

	if (lq->lq_state < LQS_DONE) {
		lq->lq_ioc_reply_func = consms_lqs_ack_complete;
		(void) putq(lq->lq_queue, req);
	}
}

/*
 * Add this specific lq into our list, finally reply
 * the previous pending I_PLINK ioctl. Also check to
 * see if mouse capabilities have changed, and send
 * a dynamical notification event to upper layer if
 * necessary.
 */
static void
consms_add_lq(consms_lq_t *lq)
{
	struct	iocblk		*iocp;

	ASSERT(MUTEX_HELD(&consmslock));

	lq->lq_ioc_reply_func = NULL;
	iocp = (struct iocblk *)lq->lq_pending_plink->b_rptr;
	iocp->ioc_error = 0;
	iocp->ioc_count = 0;
	iocp->ioc_rval = 0;
	lq->lq_pending_plink->b_datap->db_type = M_IOCACK;

	/* Reply to the I_PLINK ioctl. */
	qreply(lq->lq_pending_queue, lq->lq_pending_plink);

	lq->lq_pending_plink = NULL;
	lq->lq_pending_queue = NULL;

	/*
	 * Add this lq into list.
	 */
	consms_state.consms_num_lqs++;

	lq->lq_next = consms_state.consms_lqs;
	consms_state.consms_lqs = lq;

	/*
	 * Check to see if mouse capabilities
	 * have changed.
	 */
	consms_check_caps();

}


static void
consms_check_caps(void)
{
	consms_lq_t *lq;
	int	max_buttons = 0;
	int	max_wheels = 0;
	mblk_t	*mp;

	/*
	 * Check to see if the number of buttons
	 * and the number of wheels have changed.
	 */
	for (lq = consms_state.consms_lqs; lq != NULL; lq = lq->lq_next) {
		max_buttons = CONSMS_MAX(max_buttons, lq->lq_num_buttons);
		max_wheels = CONSMS_MAX(max_wheels, lq->lq_num_wheels);
	}

	if (max_buttons != consms_state.consms_num_buttons) {
		/*
		 * Since the number of buttons have changed,
		 * send a MOUSE_CAP_CHANGE_NUM_BUT dynamical
		 * notification event to upper layer.
		 */
		consms_state.consms_num_buttons = max_buttons;
		if (upperqueue != NULL) {
			if ((mp = consms_new_firm_event(
			    MOUSE_CAP_CHANGE_NUM_BUT,
			    consms_state.consms_num_buttons)) != NULL) {
				putnext(upperqueue, mp);
			}
		}
	}

	if (max_wheels != consms_state.consms_num_wheels) {
		/*
		 * Since the number of wheels have changed,
		 * send a MOUSE_CAP_CHANGE_NUM_WHEEL dynamical
		 * notification event to upper layer.
		 */
		consms_state.consms_num_wheels = max_wheels;
		if (upperqueue != NULL) {
			if ((mp = consms_new_firm_event(
			    MOUSE_CAP_CHANGE_NUM_WHEEL,
			    consms_state.consms_num_wheels)) != NULL) {
				putnext(upperqueue, mp);
			}
		}
	}
}

/*
 * Allocate a dynamical notification event.
 */
static mblk_t *
consms_new_firm_event(ushort_t id, int value)
{
	Firm_event *fep;
	mblk_t	*tmp;

	if ((tmp = allocb(sizeof (Firm_event), BPRI_HI)) != NULL) {
		fep = (Firm_event *)tmp->b_wptr;
		fep->id = id;
		fep->pair_type = FE_PAIR_NONE;
		fep->pair = NULL;
		fep->value = value;
		tmp->b_wptr += sizeof (Firm_event);
	}

	return (tmp);
}

/*
 * Start of dispatching interfaces as a multiplexor
 */

/*
 * There is a global msg list (consms_mux_msg),
 * which is used to link all ioctl messages from
 * upper layer, which are currently being processed.
 *
 * consms_mux_link_msg links a msg into the list,
 * consms_mux_unlink_msg unlinks a msg from the list,
 * consms_mux_find_msg finds a msg from the list
 * according to its unique id.
 *
 * The id of each msg is taken from stream's mp,
 * so the id is supposed to be unique.
 */
static void
consms_mux_link_msg(consms_msg_t *msg)
{
	mutex_enter(&consms_msg_lock);
	msg->msg_next = consms_mux_msg;
	consms_mux_msg = msg;
	mutex_exit(&consms_msg_lock);
}

static consms_msg_t *
consms_mux_unlink_msg(uint_t msg_id)
{
	consms_msg_t	*msg;
	consms_msg_t	*prev_msg;

	mutex_enter(&consms_msg_lock);
	prev_msg = NULL;
	for (msg = consms_mux_msg; msg != NULL;
	    prev_msg = msg, msg = msg->msg_next) {
		if (msg->msg_id == msg_id)
			break;
	}

	if (msg != NULL) {
		if (prev_msg != NULL) {
			prev_msg->msg_next = msg->msg_next;
		} else {
			consms_mux_msg = consms_mux_msg->msg_next;
		}
		msg->msg_next = NULL;
	}
	mutex_exit(&consms_msg_lock);

	return (msg);
}

static consms_msg_t *
consms_mux_find_msg(uint_t msg_id)
{
	consms_msg_t	*msg;

	mutex_enter(&consms_msg_lock);
	for (msg = consms_mux_msg; msg != NULL; msg = msg->msg_next) {
		if (msg->msg_id == msg_id)
			break;
	}
	mutex_exit(&consms_msg_lock);

	return (msg);
}

/*
 * Received ACK or NAK from lower mice
 *
 * For non-transparent ioctl, the msg->msg_rsp_list
 * is always NULL; for transparent ioctl, it
 * remembers the M_COPYIN/M_COPYOUT request
 * messages from lower mice. So here if msg->msg_rsp_list
 * is NULL (after receiving all ACK/NAKs), we
 * are done with this specific ioctl.
 *
 * As long as one of lower mice responds success,
 * we treat it success for a ioctl.
 */
static void
consms_mux_ack(consms_msg_t *msg, mblk_t *mp)
{
	mblk_t	*ack_mp;

	/* increment response_nums */
	msg->msg_num_responses++;

	if (mp->b_datap->db_type == M_IOCACK) {
		/*
		 * Received ACK from lower, then
		 * this is the last step for both
		 * non-transparent and transparent
		 * ioctl. We only need to remember
		 * one of the ACKs, finally reply
		 * this ACK to upper layer for this
		 * specific ioctl.
		 */
		ASSERT(msg->msg_rsp_list == NULL);
		if (msg->msg_ack_mp == NULL) {
			msg->msg_ack_mp = mp;
			mp = NULL;
		}
	}

	/*
	 * Check to see if all lower mice have responded
	 * to our dispatching ioctl.
	 */
	if (msg->msg_num_responses == msg->msg_num_requests) {
		if ((msg->msg_ack_mp == NULL) &&
		    (msg->msg_rsp_list == NULL)) {
			/*
			 * All are NAKed.
			 */
			ack_mp = mp;
			mp = NULL;
		} else if (msg->msg_rsp_list == NULL) {
			/*
			 * The last step and at least one ACKed.
			 */
			ack_mp = msg->msg_ack_mp;
			consms_mux_cache_states(msg->msg_request);
			consms_mux_max_wheel_report(ack_mp);
		} else {
			/*
			 * This is a NAK, but we have
			 * already received M_COPYIN
			 * or M_COPYOUT request from
			 * at least one of lower mice.
			 * (msg->msg_rsp_list != NULL)
			 *
			 * Still copyin or copyout.
			 */
			ack_mp = msg->msg_rsp_list->rsp_mp;
			consms_mux_max_wheel_report(ack_mp);
		}

		qreply(msg->msg_queue, ack_mp);

		if (msg->msg_rsp_list == NULL) {
			/*
			 * We are done with this ioctl.
			 */
			if (msg->msg_request)
				freemsg(msg->msg_request);
			(void) consms_mux_unlink_msg(msg->msg_id);
			kmem_free(msg, sizeof (*msg));
		}
	}

	if (mp) {
		freemsg(mp);
	}
}

/*
 * Received M_COPYIN or M_COPYOUT request from
 * lower mice for transparent ioctl
 *
 * We remember each M_COPYIN/M_COPYOUT into the
 * msg->msg_rsp_list, reply upper layer using the first
 * M_COPYIN/M_COPYOUT in the list after receiving
 * all responses from lower mice, even if some of
 * them return NAKs.
 */
static void
consms_mux_copyreq(queue_t *q, consms_msg_t *msg, mblk_t *mp)
{
	consms_response_t	*rsp;

	rsp = (consms_response_t *)kmem_zalloc(sizeof (*rsp), KM_SLEEP);
	rsp->rsp_mp = mp;
	rsp->rsp_queue = q;
	if (msg->msg_rsp_list) {
		rsp->rsp_next = msg->msg_rsp_list;
	}
	msg->msg_rsp_list = rsp;
	msg->msg_num_responses++;

	if (msg->msg_num_responses == msg->msg_num_requests) {
		consms_mux_max_wheel_report(msg->msg_rsp_list->rsp_mp);
		qreply(msg->msg_queue, msg->msg_rsp_list->rsp_mp);
	}
}

/*
 * Do the real job for updating M_COPYIN/M_COPYOUT
 * request with the mp of M_IOCDATA, then put it
 * down to lower mice.
 */
static void
consms_mux_disp_iocdata(consms_response_t *rsp, mblk_t *mp)
{
	mblk_t	*down_mp = rsp->rsp_mp;
	struct copyresp *copyresp = (struct copyresp *)mp->b_rptr;
	struct copyresp *newresp = (struct copyresp *)down_mp->b_rptr;

	/*
	 * Update the rval.
	 */
	newresp->cp_rval = copyresp->cp_rval;

	/*
	 * Update the db_type to M_IOCDATA.
	 */
	down_mp->b_datap->db_type = mp->b_datap->db_type;

	/*
	 * Update the b_cont.
	 */
	if (down_mp->b_cont != NULL) {
		freemsg(down_mp->b_cont);
		down_mp->b_cont = NULL;
	}
	if (mp->b_cont != NULL) {
		down_mp->b_cont = copymsg(mp->b_cont);
	}

	/*
	 * Put it down.
	 */
	(void) putq(WR(rsp->rsp_queue), down_mp);
}

/*
 * Dispatch M_IOCDATA down to all lower mice
 * for transparent ioctl.
 *
 * We update each M_COPYIN/M_COPYOUT in the
 * msg->msg_rsp_list with the M_IOCDATA.
 */
static void
consms_mux_iocdata(consms_msg_t *msg, mblk_t *mp)
{
	consms_response_t	*rsp;
	consms_response_t	*tmp;
	consms_response_t	*first;
	struct copyresp		*copyresp;
	int			request_nums;

	ASSERT(msg->msg_rsp_list != NULL);

	/*
	 * We should remember the ioc data for
	 * VUIDSWHEELSTATE, and MSIOSRESOLUTION,
	 * for we will cache the wheel state and
	 * the screen resolution later if ACKed.
	 */
	copyresp = (struct copyresp *)mp->b_rptr;
	if ((copyresp->cp_cmd == VUIDSWHEELSTATE) ||
	    (copyresp->cp_cmd == MSIOSRESOLUTION)) {
		freemsg(msg->msg_request);
		msg->msg_request = copymsg(mp);
	}

	/*
	 * Update request numbers and response numbers.
	 */
	msg->msg_num_requests = msg->msg_num_responses;
	msg->msg_num_responses = 0;
	request_nums = 1;

	/*
	 * Since we have use the first M_COPYIN/M_COPYOUT
	 * in the msg_rsp_list to reply upper layer, the mp
	 * of M_IOCDATA can be directly used for that.
	 */
	first = msg->msg_rsp_list;
	rsp = first->rsp_next;
	msg->msg_rsp_list = NULL;

	for (rsp = first->rsp_next; rsp != NULL; ) {
		tmp = rsp;
		rsp = rsp->rsp_next;
		consms_mux_disp_iocdata(tmp, mp);
		kmem_free(tmp, sizeof (*tmp));
		request_nums++;
	}

	/* Must set the request number before the last q. */
	msg->msg_num_requests = request_nums;

	/* the first one */
	(void) putq(WR(first->rsp_queue), mp);
	kmem_free(first, sizeof (*first));
}


/*
 * Here we update the number of wheels with
 * the virtual mouse for VUIDGWHEELCOUNT ioctl.
 */
static void
consms_mux_max_wheel_report(mblk_t *mp)
{
	struct iocblk		*iocp;
	int			num_wheels;

	if (mp == NULL || mp->b_cont == NULL)
		return;

	iocp = (struct iocblk *)mp->b_rptr;

	if ((iocp->ioc_cmd == VUIDGWHEELCOUNT) &&
	    (mp->b_datap->db_type == M_COPYOUT)) {
		num_wheels = *(int *)mp->b_cont->b_rptr;
		if (num_wheels < consms_state.consms_num_wheels) {
			*(int *)mp->b_cont->b_rptr =
			    consms_state.consms_num_wheels;
		}
	}
}

/*
 * Update the virtual mouse state variables with
 * the latest value from upper layer when these
 * set ioctls return success. Thus we can update
 * low mice with the latest state values during
 * hotplug.
 */
static void
consms_mux_cache_states(mblk_t *mp)
{
	struct iocblk 		*iocp;
	Ms_parms		*parms;
	Ms_screen_resolution	*sr;
	wheel_state		*ws;

	if (mp == NULL || mp->b_cont == NULL)
		return;

	iocp = (struct iocblk *)mp->b_rptr;
	switch (iocp->ioc_cmd) {
	case VUIDSFORMAT:
		consms_state.consms_vuid_format = *(int *)mp->b_cont->b_rptr;
		break;

	case MSIOSETPARMS:
		parms = (Ms_parms *)mp->b_cont->b_rptr;
		consms_state.consms_ms_parms = *parms;
		break;

	case MSIOSRESOLUTION:
		sr = (Ms_screen_resolution *)mp->b_cont->b_rptr;
		consms_state.consms_ms_sr = *sr;
		break;

	case VUIDSWHEELSTATE:
		ws = (wheel_state *)mp->b_cont->b_rptr;
		consms_state.consms_wheel_state_bf =
		    (ws->stateflags << ws->id) |
		    (consms_state.consms_wheel_state_bf & ~(1 << ws->id));
		break;
	}
}

/*
 * Dispatch ioctl mp (non-transparent and transparent)
 * down to all lower mice.
 *
 * First, create a pending message for this mp, link it into
 * the global messages list. Then wait for ACK/NAK for
 * non-transparent ioctl, COPYIN/COPYOUT for transparent
 * ioctl.
 */
static int
consms_mux_disp_ioctl(queue_t *q, mblk_t *mp)
{
	struct iocblk	*iocp;
	consms_msg_t	*msg;
	consms_lq_t	*lq;
	mblk_t		*copy_mp;
	int		error = 0;

	iocp = (struct iocblk *)mp->b_rptr;
	msg = (consms_msg_t *)kmem_zalloc(sizeof (*msg), KM_SLEEP);
	msg->msg_id = iocp->ioc_id;
	msg->msg_request = mp;
	msg->msg_queue = q;
	msg->msg_num_requests = consms_state.consms_num_lqs;
	consms_mux_link_msg(msg);

	for (lq = consms_state.consms_lqs; lq != NULL; lq = lq->lq_next) {
		if ((copy_mp = copymsg(mp)) != NULL) {
			(void) putq(lq->lq_queue, copy_mp);
		} else {
			/*
			 * If copymsg fails, we ignore this lq and
			 * try next one. As long as one of them succeeds,
			 * we dispatch this ioctl down. And later as long
			 * as one of the lower drivers return success, we
			 * reply to this ioctl with success.
			 */
			msg->msg_num_requests--;
		}
	}

	if (msg->msg_num_requests <= 0) {
		/*
		 * Since copymsg fails for all lqs, we NAK this ioctl.
		 */
		(void) consms_mux_unlink_msg(msg->msg_id);
		kmem_free(msg, sizeof (*msg));
		error = ENOMEM;
	}

	return (error);
}

/*
 * Dispatch M_DATA and M_FLUSH message down to all
 * lower mice, and there are no acknowledgements
 * for them. Here we just copy the mp and then
 * put it into the lower queues.
 */
static void
consms_mux_disp_data(mblk_t *mp)
{
	consms_lq_t	*lq;
	mblk_t		*copy_mp;

	for (lq = consms_state.consms_lqs; lq != NULL; lq = lq->lq_next) {
		if ((copy_mp = copymsg(mp)) != NULL) {
			(void) putq(lq->lq_queue, copy_mp);
		}
	}

	freemsg(mp);
}