consms.c revision d5007c14714a317d8cf986a22ec8ac39f16e168f
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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(int, 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 */
};
/*
* 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' %I%",
&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(int 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);
}