vuidmice.c revision 22eb7cb54d8a6bcf6fe2674cb4b1f0cf2d85cfb6
/*
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* VUIDMICE module: put mouse events into vuid format
*/
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/sad.h>
#include <sys/vuid_event.h>
#include <sys/vuidmice.h>
#include <sys/vuid_wheel.h>
#include <sys/msio.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
static int vuidmice_open(queue_t *const, const dev_t *const,
const int, const int, const cred_t *const);
static int vuidmice_close(queue_t *const, const int, const cred_t *const);
static int vuidmice_rput(queue_t *const, mblk_t *);
static int vuidmice_rsrv(queue_t *const);
static int vuidmice_wput(queue_t *const, mblk_t *);
static void vuidmice_miocdata(queue_t *const, mblk_t *);
static int vuidmice_handle_wheel_resolution_ioctl(queue_t *const,
mblk_t *, int);
static int vuidmice_service_wheel_info(mblk_t *);
static int vuidmice_service_wheel_state(queue_t *, mblk_t *, uint_t);
void VUID_QUEUE(queue_t *const, mblk_t *);
int VUID_OPEN(queue_t *const);
void VUID_CLOSE(queue_t *const);
static kmutex_t vuidmice_lock;
static struct module_info vuidmice_iinfo = {
0,
VUID_NAME,
0,
INFPSZ,
1000,
100
};
static struct qinit vuidmice_rinit = {
vuidmice_rput,
vuidmice_rsrv,
vuidmice_open,
vuidmice_close,
NULL,
&vuidmice_iinfo,
NULL
};
static struct module_info vuidmice_oinfo = {
0,
VUID_NAME,
0,
INFPSZ,
1000,
100
};
static struct qinit vuidmice_winit = {
vuidmice_wput,
NULL,
NULL,
NULL,
NULL,
&vuidmice_oinfo,
NULL
};
struct streamtab vuidmice_info = {
&vuidmice_rinit,
&vuidmice_winit,
NULL,
NULL
};
/*
* This is the loadable module wrapper.
*/
/*
* D_MTQPAIR effectively makes the module single threaded.
* There can be only one thread active in the module at any time.
* It may be a read or write thread.
*/
#define VUIDMICE_CONF_FLAG (D_MP | D_MTQPAIR)
static struct fmodsw fsw = {
VUID_NAME,
&vuidmice_info,
VUIDMICE_CONF_FLAG
};
static struct modlstrmod modlstrmod = {
&mod_strmodops,
"mouse events to vuid events",
&fsw
};
/*
* Module linkage information for the kernel.
*/
static struct modlinkage modlinkage = {
MODREV_1,
&modlstrmod,
NULL
};
static int module_open = 0; /* allow only one open of this module */
int
_init(void)
{
register int rc;
mutex_init(&vuidmice_lock, NULL, MUTEX_DEFAULT, NULL);
if ((rc = mod_install(&modlinkage)) != 0) {
mutex_destroy(&vuidmice_lock);
}
return (rc);
}
int
_fini(void)
{
register int rc;
if ((rc = mod_remove(&modlinkage)) == 0)
mutex_destroy(&vuidmice_lock);
return (rc);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/* ARGSUSED1 */
static int
vuidmice_open(queue_t *const qp, const dev_t *const devp,
const int oflag, const int sflag, const cred_t *const crp)
{
if (qp->q_ptr != NULL)
return (0); /* reopen */
mutex_enter(&vuidmice_lock);
/* Allow only 1 open of this module */
if (module_open) {
mutex_exit(&vuidmice_lock);
return (EBUSY);
}
module_open++;
mutex_exit(&vuidmice_lock);
/*
* Both the read and write queues share the same state structures.
*/
qp->q_ptr = kmem_zalloc(sizeof (struct MouseStateInfo), KM_SLEEP);
WR(qp)->q_ptr = qp->q_ptr;
/* initialize state */
STATEP->format = VUID_NATIVE;
qprocson(qp);
#ifdef VUID_OPEN
if (VUID_OPEN(qp) != 0) {
qprocsoff(qp);
mutex_enter(&vuidmice_lock);
module_open--;
mutex_exit(&vuidmice_lock);
kmem_free(qp->q_ptr, sizeof (struct MouseStateInfo));
qp->q_ptr = NULL;
return (ENXIO);
}
#endif
return (0);
}
/* ARGSUSED1 */
static int
vuidmice_close(queue_t *const qp, const int flag, const cred_t *const crp)
{
ASSERT(qp != NULL);
qprocsoff(qp);
flushq(qp, FLUSHALL);
flushq(OTHERQ(qp), FLUSHALL);
#ifdef VUID_CLOSE
VUID_CLOSE(qp);
#endif
mutex_enter(&vuidmice_lock);
module_open--;
mutex_exit(&vuidmice_lock);
kmem_free(qp->q_ptr, sizeof (struct MouseStateInfo));
qp->q_ptr = NULL;
return (0);
}
/*
* Put procedure for input from driver end of stream (read queue).
*/
static int
vuidmice_rput(queue_t *const qp, mblk_t *mp)
{
ASSERT(qp != NULL);
ASSERT(mp != NULL);
/*
* Handle all the related high priority messages here, hence
* should spend the least amount of time here.
*/
if (DB_TYPE(mp) == M_DATA) {
if ((int)STATEP->format == VUID_FIRM_EVENT)
return (putq(qp, mp)); /* queue message & return */
} else if (DB_TYPE(mp) == M_FLUSH) {
if (*mp->b_rptr & FLUSHR)
flushq(qp, FLUSHALL);
}
putnext(qp, mp); /* pass it on */
return (0);
}
static int
vuidmice_rsrv(queue_t *const qp)
{
register mblk_t *mp;
ASSERT(qp != NULL);
while ((mp = getq(qp)) != NULL) {
ASSERT(DB_TYPE(mp) == M_DATA);
if (!canputnext(qp))
return (putbq(qp, mp)); /* read side is blocked */
switch (DB_TYPE(mp)) {
case M_DATA:
if ((int)STATEP->format == VUID_FIRM_EVENT)
(void) VUID_QUEUE(qp, mp);
else
(void) putnext(qp, mp);
break;
default:
cmn_err(CE_WARN,
"vuidmice_rsrv: bad message type (0x%x)\n",
DB_TYPE(mp));
(void) putnext(qp, mp);
break;
}
}
return (0);
}
/*
* Put procedure for write from user end of stream (write queue).
*/
static int
vuidmice_wput(queue_t *const qp, mblk_t *mp)
{
int error = 0;
ASSERT(qp != NULL);
ASSERT(mp != NULL);
/*
* Handle all the related high priority messages here, hence
* should spend the least amount of time here.
*/
switch (DB_TYPE(mp)) { /* handle hi pri messages here */
case M_FLUSH:
if (*mp->b_rptr & FLUSHW)
flushq(qp, FLUSHALL);
putnext(qp, mp); /* pass it on */
return (0);
case M_IOCTL: {
struct iocblk *iocbp = (void *)mp->b_rptr;
switch (iocbp->ioc_cmd) {
case VUIDSFORMAT:
/*
* VUIDSFORMAT is known to the stream head and thus
* is guaranteed to be an I_STR ioctl.
*/
if (iocbp->ioc_count == TRANSPARENT) {
miocnak(qp, mp, 0, EINVAL);
return (0);
} else {
int format_type;
error = miocpullup(mp, sizeof (int));
if (error != 0) {
miocnak(qp, mp, 0, error);
return (0);
}
format_type =
*(int *)(void *)mp->b_cont->b_rptr;
STATEP->format = (uchar_t)format_type;
iocbp->ioc_rval = 0;
iocbp->ioc_count = 0;
iocbp->ioc_error = 0;
mp->b_datap->db_type = M_IOCACK;
}
/* return buffer to pool ASAP */
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
qreply(qp, mp);
return (0);
case VUIDGFORMAT:
/* return buffer to pool ASAP */
if (mp->b_cont) {
freemsg(mp->b_cont); /* over written below */
mp->b_cont = NULL;
}
/*
* VUIDGFORMAT is known to the stream head and thus
* is guaranteed to be an I_STR ioctl.
*/
if (iocbp->ioc_count == TRANSPARENT) {
miocnak(qp, mp, 0, EINVAL);
return (0);
}
mp->b_cont = allocb(sizeof (int), BPRI_MED);
if (mp->b_cont == NULL) {
miocnak(qp, mp, 0, EAGAIN);
return (0);
}
*(int *)(void *)mp->b_cont->b_rptr =
(int)STATEP->format;
mp->b_cont->b_wptr += sizeof (int);
iocbp->ioc_count = sizeof (int);
mp->b_datap->db_type = M_IOCACK;
qreply(qp, mp);
return (0);
case VUID_NATIVE:
case VUIDSADDR:
case VUIDGADDR:
miocnak(qp, mp, 0, ENOTTY);
return (0);
case MSIOBUTTONS:
/* return buffer to pool ASAP */
if (mp->b_cont) {
freemsg(mp->b_cont); /* over written below */
mp->b_cont = NULL;
}
/*
* MSIOBUTTONS is known to streamio.c and this
* is assume to be non-I_STR & non-TRANSPARENT ioctl
*/
if (iocbp->ioc_count == TRANSPARENT) {
miocnak(qp, mp, 0, EINVAL);
return (0);
}
if (STATEP->nbuttons == 0) {
miocnak(qp, mp, 0, EINVAL);
return (0);
}
mp->b_cont = allocb(sizeof (int), BPRI_MED);
if (mp->b_cont == NULL) {
miocnak(qp, mp, 0, EAGAIN);
return (0);
}
*(int *)(void *)mp->b_cont->b_rptr =
(int)STATEP->nbuttons;
mp->b_cont->b_wptr += sizeof (int);
iocbp->ioc_count = sizeof (int);
mp->b_datap->db_type = M_IOCACK;
qreply(qp, mp);
return (0);
/*
* New IOCTL support. Since it's explicitly mentioned
* that you can't add more ioctls to stream head's
* hard coded list, we have to do the transparent
* ioctl processing which is not very exciting.
*/
case VUIDGWHEELCOUNT:
case VUIDGWHEELINFO:
case VUIDGWHEELSTATE:
case VUIDSWHEELSTATE:
case MSIOSRESOLUTION:
error = vuidmice_handle_wheel_resolution_ioctl(qp,
mp, iocbp->ioc_cmd);
if (!error) {
return (0);
} else {
miocnak(qp, mp, 0, error);
return (0);
}
default:
putnext(qp, mp); /* nothing to process here */
return (0);
}
} /* End of case M_IOCTL */
case M_IOCDATA:
vuidmice_miocdata(qp, mp);
return (0);
default:
putnext(qp, mp); /* pass it on */
return (0);
}
/*NOTREACHED*/
}
void
VUID_PUTNEXT(queue_t *const qp, uchar_t event_id, uchar_t event_pair_type,
uchar_t event_pair, int event_value)
{
int strikes = 1;
mblk_t *bp;
Firm_event *fep;
/*
* Give this event 3 chances to allocate blocks,
* otherwise discard this mouse event. 3 Strikes and you're out.
*/
while ((bp = allocb((int)sizeof (Firm_event), BPRI_HI)) == NULL) {
if (++strikes > 3)
return;
drv_usecwait(10);
}
fep = (void *)bp->b_wptr;
fep->id = vuid_id_addr(VKEY_FIRST) | vuid_id_offset(event_id);
fep->pair_type = event_pair_type;
fep->pair = event_pair;
fep->value = event_value;
uniqtime32(&fep->time);
bp->b_wptr += sizeof (Firm_event);
if (canput(qp->q_next))
putnext(qp, bp);
else
(void) putbq(qp, bp); /* read side is blocked */
}
/*
* vuidmice_miocdata
* M_IOCDATA processing for IOCTL's: VUIDGWHEELCOUNT, VUIDGWHEELINFO,
* VUIDGWHEELSTATE, VUIDSWHEELSTATE & MSIOSRESOLUTION.
*/
static void
vuidmice_miocdata(queue_t *qp, mblk_t *mp)
{
struct copyresp *copyresp;
struct iocblk *iocbp;
mblk_t *ioctmp;
mblk_t *datap;
Mouse_iocstate_t *Mouseioc;
size_t size;
int err = 0;
copyresp = (void *)mp->b_rptr;
iocbp = (void *)mp->b_rptr;
if (copyresp->cp_rval) {
err = EAGAIN;
goto err;
}
switch (copyresp->cp_cmd) {
case VUIDGWHEELCOUNT:
mp->b_datap->db_type = M_IOCACK;
mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);
iocbp->ioc_error = 0;
iocbp->ioc_count = 0;
iocbp->ioc_rval = 0;
if (mp->b_cont != NULL) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
break;
case VUIDGWHEELINFO:
case VUIDGWHEELSTATE:
ioctmp = copyresp->cp_private;
Mouseioc = (void *)ioctmp->b_rptr;
if (Mouseioc->ioc_state == GETSTRUCT) {
if (mp->b_cont == NULL) {
err = EINVAL;
break;
}
datap = mp->b_cont;
if (copyresp->cp_cmd == VUIDGWHEELSTATE) {
err = vuidmice_service_wheel_state(qp, datap,
VUIDGWHEELSTATE);
} else {
err = vuidmice_service_wheel_info(datap);
}
if (err) {
break;
}
if (copyresp->cp_cmd == VUIDGWHEELSTATE) {
size = sizeof (wheel_state);
} else {
size = sizeof (wheel_info);
}
Mouseioc->ioc_state = GETRESULT;
ASSERT(Mouseioc->u_addr != NULL);
mcopyout(mp, ioctmp, size, Mouseioc->u_addr, NULL);
} else if (Mouseioc->ioc_state == GETRESULT) {
freemsg(ioctmp);
mp->b_datap->db_type = M_IOCACK;
mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);
iocbp->ioc_error = 0;
iocbp->ioc_count = 0;
iocbp->ioc_rval = 0;
if (mp->b_cont != NULL) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
}
break;
case VUIDSWHEELSTATE:
case MSIOSRESOLUTION:
ioctmp = copyresp->cp_private;
Mouseioc = (void *)ioctmp->b_rptr;
if (mp->b_cont == NULL) {
err = EINVAL;
break;
}
datap = mp->b_cont;
if (copyresp->cp_cmd == VUIDSWHEELSTATE) {
err = vuidmice_service_wheel_state(qp,
datap, VUIDSWHEELSTATE);
}
if (err) {
break;
}
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
freemsg(ioctmp);
iocbp->ioc_count = 0;
iocbp->ioc_error = 0;
iocbp->ioc_rval = 0;
mp->b_datap->db_type = M_IOCACK;
break;
default:
err = EINVAL;
break;
}
err:
if (err) {
mp->b_datap->db_type = M_IOCNAK;
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
if (copyresp->cp_private) {
freemsg(copyresp->cp_private);
copyresp->cp_private = NULL;
}
iocbp->ioc_count = 0;
iocbp->ioc_error = err;
}
qreply(qp, mp);
}
/*
* vuidmice_handle_wheel_resolution_ioctl
* Handle wheel mouse and MSIOSRESOLUTION ioctls.
*
* Here we also support non-transparent way of these ioctls
* just like usb mouse driver does, so the consms module is
* very simple to deal with these ioctls.
*/
static int
vuidmice_handle_wheel_resolution_ioctl(queue_t *qp, mblk_t *mp, int cmd)
{
int err = 0;
Mouse_iocstate_t *Mouseioc;
caddr_t useraddr;
size_t size;
mblk_t *ioctmp;
mblk_t *datap;
struct iocblk *iocbp = (void *)mp->b_rptr;
if (iocbp->ioc_count == TRANSPARENT) {
if (mp->b_cont == NULL)
return (EINVAL);
useraddr = *((caddr_t *)(void *)mp->b_cont->b_rptr);
switch (cmd) {
case VUIDGWHEELCOUNT:
size = sizeof (int);
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL)
return (EAGAIN);
*((int *)(void *)datap->b_wptr) =
STATEP->vuid_mouse_mode;
mcopyout(mp, NULL, size, NULL, datap);
qreply(qp, mp);
return (err);
case VUIDGWHEELINFO:
size = sizeof (wheel_info);
break;
case VUIDSWHEELSTATE:
case VUIDGWHEELSTATE:
size = sizeof (wheel_state);
break;
case MSIOSRESOLUTION:
size = sizeof (Ms_screen_resolution);
break;
}
if ((ioctmp = allocb(sizeof (Mouse_iocstate_t),
BPRI_MED)) == NULL)
return (EAGAIN);
Mouseioc = (void *)ioctmp->b_rptr;
Mouseioc->ioc_state = GETSTRUCT;
Mouseioc->u_addr = useraddr;
ioctmp->b_wptr = ioctmp->b_rptr + sizeof (Mouse_iocstate_t);
mcopyin(mp, ioctmp, size, NULL);
qreply(qp, mp);
return (err);
} else {
switch (cmd) {
case VUIDGWHEELCOUNT:
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
err = EAGAIN;
break;
}
*((int *)(void *)datap->b_wptr) =
STATEP->vuid_mouse_mode;
datap->b_wptr += sizeof (int);
mp->b_cont = datap;
break;
case VUIDGWHEELINFO:
if (mp->b_cont == NULL ||
iocbp->ioc_count != sizeof (wheel_info)) {
err = EINVAL;
break;
}
datap = mp->b_cont;
err = vuidmice_service_wheel_info(datap);
break;
case VUIDSWHEELSTATE:
case VUIDGWHEELSTATE:
if (mp->b_cont == NULL ||
iocbp->ioc_count != sizeof (wheel_state)) {
err = EINVAL;
break;
}
datap = mp->b_cont;
err = vuidmice_service_wheel_state(qp, datap, cmd);
break;
case MSIOSRESOLUTION:
/*
* Now we just make Xserver and
* the virtual mouse happy. Of course,
* the screen resolution value may
* be used later for absolute PS/2 mouse.
*/
err = 0;
break;
}
if (!err) {
mp->b_datap->db_type = M_IOCACK;
iocbp->ioc_rval = 0;
iocbp->ioc_error = 0;
qreply(qp, mp);
}
return (err);
}
}
static int
vuidmice_service_wheel_info(register mblk_t *datap)
{
wheel_info *wi;
int err = 0;
wi = (void *)datap->b_rptr;
if (wi->vers != VUID_WHEEL_INFO_VERS) {
err = EINVAL;
return (err);
}
if (wi->id > (VUIDMICE_NUM_WHEELS - 1)) {
err = EINVAL;
return (err);
}
wi->format = (wi->id == VUIDMICE_VERTICAL_WHEEL_ID) ?
VUID_WHEEL_FORMAT_VERTICAL : VUID_WHEEL_FORMAT_HORIZONTAL;
return (err);
}
static int
vuidmice_service_wheel_state(register queue_t *qp,
register mblk_t *datap,
register uint_t cmd)
{
wheel_state *ws;
uint_t err = 0;
ws = (void *)datap->b_rptr;
if (ws->vers != VUID_WHEEL_STATE_VERS) {
err = EINVAL;
return (err);
}
if (ws->id > (VUIDMICE_NUM_WHEELS - 1)) {
err = EINVAL;
return (err);
}
switch (cmd) {
case VUIDGWHEELSTATE:
ws->stateflags =
(STATEP->wheel_state_bf >> ws->id) & 1;
break;
case VUIDSWHEELSTATE:
STATEP->wheel_state_bf = (ws->stateflags << ws->id) |
(STATEP->wheel_state_bf & ~(1 << ws->id));
break;
default:
err = EINVAL;
return (err);
}
return (err);
}