summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/mouse8042.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/mouse8042.c')
-rw-r--r--usr/src/uts/common/io/mouse8042.c478
1 files changed, 358 insertions, 120 deletions
diff --git a/usr/src/uts/common/io/mouse8042.c b/usr/src/uts/common/io/mouse8042.c
index e666caed53..1ed61b49a8 100644
--- a/usr/src/uts/common/io/mouse8042.c
+++ b/usr/src/uts/common/io/mouse8042.c
@@ -23,7 +23,7 @@
/* All Rights Reserved */
/*
- * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -42,6 +42,7 @@
#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>
@@ -53,17 +54,16 @@
#include <sys/i8042.h>
#include <sys/note.h>
+#include <sys/mouse.h>
#define DRIVER_NAME(dip) ddi_driver_name(dip)
-#ifdef DEBUG
-#define MOUSE8042_DEBUG
-#endif
-
#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 /* 500 ms */
+
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);
@@ -85,6 +85,17 @@ extern int consconfig_unlink(major_t major, minor_t minor);
*/
static dev_info_t *mouse8042_dip;
+/*
+ * RESET states
+ */
+typedef enum {
+ MSE_RESET_IDLE, /* No reset in progress */
+ MSE_RESET_PRE, /* Send reset, waiting for ACK */
+ MSE_RESET_ACK, /* Got ACK, waiting for 0xAA */
+ MSE_RESET_AA, /* Got 0xAA, waiting for 0x00 */
+ MSE_RESET_FAILED
+} mouse8042_reset_state_e;
+
struct mouse_state {
queue_t *ms_rqp;
queue_t *ms_wqp;
@@ -95,18 +106,19 @@ struct mouse_state {
minor_t ms_minor;
boolean_t ms_opened;
+ kmutex_t reset_mutex;
+ mouse8042_reset_state_e reset_state;
+ timeout_id_t reset_tid;
+ int ready;
+ mblk_t *reply_mp;
+ bufcall_id_t bc_id;
};
-#if defined(MOUSE8042_DEBUG)
-int mouse8042_debug = 0;
-int mouse8042_debug_minimal = 0;
-#endif
-
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_wput(queue_t *q, mblk_t *mp);
+static int mouse8042_wsrv(queue_t *qp);
static int mouse8042_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result);
@@ -137,8 +149,8 @@ static struct qinit mouse8042_rinit = {
};
static struct qinit mouse8042_winit = {
- mouse8042_wput, /* put */
- NULL, /* service */
+ putq, /* put */
+ mouse8042_wsrv, /* service */
NULL, /* open */
NULL, /* close */
NULL, /* admin */
@@ -254,15 +266,12 @@ mouse8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
int rc;
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug) {
- cmn_err(CE_CONT, MODULE_NAME "_attach entry\n");
- }
-#endif
-
if (cmd == DDI_RESUME) {
state = (struct mouse_state *)ddi_get_driver_private(dip);
+ /* Ready to handle inbound data from mouse8042_intr */
+ state->ready = 1;
+
/*
* Send a 0xaa 0x00 upstream.
* This causes the vuid module to reset the mouse.
@@ -289,6 +298,9 @@ mouse8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
/* allocate and initialize state structure */
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);
/*
@@ -310,10 +322,6 @@ mouse8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
rc = ddi_create_minor_node(dip, "mouse", S_IFCHR, instance * 2,
DDI_NT_MOUSE, NULL);
if (rc != DDI_SUCCESS) {
-#if defined(MOUSE8042_DEBUG)
- cmn_err(CE_CONT,
- MODULE_NAME "_attach: ddi_create_minor_node failed\n");
-#endif
goto fail_1;
}
@@ -325,41 +333,33 @@ mouse8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
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) {
-#if defined(MOUSE8042_DEBUG)
- cmn_err(CE_WARN, MODULE_NAME "_attach: can't map registers");
-#endif
goto fail_2;
}
rc = ddi_get_iblock_cookie(dip, 0, &state->ms_iblock_cookie);
if (rc != DDI_SUCCESS) {
-#if defined(MOUSE8042_DEBUG)
- cmn_err(CE_WARN,
- MODULE_NAME "_attach: Can't get iblock cookie");
-#endif
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);
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) {
-#if defined(MOUSE8042_DEBUG)
- cmn_err(CE_WARN, MODULE_NAME "_attach: cannot add interrupt");
-#endif
goto fail_3;
}
mouse8042_dip = dip;
+ /* Ready to handle inbound data from mouse8042_intr */
+ state->ready = 1;
+
/* Now that we're attached, announce our presence to the world. */
ddi_report_dev(dip);
-#if defined(MOUSE8042_DEBUG)
- cmn_err(CE_CONT, "?%s #%d\n", DRIVER_NAME(dip), ddi_get_instance(dip));
-#endif
return (DDI_SUCCESS);
fail_3:
@@ -383,11 +383,14 @@ mouse8042_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
switch (cmd) {
case DDI_SUSPEND:
+ /* Ignore all data from mouse8042_intr until we fully resume */
+ state->ready = 0;
return (DDI_SUCCESS);
case DDI_DETACH:
ddi_remove_intr(dip, 0, state->ms_iblock_cookie);
mouse8042_dip = NULL;
+ mutex_destroy(&state->reset_mutex);
mutex_destroy(&state->ms_mutex);
ddi_prop_remove_all(dip);
ddi_regs_map_free(&state->ms_handle);
@@ -396,12 +399,6 @@ mouse8042_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
return (DDI_SUCCESS);
default:
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug) {
- cmn_err(CE_CONT,
- "mouse8042_detach: cmd = %d unknown\n", cmd);
- }
-#endif
return (DDI_FAILURE);
}
}
@@ -419,10 +416,6 @@ mouse8042_getinfo(
minor_t minor = getminor(dev);
int instance = MOUSE8042_MINOR_TO_INSTANCE(minor);
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_getinfo: call\n");
-#endif
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if (mouse8042_dip == NULL)
@@ -457,11 +450,6 @@ mouse8042_open(
state = ddi_get_driver_private(mouse8042_dip);
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_open:entered\n");
-#endif
-
mutex_enter(&state->ms_mutex);
if (state->ms_opened) {
@@ -545,15 +533,23 @@ mouse8042_close(queue_t *q, int flag, cred_t *cred_p)
state = (struct mouse_state *)q->q_ptr;
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_close:entered\n");
-#endif
-
mutex_enter(&state->ms_mutex);
qprocsoff(q);
+ if (state->reset_tid != 0) {
+ (void) quntimeout(q, state->reset_tid);
+ state->reset_tid = 0;
+ }
+ if (state->bc_id != 0) {
+ (void) qunbufcall(q, state->bc_id);
+ state->bc_id = 0;
+ }
+ if (state->reply_mp != NULL) {
+ freemsg(state->reply_mp);
+ state->reply_mp = NULL;
+ }
+
q->q_ptr = NULL;
WR(q)->q_ptr = NULL;
state->ms_rqp = NULL;
@@ -600,78 +596,267 @@ mouse8042_iocnack(
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 the interrupt handler hasn't completed the reset handling
+ * (reset_state would be IDLE or FAILED in that case), then
+ * drop the 8042 lock, and send a faked retry reply upstream,
+ * then enable the queue for further message processing.
+ */
+ if (state->reset_state != MSE_RESET_IDLE &&
+ state->reset_state != MSE_RESET_FAILED) {
+
+ state->reset_tid = 0;
+ state->reset_state = MSE_RESET_IDLE;
+
+ (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);
+}
+
+/*
+ * Returns 1 if the caller should put the message (bp) back on the queue
+ */
static int
-mouse8042_wput(queue_t *q, mblk_t *mp)
+mouse8042_process_reset(queue_t *q, mblk_t *mp, struct mouse_state *state)
+{
+ mutex_enter(&state->reset_mutex);
+ /*
+ * If we're in the middle of a reset, put the message back on the queue
+ * for processing later.
+ */
+ if (state->reset_state != MSE_RESET_IDLE) {
+ /*
+ * We noenable the queue again here in case it was backenabled
+ * by an upper-level module.
+ */
+ noenable(q);
+
+ mutex_exit(&state->reset_mutex);
+ return (1);
+ }
+
+ /*
+ * Drop the reset state lock before allocating the response message and
+ * grabbing the 8042 exclusive-access lock (since those operations
+ * may take an extended period of time to complete).
+ */
+ mutex_exit(&state->reset_mutex);
+
+ state->reply_mp = allocb(3, BPRI_MED);
+ if (state->reply_mp == NULL) {
+ /*
+ * Allocation failed -- set up a bufcall to enable the queue
+ * whenever there is enough memory to allocate the response
+ * message.
+ */
+ state->bc_id = qbufcall(q, 3, BPRI_MED,
+ (void (*)(void *))qenable, q);
+
+ if (state->bc_id == 0) {
+ /*
+ * If the qbufcall failed, we cannot proceed, so use the
+ * message we were sent to respond with an error.
+ */
+ *mp->b_rptr = MSEERROR;
+ mp->b_wptr = mp->b_rptr + 1;
+ qreply(q, mp);
+ return (0);
+ }
+
+ return (1);
+ }
+
+ /*
+ * Gain exclusive access to the 8042 for the duration of the reset.
+ * The unlock will occur when the reset has either completed or timed
+ * out.
+ */
+ (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);
+}
+
+/*
+ * Returns 1 if the caller should stop processing messages
+ */
+static int
+mouse8042_process_data_msg(queue_t *q, mblk_t *mp, struct mouse_state *state)
{
- struct iocblk *iocbp;
mblk_t *bp;
mblk_t *next;
- struct mouse_state *state;
- state = (struct mouse_state *)q->q_ptr;
+ bp = mp;
+ do {
+ while (bp->b_rptr < bp->b_wptr) {
+ /*
+ * Detect an attempt to reset the mouse. Lock out any
+ * further mouse writes until the reset has completed.
+ */
+ if (*bp->b_rptr == MSERESET) {
+
+ /*
+ * If we couldn't allocate memory and we
+ * we couldn't register a bufcall,
+ * mouse8042_process_reset returns 0 and
+ * has already used the message to send an
+ * error reply back upstream, so there is no
+ * need to deallocate or put this message back
+ * on the queue.
+ */
+ if (mouse8042_process_reset(q, bp, state) == 0)
+ return (1);
+
+ /*
+ * If there's no data remaining in this block,
+ * free this block and put the following blocks
+ * of this message back on the queue. If putting
+ * the rest of the message back on the queue
+ * fails, free the the message.
+ */
+ 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;
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_wput:entered\n");
-#endif
iocbp = (struct iocblk *)mp->b_rptr;
+
switch (mp->b_datap->db_type) {
case M_FLUSH:
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_wput:M_FLUSH\n");
-#endif
-
- if (*mp->b_rptr & FLUSHW)
+ if (*mp->b_rptr & FLUSHW) {
flushq(q, FLUSHDATA);
- qreply(q, mp);
+ *mp->b_rptr &= ~FLUSHW;
+ }
+ if (*mp->b_rptr & FLUSHR) {
+ qreply(q, mp);
+ } else
+ freemsg(mp);
break;
case M_IOCTL:
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_wput:M_IOCTL\n");
-#endif
mouse8042_iocnack(q, mp, iocbp, EINVAL, 0);
break;
case M_IOCDATA:
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_wput:M_IOCDATA\n");
-#endif
mouse8042_iocnack(q, mp, iocbp, EINVAL, 0);
break;
case M_DATA:
- bp = mp;
- do {
- while (bp->b_rptr < bp->b_wptr) {
-#if defined(MOUSE8042_DEBUG)
- if (mouse8042_debug) {
- cmn_err(CE_CONT,
- "mouse8042: send %2x\n",
- *bp->b_rptr);
- }
- if (mouse8042_debug_minimal) {
- cmn_err(CE_CONT, ">a:%2x ",
- *bp->b_rptr);
- }
-#endif
- 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);
+ rv = mouse8042_process_data_msg(q, mp, state);
break;
default:
freemsg(mp);
break;
}
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_wput:leaving\n");
-#endif
- return (0); /* ignored */
+
+ return (rv);
+}
+
+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);
+}
+
+/*
+ * Returns the next reset state, given the current state and the byte
+ * received from the mouse. Error and Resend codes are handled by the
+ * caller.
+ */
+static mouse8042_reset_state_e
+mouse8042_reset_fsm(mouse8042_reset_state_e reset_state, uint8_t mdata)
+{
+ switch (reset_state) {
+ case MSE_RESET_PRE: /* RESET sent, now we expect an ACK */
+ if (mdata == MSE_ACK) /* Got the ACK */
+ return (MSE_RESET_ACK);
+ break;
+
+ case MSE_RESET_ACK: /* ACK received; now we expect 0xAA */
+ if (mdata == MSE_AA) /* Got the 0xAA */
+ return (MSE_RESET_AA);
+ break;
+
+ case MSE_RESET_AA: /* 0xAA received; now we expect 0x00 */
+ if (mdata == MSE_00)
+ return (MSE_RESET_IDLE);
+ break;
+ }
+
+ return (reset_state);
}
static uint_t
@@ -684,10 +869,6 @@ mouse8042_intr(caddr_t arg)
mutex_enter(&state->ms_mutex);
-#if defined(MOUSE8042_DEBUG)
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_intr()\n");
-#endif
rc = DDI_INTR_UNCLAIMED;
for (;;) {
@@ -700,24 +881,81 @@ mouse8042_intr(caddr_t arg)
mdata = ddi_get8(state->ms_handle,
state->ms_addr + I8042_INT_INPUT_DATA);
-#if defined(MOUSE8042_DEBUG)
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_intr: got %2x\n", mdata);
- if (mouse8042_debug_minimal)
- cmn_err(CE_CONT, "<A:%2x ", mdata);
-#endif
-
rc = DDI_INTR_CLAIMED;
+ /*
+ * If we're not ready for this data, discard it.
+ */
+ 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 we transitioned back to the idle reset state (or
+ * the reset failed), disable the timeout, release the
+ * 8042 exclusive-access lock, then send the response
+ * the the upper-level modules. Finally, enable the
+ * queue and schedule queue service procedures so that
+ * upper-level modules can process the response.
+ * Otherwise, if we're still in the middle of the
+ * reset sequence, do not send the data up (since the
+ * response is sent at the end of the sequence, or
+ * on timeout/error).
+ */
+ 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;
+ mp = state->reply_mp;
+ if (state->reset_state == MSE_RESET_FAILED) {
+ *mp->b_wptr++ = mdata;
+ } else {
+ *mp->b_wptr++ = MSE_ACK;
+ *mp->b_wptr++ = MSE_AA;
+ *mp->b_wptr++ = MSE_00;
+ }
+ state->reply_mp = NULL;
+
+ state->reset_state = MSE_RESET_IDLE;
+
+ if (state->ms_rqp != NULL)
+ putnext(state->ms_rqp, mp);
+ else
+ freemsg(mp);
+
+ 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);
}
}
-#ifdef MOUSE8042_DEBUG
- if (mouse8042_debug)
- cmn_err(CE_CONT, "mouse8042_intr() ok\n");
-#endif
mutex_exit(&state->ms_mutex);
return (rc);