#include <sys/param.h>
#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/signal.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/termio.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strtty.h>
#include <sys/strsun.h>
#include <sys/debug.h>
#include <sys/ddi.h>
#include <sys/stat.h>
#include <sys/cmn_err.h>
#include <sys/sunddi.h>
#include <sys/promif.h>
#include <sys/cred.h>
#include <sys/i8042.h>
#include <sys/note.h>
#include <sys/mouse.h>
#define DRIVER_NAME(dip) ddi_driver_name(dip)
#define MOUSE8042_INTERNAL_OPEN(minor) (((minor) & 0x1) == 1)
#define MOUSE8042_MINOR_TO_INSTANCE(minor) ((minor) / 2)
#define MOUSE8042_INTERNAL_MINOR(minor) ((minor) + 1)
#define MOUSE8042_RESET_TIMEOUT_USECS 500000
extern int ddi_create_internal_pathname(dev_info_t *, char *, int, minor_t);
extern void consconfig_link(major_t major, minor_t minor);
extern int consconfig_unlink(major_t major, minor_t minor);
static dev_info_t *mouse8042_dip;
typedef enum {
MSE_RESET_IDLE,
MSE_RESET_PRE,
MSE_RESET_ACK,
MSE_RESET_AA,
MSE_RESET_FAILED
} mouse8042_reset_state_e;
struct mouse_state {
queue_t *ms_rqp;
queue_t *ms_wqp;
ddi_iblock_cookie_t ms_iblock_cookie;
ddi_acc_handle_t ms_handle;
uint8_t *ms_addr;
kmutex_t ms_mutex;
minor_t ms_minor;
boolean_t ms_opened;
kmutex_t reset_mutex;
kcondvar_t reset_cv;
mouse8042_reset_state_e reset_state;
timeout_id_t reset_tid;
int ready;
mblk_t *reply_mp;
mblk_t *reset_ack_mp;
bufcall_id_t bc_id;
};
static uint_t mouse8042_intr(caddr_t arg);
static int mouse8042_open(queue_t *q, dev_t *devp, int flag, int sflag,
cred_t *cred_p);
static int mouse8042_close(queue_t *q, int flag, cred_t *cred_p);
static int mouse8042_wsrv(queue_t *qp);
static int mouse8042_wput(queue_t *q, mblk_t *mp);
static int mouse8042_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result);
static int mouse8042_attach(dev_info_t *dev, ddi_attach_cmd_t cmd);
static int mouse8042_detach(dev_info_t *dev, ddi_detach_cmd_t cmd);
#define MODULE_NAME "mouse8042"
static struct module_info mouse8042_minfo = {
23,
MODULE_NAME,
0, INFPSZ,
256, 128
};
static struct qinit mouse8042_rinit = {
NULL,
NULL,
mouse8042_open,
mouse8042_close,
NULL,
&mouse8042_minfo,
NULL
};
static struct qinit mouse8042_winit = {
mouse8042_wput,
mouse8042_wsrv,
NULL,
NULL,
NULL,
&mouse8042_minfo,
NULL
};
static struct streamtab mouse8042_strinfo = {
&mouse8042_rinit,
&mouse8042_winit,
NULL,
NULL,
};
static struct cb_ops mouse8042_cb_ops = {
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
&mouse8042_strinfo,
D_MP | D_NEW
};
static struct dev_ops mouse8042_ops = {
DEVO_REV,
0,
mouse8042_getinfo,
nulldev,
nulldev,
mouse8042_attach,
mouse8042_detach,
nodev,
&mouse8042_cb_ops,
(struct bus_ops *)0,
NULL,
ddi_quiesce_not_needed,
};
#include <sys/modctl.h>
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"PS/2 Mouse",
&mouse8042_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv,
NULL
};
int
_init()
{
int rv;
rv = mod_install(&modlinkage);
return (rv);
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
mouse8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct mouse_state *state;
mblk_t *mp;
int instance = ddi_get_instance(dip);
static ddi_device_acc_attr_t attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC,
};
int rc;
if (cmd == DDI_RESUME) {
state = (struct mouse_state *)ddi_get_driver_private(dip);
state->ready = 1;
if (state->ms_rqp != NULL) {
if (mp = allocb(1, BPRI_MED)) {
*mp->b_wptr++ = 0xaa;
putnext(state->ms_rqp, mp);
}
if (mp = allocb(1, BPRI_MED)) {
*mp->b_wptr++ = 0x0;
putnext(state->ms_rqp, mp);
}
}
return (DDI_SUCCESS);
}
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
if (mouse8042_dip != NULL)
return (DDI_FAILURE);
state = kmem_zalloc(sizeof (struct mouse_state), KM_SLEEP);
state->ms_opened = B_FALSE;
state->reset_state = MSE_RESET_IDLE;
state->reset_tid = 0;
state->bc_id = 0;
ddi_set_driver_private(dip, state);
rc = ddi_create_minor_node(dip, "mouse", S_IFCHR, instance * 2,
DDI_NT_MOUSE, 0);
if (rc != DDI_SUCCESS) {
goto fail_1;
}
if (ddi_create_internal_pathname(dip, "internal_mouse", S_IFCHR,
instance * 2 + 1) != DDI_SUCCESS) {
goto fail_2;
}
rc = ddi_regs_map_setup(dip, 0, (caddr_t *)&state->ms_addr,
(offset_t)0, (offset_t)0, &attr, &state->ms_handle);
if (rc != DDI_SUCCESS) {
goto fail_2;
}
rc = ddi_get_iblock_cookie(dip, 0, &state->ms_iblock_cookie);
if (rc != DDI_SUCCESS) {
goto fail_3;
}
mutex_init(&state->ms_mutex, NULL, MUTEX_DRIVER,
state->ms_iblock_cookie);
mutex_init(&state->reset_mutex, NULL, MUTEX_DRIVER,
state->ms_iblock_cookie);
cv_init(&state->reset_cv, NULL, CV_DRIVER, NULL);
rc = ddi_add_intr(dip, 0,
(ddi_iblock_cookie_t *)NULL, (ddi_idevice_cookie_t *)NULL,
mouse8042_intr, (caddr_t)state);
if (rc != DDI_SUCCESS) {
goto fail_3;
}
mouse8042_dip = dip;
state->ready = 1;
ddi_report_dev(dip);
return (DDI_SUCCESS);
fail_3:
ddi_regs_map_free(&state->ms_handle);
fail_2:
ddi_remove_minor_node(dip, NULL);
fail_1:
kmem_free(state, sizeof (struct mouse_state));
return (rc);
}
static int
mouse8042_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct mouse_state *state;
state = ddi_get_driver_private(dip);
switch (cmd) {
case DDI_SUSPEND:
state->ready = 0;
return (DDI_SUCCESS);
case DDI_DETACH:
ddi_remove_intr(dip, 0, state->ms_iblock_cookie);
mouse8042_dip = NULL;
cv_destroy(&state->reset_cv);
mutex_destroy(&state->reset_mutex);
mutex_destroy(&state->ms_mutex);
ddi_prop_remove_all(dip);
ddi_regs_map_free(&state->ms_handle);
ddi_remove_minor_node(dip, NULL);
kmem_free(state, sizeof (struct mouse_state));
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
mouse8042_getinfo(
dev_info_t *dip,
ddi_info_cmd_t infocmd,
void *arg,
void **result)
{
dev_t dev = (dev_t)arg;
minor_t minor = getminor(dev);
int instance = MOUSE8042_MINOR_TO_INSTANCE(minor);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if (mouse8042_dip == NULL)
return (DDI_FAILURE);
*result = (void *)mouse8042_dip;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
mouse8042_open(
queue_t *q,
dev_t *devp,
int flag,
int sflag,
cred_t *cred_p)
{
struct mouse_state *state;
minor_t minor = getminor(*devp);
int rval;
if (mouse8042_dip == NULL)
return (ENXIO);
state = ddi_get_driver_private(mouse8042_dip);
mutex_enter(&state->ms_mutex);
if (state->ms_opened) {
if (state->ms_minor == minor) {
mutex_exit(&state->ms_mutex);
return (0);
}
ASSERT(!MOUSE8042_INTERNAL_OPEN(minor));
if (MOUSE8042_INTERNAL_OPEN(minor)) {
mutex_exit(&state->ms_mutex);
return (EINVAL);
}
mutex_exit(&state->ms_mutex);
if ((rval = consconfig_unlink(ddi_driver_major(mouse8042_dip),
MOUSE8042_INTERNAL_MINOR(minor))) != 0) {
return (rval);
}
mutex_enter(&state->ms_mutex);
}
q->q_ptr = (caddr_t)state;
WR(q)->q_ptr = (caddr_t)state;
state->ms_rqp = q;
state->ms_wqp = WR(q);
qprocson(q);
state->ms_minor = minor;
state->ms_opened = B_TRUE;
mutex_exit(&state->ms_mutex);
return (0);
}
static int
mouse8042_close(queue_t *q, int flag, cred_t *cred_p)
{
struct mouse_state *state;
minor_t minor;
state = (struct mouse_state *)q->q_ptr;
qprocsoff(q);
mutex_enter(&state->reset_mutex);
while (state->reset_state != MSE_RESET_IDLE) {
cv_wait(&state->reset_cv, &state->reset_mutex);
}
if (state->reset_tid != 0) {
(void) quntimeout(q, state->reset_tid);
state->reset_tid = 0;
}
if (state->reply_mp != NULL) {
freemsg(state->reply_mp);
state->reply_mp = NULL;
}
if (state->reset_ack_mp != NULL) {
freemsg(state->reset_ack_mp);
state->reset_ack_mp = NULL;
}
mutex_exit(&state->reset_mutex);
mutex_enter(&state->ms_mutex);
if (state->bc_id != 0) {
(void) qunbufcall(q, state->bc_id);
state->bc_id = 0;
}
q->q_ptr = NULL;
WR(q)->q_ptr = NULL;
state->ms_rqp = NULL;
state->ms_wqp = NULL;
state->ms_opened = B_FALSE;
minor = state->ms_minor;
mutex_exit(&state->ms_mutex);
if (!MOUSE8042_INTERNAL_OPEN(minor)) {
consconfig_link(ddi_driver_major(mouse8042_dip),
MOUSE8042_INTERNAL_MINOR(minor));
}
return (0);
}
static void
mouse8042_iocnack(
queue_t *qp,
mblk_t *mp,
struct iocblk *iocp,
int error,
int rval)
{
mp->b_datap->db_type = M_IOCNAK;
iocp->ioc_rval = rval;
iocp->ioc_error = error;
qreply(qp, mp);
}
static void
mouse8042_reset_timeout(void *argp)
{
struct mouse_state *state = (struct mouse_state *)argp;
mblk_t *mp;
mutex_enter(&state->reset_mutex);
if (state->reset_state != MSE_RESET_IDLE &&
state->reset_state != MSE_RESET_FAILED) {
state->reset_tid = 0;
state->reset_state = MSE_RESET_IDLE;
cv_signal(&state->reset_cv);
(void) ddi_get8(state->ms_handle, state->ms_addr +
I8042_UNLOCK);
mp = state->reply_mp;
*mp->b_wptr++ = MSERESEND;
state->reply_mp = NULL;
if (state->ms_rqp != NULL)
putnext(state->ms_rqp, mp);
else
freemsg(mp);
ASSERT(state->ms_wqp != NULL);
enableok(state->ms_wqp);
qenable(state->ms_wqp);
}
mutex_exit(&state->reset_mutex);
}
static int
mouse8042_initiate_reset(queue_t *q, mblk_t *mp, struct mouse_state *state)
{
mutex_enter(&state->reset_mutex);
if (state->reset_state != MSE_RESET_IDLE) {
noenable(q);
mutex_exit(&state->reset_mutex);
return (1);
}
mutex_exit(&state->reset_mutex);
if (state->reply_mp == NULL)
state->reply_mp = allocb(2, BPRI_MED);
if (state->reset_ack_mp == NULL)
state->reset_ack_mp = allocb(1, BPRI_MED);
if (state->reply_mp == NULL || state->reset_ack_mp == NULL) {
state->bc_id = qbufcall(q, (state->reply_mp == NULL) ? 2 : 1,
BPRI_MED, (void (*)(void *))qenable, q);
if (state->bc_id == 0) {
*mp->b_rptr = MSEERROR;
mp->b_wptr = mp->b_rptr + 1;
qreply(q, mp);
return (0);
}
return (1);
} else {
state->bc_id = 0;
}
(void) ddi_get8(state->ms_handle,
state->ms_addr + I8042_LOCK);
mutex_enter(&state->reset_mutex);
state->reset_state = MSE_RESET_PRE;
noenable(q);
state->reset_tid = qtimeout(q,
mouse8042_reset_timeout,
state,
drv_usectohz(
MOUSE8042_RESET_TIMEOUT_USECS));
ddi_put8(state->ms_handle,
state->ms_addr +
I8042_INT_OUTPUT_DATA, MSERESET);
mp->b_rptr++;
mutex_exit(&state->reset_mutex);
return (1);
}
static int
mouse8042_process_data_msg(queue_t *q, mblk_t *mp, struct mouse_state *state)
{
mblk_t *bp;
mblk_t *next;
bp = mp;
do {
while (bp->b_rptr < bp->b_wptr) {
if (*bp->b_rptr == MSERESET) {
if (mouse8042_initiate_reset(q, bp, state) == 0)
return (1);
if (MBLKL(bp) == 0) {
next = bp->b_cont;
freeb(bp);
bp = next;
}
if (bp != NULL) {
if (!putbq(q, bp))
freemsg(bp);
}
return (1);
}
ddi_put8(state->ms_handle,
state->ms_addr + I8042_INT_OUTPUT_DATA,
*bp->b_rptr++);
}
next = bp->b_cont;
freeb(bp);
} while ((bp = next) != NULL);
return (0);
}
static int
mouse8042_process_msg(queue_t *q, mblk_t *mp, struct mouse_state *state)
{
struct iocblk *iocbp;
int rv = 0;
iocbp = (struct iocblk *)mp->b_rptr;
switch (mp->b_datap->db_type) {
case M_FLUSH:
if (*mp->b_rptr & FLUSHW) {
flushq(q, FLUSHDATA);
*mp->b_rptr &= ~FLUSHW;
}
if (*mp->b_rptr & FLUSHR) {
qreply(q, mp);
} else
freemsg(mp);
break;
case M_IOCTL:
mouse8042_iocnack(q, mp, iocbp, EINVAL, 0);
break;
case M_IOCDATA:
mouse8042_iocnack(q, mp, iocbp, EINVAL, 0);
break;
case M_DATA:
rv = mouse8042_process_data_msg(q, mp, state);
break;
default:
freemsg(mp);
break;
}
return (rv);
}
static int
mouse8042_wput(queue_t *q, mblk_t *mp)
{
struct mouse_state *state;
state = (struct mouse_state *)q->q_ptr;
if (state->reset_state != MSE_RESET_IDLE)
return (putq(q, mp));
if (q->q_first)
return (putq(q, mp));
(void) mouse8042_process_msg(q, mp, state);
return (0);
}
static int
mouse8042_wsrv(queue_t *qp)
{
mblk_t *mp;
struct mouse_state *state;
state = (struct mouse_state *)qp->q_ptr;
while ((mp = getq(qp)) != NULL) {
if (mouse8042_process_msg(qp, mp, state) != 0)
break;
}
return (0);
}
static mouse8042_reset_state_e
mouse8042_reset_fsm(mouse8042_reset_state_e reset_state, uint8_t mdata)
{
switch (reset_state) {
case MSE_RESET_PRE:
if (mdata == MSE_ACK)
return (MSE_RESET_ACK);
break;
case MSE_RESET_ACK:
if (mdata == MSE_AA)
return (MSE_RESET_AA);
break;
case MSE_RESET_AA:
if (mdata == MSE_00)
return (MSE_RESET_IDLE);
break;
}
return (reset_state);
}
static uint_t
mouse8042_intr(caddr_t arg)
{
unsigned char mdata;
mblk_t *mp;
struct mouse_state *state = (struct mouse_state *)arg;
int rc;
mutex_enter(&state->ms_mutex);
rc = DDI_INTR_UNCLAIMED;
for (;;) {
if (ddi_get8(state->ms_handle,
state->ms_addr + I8042_INT_INPUT_AVAIL) == 0) {
break;
}
mdata = ddi_get8(state->ms_handle,
state->ms_addr + I8042_INT_INPUT_DATA);
rc = DDI_INTR_CLAIMED;
if (!state->ready)
continue;
mutex_enter(&state->reset_mutex);
if (state->reset_state != MSE_RESET_IDLE) {
if (mdata == MSEERROR || mdata == MSERESET) {
state->reset_state = MSE_RESET_FAILED;
} else {
state->reset_state =
mouse8042_reset_fsm(state->reset_state,
mdata);
}
if (state->reset_state == MSE_RESET_ACK) {
if (state->reset_ack_mp != NULL) {
mp = state->reset_ack_mp;
state->reset_ack_mp = NULL;
if (state->ms_rqp != NULL) {
*mp->b_wptr++ = MSE_ACK;
putnext(state->ms_rqp, mp);
} else
freemsg(mp);
}
if (state->ms_wqp != NULL) {
enableok(state->ms_wqp);
qenable(state->ms_wqp);
}
} else if (state->reset_state == MSE_RESET_IDLE ||
state->reset_state == MSE_RESET_FAILED) {
mutex_exit(&state->reset_mutex);
(void) quntimeout(state->ms_wqp,
state->reset_tid);
mutex_enter(&state->reset_mutex);
(void) ddi_get8(state->ms_handle,
state->ms_addr + I8042_UNLOCK);
state->reset_tid = 0;
if (state->reply_mp != NULL) {
mp = state->reply_mp;
if (state->reset_state ==
MSE_RESET_FAILED) {
*mp->b_wptr++ = mdata;
} else {
*mp->b_wptr++ = MSE_AA;
*mp->b_wptr++ = MSE_00;
}
state->reply_mp = NULL;
} else {
mp = NULL;
}
state->reset_state = MSE_RESET_IDLE;
cv_signal(&state->reset_cv);
if (mp != NULL) {
if (state->ms_rqp != NULL)
putnext(state->ms_rqp, mp);
else
freemsg(mp);
}
if (state->ms_wqp != NULL) {
enableok(state->ms_wqp);
qenable(state->ms_wqp);
}
}
mutex_exit(&state->reset_mutex);
mutex_exit(&state->ms_mutex);
return (rc);
}
mutex_exit(&state->reset_mutex);
if (state->ms_rqp != NULL && (mp = allocb(1, BPRI_MED))) {
*mp->b_wptr++ = mdata;
putnext(state->ms_rqp, mp);
}
}
mutex_exit(&state->ms_mutex);
return (rc);
}