changeset 10803:32de18ad59f8

6881318 mouse8042 panics on access to /dev/kdmouse (Acer Aspire One, Synaptics) 6885611 Xorg synaptics driver no longer works after 6825031
author Seth Goldberg <Seth.Goldberg@Sun.COM>
date Thu, 15 Oct 2009 16:16:48 -0700
parents 371d70657c0e
children f38860d83eb4
files usr/src/uts/common/io/mouse8042.c usr/src/uts/common/io/vuidmice/vuidps2.c
diffstat 2 files changed, 159 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/uts/common/io/mouse8042.c	Thu Oct 15 14:52:42 2009 -0700
+++ b/usr/src/uts/common/io/mouse8042.c	Thu Oct 15 16:16:48 2009 -0700
@@ -107,10 +107,12 @@
 	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;
 };
 
@@ -119,6 +121,7 @@
 		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);
@@ -149,7 +152,7 @@
 };
 
 static struct qinit mouse8042_winit = {
-	putq,		/* put */
+	mouse8042_wput,	/* put */
 	mouse8042_wsrv,	/* service */
 	NULL,		/* open */
 	NULL,		/* close */
@@ -345,6 +348,7 @@
 	    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,
@@ -390,6 +394,7 @@
 	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);
@@ -533,22 +538,47 @@
 
 	state = (struct mouse_state *)q->q_ptr;
 
-	mutex_enter(&state->ms_mutex);
+	/*
+	 * Disable queue processing now, so that another reset cannot get in
+	 * after we wait for the current reset (if any) to complete.
+	 */
+	qprocsoff(q);
 
-	qprocsoff(q);
+	mutex_enter(&state->reset_mutex);
+	while (state->reset_state != MSE_RESET_IDLE) {
+		/*
+		 * Waiting for the previous reset to finish is
+		 * non-interruptible.  Some upper-level clients
+		 * cannot deal with EINTR and will not close the
+		 * STREAM properly, resulting in failure to reopen it
+		 * within the same process.
+		 */
+		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;
 	}
-	if (state->reply_mp != NULL) {
-		freemsg(state->reply_mp);
-		state->reply_mp = NULL;
-	}
 
 	q->q_ptr = NULL;
 	WR(q)->q_ptr = NULL;
@@ -615,6 +645,7 @@
 
 		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);
@@ -641,7 +672,7 @@
  * Returns 1 if the caller should put the message (bp) back on the queue
  */
 static int
-mouse8042_process_reset(queue_t *q, mblk_t *mp, struct mouse_state *state)
+mouse8042_initiate_reset(queue_t *q, mblk_t *mp, struct mouse_state *state)
 {
 	mutex_enter(&state->reset_mutex);
 	/*
@@ -666,15 +697,19 @@
 	 */
 	mutex_exit(&state->reset_mutex);
 
-	state->reply_mp = allocb(3, BPRI_MED);
-	if (state->reply_mp == NULL) {
+	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) {
 		/*
 		 * 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);
+		state->bc_id = qbufcall(q, (state->reply_mp == NULL) ? 2 : 1,
+		    BPRI_MED, (void (*)(void *))qenable, q);
 
 		if (state->bc_id == 0) {
 			/*
@@ -688,6 +723,9 @@
 		}
 
 		return (1);
+	} else {
+		/* Bufcall completed successfully (or wasn't needed) */
+		state->bc_id = 0;
 	}
 
 	/*
@@ -740,13 +778,13 @@
 				/*
 				 * If we couldn't allocate memory and we
 				 * we couldn't register a bufcall,
-				 * mouse8042_process_reset returns 0 and
+				 * mouse8042_initiate_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)
+				if (mouse8042_initiate_reset(q, bp, state) == 0)
 					return (1);
 
 				/*
@@ -816,6 +854,40 @@
 	return (rv);
 }
 
+/*
+ * This is the main mouse input routine.  Commands and parameters
+ * from upstream are sent to the mouse device immediately, unless
+ * the mouse is in the process of being reset, in which case
+ * commands are queued and executed later in the service procedure.
+ */
+static int
+mouse8042_wput(queue_t *q, mblk_t *mp)
+{
+	struct mouse_state *state;
+	state = (struct mouse_state *)q->q_ptr;
+
+	/*
+	 * Process all messages immediately, unless a reset is in
+	 * progress.  If a reset is in progress, deflect processing to
+	 * the service procedure.
+	 */
+	if (state->reset_state != MSE_RESET_IDLE)
+		return (putq(q, mp));
+
+	/*
+	 * If there are still messages outstanding in the queue that
+	 * the service procedure hasn't processed yet, put this
+	 * message in the queue also, to ensure proper message
+	 * ordering.
+	 */
+	if (q->q_first)
+		return (putq(q, mp));
+
+	(void) mouse8042_process_msg(q, mp, state);
+
+	return (0);
+}
+
 static int
 mouse8042_wsrv(queue_t *qp)
 {
@@ -900,6 +972,35 @@
 				    mdata);
 			}
 
+			if (state->reset_state == MSE_RESET_ACK) {
+
+			/*
+			 * We received an ACK from the mouse, so
+			 * send it upstream immediately so that
+			 * consumers depending on the immediate
+			 * ACK don't time out.
+			 */
+				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) {
+
 			/*
 			 * If we transitioned back to the idle reset state (or
 			 * the reset failed), disable the timeout, release the
@@ -912,8 +1013,6 @@
 			 * 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,
@@ -924,25 +1023,34 @@
 				    state->ms_addr + I8042_UNLOCK);
 
 				state->reset_tid = 0;
-				mp = state->reply_mp;
-				if (state->reset_state == MSE_RESET_FAILED) {
-					*mp->b_wptr++ = mdata;
+				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->b_wptr++ = MSE_ACK;
-					*mp->b_wptr++ = MSE_AA;
-					*mp->b_wptr++ = MSE_00;
+					mp = NULL;
 				}
-				state->reply_mp = NULL;
 
 				state->reset_state = MSE_RESET_IDLE;
+				cv_signal(&state->reset_cv);
 
-				if (state->ms_rqp != NULL)
-					putnext(state->ms_rqp, mp);
-				else
-					freemsg(mp);
+				if (mp != NULL) {
+					if (state->ms_rqp != NULL)
+						putnext(state->ms_rqp, mp);
+					else
+						freemsg(mp);
+				}
 
-				enableok(state->ms_wqp);
-				qenable(state->ms_wqp);
+				if (state->ms_wqp != NULL) {
+					enableok(state->ms_wqp);
+					qenable(state->ms_wqp);
+				}
 			}
 
 			mutex_exit(&state->reset_mutex);
--- a/usr/src/uts/common/io/vuidmice/vuidps2.c	Thu Oct 15 14:52:42 2009 -0700
+++ b/usr/src/uts/common/io/vuidmice/vuidps2.c	Thu Oct 15 16:16:48 2009 -0700
@@ -622,13 +622,18 @@
 			 */
 			if (length == 1) {
 				/*
-				 * Technically, a length of 1 from the mouse
-				 * driver should only contain a MSEERROR or
-				 * MSERESEND value, but we don't test
-				 * that here because we'd be issuing a reset
-				 * anyway.  For mice that are not connected,
-				 * we will receive a MSERESEND quickly.
+				 * A response with length 1 from the mouse
+				 * driver can be either an ACK (the first part
+				 * of the reset reply) or either MSEERROR or
+				 * MSERESEND.  Issue another reset if either
+				 * of the latter are received.  For mice that
+				 * are not connected, MSERESEND is received
+				 * quickly.
 				 */
+
+				if (code == MSE_ACK)
+					break;
+
 				if (++STATEP->init_count >=
 				    PS2_MAX_INIT_COUNT) {
 					STATEP->inited |= PS2_FLAG_INIT_TIMEOUT;
@@ -636,13 +641,22 @@
 				} else {
 					put1(WR(qp), MSERESET);
 				}
+
 				break;
 
-			} else if (length != 3) {
+			} else if (length != 2) {
 				break;
 			}
 
-			mp->b_rptr += 2;	/* Skip past the 0xAA 0x00 */
+			/*
+			 * The only possible 2-byte reply from mouse8042 is
+			 * 0xAA 0x00.  If the mouse doesn't send that, mouse8042
+			 * will send a 1-byte error message, handled above by
+			 * resetting the mouse.
+			 */
+
+			/* Skip past the 0x00 (since `code' contains 0xAA) */
+			mp->b_rptr += 1;
 
 			/* Reset completed successfully */