diff options
Diffstat (limited to 'usr/src/uts/common/io/i8042.c')
-rw-r--r-- | usr/src/uts/common/io/i8042.c | 385 |
1 files changed, 66 insertions, 319 deletions
diff --git a/usr/src/uts/common/io/i8042.c b/usr/src/uts/common/io/i8042.c index a36451680d..23489be575 100644 --- a/usr/src/uts/common/io/i8042.c +++ b/usr/src/uts/common/io/i8042.c @@ -147,17 +147,10 @@ struct i8042_port { int rptr; int overruns; unsigned char buf[BUFSIZ]; - /* - * Used during i8042_rep_put8 to intercept the 8042 response in - * i8042_intr() + * has_glock is 1 if this child has the [put8] exclusive-access lock. */ - boolean_t intercept_complete; - boolean_t intr_intercept_enabled; - uint8_t intercept[2]; - uint8_t intercepted_byte; - kcondvar_t intercept_cv; - kmutex_t intercept_mutex; + volatile boolean_t has_glock; }; /* @@ -183,14 +176,16 @@ struct i8042 { #ifdef __sparc timeout_id_t timeout_id; #endif -#ifdef DEBUG /* - * intr_thread is set to curthread in i8042_intr and is - * tested against curthread in i8402_rep_put8(). + * glock is 1 if any child has the [put8] exclusive-access lock + * glock_cv is associated with the condition `glock == 0' */ - kthread_t *intr_thread; -#endif - ddi_softint_handle_t intercept_sih; + volatile int glock; + /* + * Callers awaiting exclusive access in i8042_put8 sleep on glock_cv + * and are signaled when another child relinquishes exclusive access. + */ + kcondvar_t glock_cv; }; /* @@ -401,12 +396,8 @@ static void i8042_write_command_byte(struct i8042 *, unsigned char); static uint8_t i8042_get8(ddi_acc_impl_t *handlep, uint8_t *addr); static void i8042_put8(ddi_acc_impl_t *handlep, uint8_t *addr, uint8_t value); -static void i8042_put8_nolock(ddi_acc_impl_t *handlep, uint8_t *addr, - uint8_t value); -static void i8042_rep_put8(ddi_acc_impl_t *handlep, uint8_t *haddr, - uint8_t *daddr, size_t repcount, uint_t flags); static void i8042_send(struct i8042 *global, int reg, unsigned char cmd); -static uint_t i8042_intercept_softint(caddr_t arg1, caddr_t arg2); +static uint8_t i8042_get8(ddi_acc_impl_t *handlep, uint8_t *addr); unsigned int i8042_unclaimed_interrupts = 0; @@ -492,8 +483,6 @@ i8042_cleanup(struct i8042 *global) } } - (void) ddi_intr_remove_softint(global->intercept_sih); - if (global->init_state & I8042_INIT_MUTEXES) { for (which_port = 0; which_port < NUM_PORTS; which_port++) { @@ -501,9 +490,8 @@ i8042_cleanup(struct i8042 *global) port = &global->i8042_ports[which_port]; mutex_destroy(&port->intr_mutex); #endif - mutex_destroy(&port->intercept_mutex); - cv_destroy(&port->intercept_cv); } + cv_destroy(&global->glock_cv); mutex_destroy(&global->i8042_out_mutex); mutex_destroy(&global->i8042_mutex); } @@ -685,20 +673,17 @@ i8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) mutex_init(&global->i8042_out_mutex, NULL, MUTEX_DRIVER, NULL); + cv_init(&global->glock_cv, NULL, CV_DRIVER, NULL); + for (which_port = 0; which_port < NUM_PORTS; ++which_port) { port = &global->i8042_ports[which_port]; port->initialized = B_FALSE; port->i8042_global = global; port->which = which_port; - port->intr_intercept_enabled = B_FALSE; - cv_init(&port->intercept_cv, NULL, CV_DRIVER, NULL); #if defined(USE_SOFT_INTRS) port->soft_hdl = 0; #else - mutex_init(&port->intercept_mutex, NULL, MUTEX_DRIVER, - (void *)DDI_INTR_SOFTPRI_DEFAULT); - /* * Assume that the interrupt block cookie for port <n> * is iblock_cookies[<n>] (a 1:1 mapping). If there are not @@ -714,8 +699,6 @@ i8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) } else { mutex_init(&port->intr_mutex, NULL, MUTEX_DRIVER, NULL); - mutex_init(&port->intercept_mutex, NULL, MUTEX_DRIVER, - NULL); } #endif @@ -746,11 +729,6 @@ i8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) goto fail; - if (ddi_intr_add_softint(dip, &global->intercept_sih, - DDI_INTR_SOFTPRI_DEFAULT, i8042_intercept_softint, global) - != DDI_SUCCESS) - goto fail; - /* * Assume the number of interrupts is less that the number of * bits in the variable used to keep track of which interrupt @@ -938,6 +916,7 @@ i8042_map( port->rptr = 0; port->dip = dip; port->inumber = 0; + port->has_glock = B_FALSE; port->initialized = B_TRUE; handle = mp->map_handlep; @@ -955,7 +934,7 @@ i8042_map( ap->ahi_get32 = NULL; ap->ahi_put64 = NULL; ap->ahi_get64 = NULL; - ap->ahi_rep_put8 = i8042_rep_put8; + ap->ahi_rep_put8 = NULL; ap->ahi_rep_get8 = NULL; ap->ahi_rep_put16 = NULL; ap->ahi_rep_get16 = NULL; @@ -1030,9 +1009,6 @@ i8042_intr(caddr_t arg) int new_wptr; struct i8042_port *port; -#ifdef DEBUG - global->intr_thread = curthread; -#endif mutex_enter(&global->i8042_mutex); stat = ddi_get8(global->io_handle, global->io_addr + I8042_STAT); @@ -1040,9 +1016,6 @@ i8042_intr(caddr_t arg) if (! (stat & I8042_STAT_OUTBF)) { ++i8042_unclaimed_interrupts; mutex_exit(&global->i8042_mutex); -#ifdef DEBUG - global->intr_thread = NULL; -#endif return (DDI_INTR_UNCLAIMED); } @@ -1054,27 +1027,6 @@ i8042_intr(caddr_t arg) if (! port->initialized) { mutex_exit(&global->i8042_mutex); -#ifdef DEBUG - global->intr_thread = NULL; -#endif - return (DDI_INTR_CLAIMED); - } - - /* - * If interception is enabled, and the byte matches what is being - * waited for, clear the interception flag and trigger a softintr - * that will signal the waiter, then exit the interrupt handler - * without passing the byte to the child's interrupt handler. - */ - if (port->intr_intercept_enabled && (port->intercept[0] == byte || - port->intercept[1] == byte)) { - port->intercepted_byte = byte; - port->intr_intercept_enabled = B_FALSE; - (void) ddi_intr_trigger_softint(global->intercept_sih, port); - mutex_exit(&global->i8042_mutex); -#ifdef DEBUG - global->intr_thread = NULL; -#endif return (DDI_INTR_CLAIMED); } @@ -1089,10 +1041,6 @@ i8042_intr(caddr_t arg) #endif mutex_exit(&global->i8042_mutex); - -#ifdef DEBUG - global->intr_thread = NULL; -#endif return (DDI_INTR_CLAIMED); } @@ -1114,9 +1062,6 @@ i8042_intr(caddr_t arg) mutex_exit(&port->intr_mutex); #endif -#ifdef DEBUG - global->intr_thread = NULL; -#endif return (DDI_INTR_CLAIMED); } @@ -1204,6 +1149,37 @@ i8042_get8(ddi_acc_impl_t *handlep, uint8_t *addr) global = port->i8042_global; switch ((uintptr_t)addr) { + case I8042_LOCK: + ASSERT(port->has_glock != B_TRUE); /* No reentrancy */ + mutex_enter(&global->i8042_out_mutex); + /* + * Block other children requesting exclusive access here until + * the child possessing it relinquishes the lock. + */ + while (global->glock) { + cv_wait(&global->glock_cv, &global->i8042_out_mutex); + } + port->has_glock = B_TRUE; + global->glock = 1; + mutex_exit(&global->i8042_out_mutex); + ret = 0; + break; + + case I8042_UNLOCK: + mutex_enter(&global->i8042_out_mutex); + ASSERT(global->glock != 0); + ASSERT(port->has_glock == B_TRUE); + port->has_glock = B_FALSE; + global->glock = 0; + /* + * Signal anyone waiting for exclusive access that it is now + * available. + */ + cv_signal(&global->glock_cv); + mutex_exit(&global->i8042_out_mutex); + ret = 0; + break; + case I8042_INT_INPUT_AVAIL: mutex_enter(&global->i8042_mutex); ret = port->rptr != port->wptr; @@ -1311,211 +1287,14 @@ i8042_get8(ddi_acc_impl_t *handlep, uint8_t *addr) return (ret); } -/* - * Send the byte specified and wait for a reply -- either the retry response, - * or another response (assumed to be an acknowledgement). If the retry - * response is received within the timeout period, the initial byte is resent - * to the 8042. - */ -static int -i8042_do_intercept(ddi_acc_impl_t *handlep, struct i8042_port *port, - uint8_t *oaddr, uint8_t byte, uint8_t retry_response) -{ - int timedout = 0; - struct i8042 *global; - clock_t tval; - - global = port->i8042_global; - - /* - * Intercept the command response so that the 8042 interrupt handler - * does not call the port's interrupt handler. - */ - port->intercept_complete = B_FALSE; - port->intr_intercept_enabled = B_TRUE; - - /* Maximum time to wait: */ - tval = ddi_get_lbolt() + drv_usectohz(MAX_WAIT_ITERATIONS * - USECS_PER_WAIT); - - do { - i8042_put8_nolock(handlep, oaddr, byte); - - /* - * Wait for the command response - */ - while (!port->intercept_complete) { - if (cv_timedwait(&port->intercept_cv, - &port->intercept_mutex, tval) < 0 && - !port->intercept_complete) { - timedout = 1; - break; - } - } - - /* - * If the intercepted byte is the retry response, keep retrying - * until we time out, or until the success response is received. - */ - if (port->intercept_complete && - port->intercepted_byte == retry_response) - port->intercept_complete = B_FALSE; - - } while (!timedout && !port->intercept_complete); - - port->intr_intercept_enabled = B_FALSE; - - if (timedout && !port->intercept_complete) { - /* - * Some keyboard controllers don't trigger an interrupt, - * so check the status bits to see if there's input available - */ - if (ddi_get8(global->io_handle, global->io_addr + I8042_STAT) & - I8042_STAT_OUTBF) { - byte = ddi_get8(global->io_handle, - global->io_addr + I8042_DATA); - - port->intercepted_byte = byte; - - if (byte == retry_response) - return (B_TRUE); /* Timed out */ - else if (port->intercept[0] == byte) { - port->intercept_complete = B_TRUE; - return (B_FALSE); /* Response OK */ - } - } - } - - return (timedout); -} - -/* - * The _rep_put8() operation is designed to allow child drivers to - * execute commands that have responses or that have responses plus an - * option byte. These commands need to be executed atomically with respect - * to commands from other children (some 8042 implementations get confused - * when other child devices intersperse their commands while a command - * to a different 8042-connected device is in flight). - * - * haddr points to a buffer with either 3 or 4 bytes. Three bytes if a - * command (byte 0) is being sent for which we expect a response code (byte 1) - * (this function blocks until we either read a response code (or the retry - * code (byte 2)) or until a timer expires). - * Four if the command (byte 0) requires a response (byte 1) and then an - * option byte (byte 3). The option byte is only sent iff the response code - * expected is received before the timeout. As with the 3-byte request, byte - * 2 is the retry response. - * - * While this function may technically called in interrupt context, it may - * block (depending on the IPL of the i8042 interrupt handler vs. the handler - * executing) for as long as the timeout (and fail if i8042_intr cannot run). - * - * flags are ignored. - * - */ -/*ARGSUSED*/ static void -i8042_rep_put8(ddi_acc_impl_t *handlep, uint8_t *haddr, uint8_t *daddr, - size_t repcount, uint_t flags) +i8042_put8(ddi_acc_impl_t *handlep, uint8_t *addr, uint8_t value) { - struct i8042_port *port; struct i8042 *global; - uint8_t *oaddr; - uintptr_t devaddr = (uintptr_t)daddr; - int timedout = 0; - boolean_t polled; + struct i8042_port *port; ddi_acc_hdl_t *h; h = (ddi_acc_hdl_t *)handlep; - - port = (struct i8042_port *)h->ah_bus_private; - global = port->i8042_global; - - /* - * If this function is called, somehow, while we're in i8042_intr, - * the logic below will not work. That situation should never be - * possible. - */ - ASSERT(global->intr_thread != curthread); - - /* - * Only support the main port for now - */ - if (port->which != MAIN_PORT || (devaddr != I8042_INT_CMD_PLUS_PARAM && - devaddr != I8042_POLL_CMD_PLUS_PARAM)) { -#ifdef DEBUG - prom_printf("WARNING: i8042_rep_put8(): port or address " - "invalid\n"); -#endif - return; - } - - /* - * Only support commands with MAX one parameter. The format of the - * buffer supplied must be { <CMD>, <CMD_OK_RESPONSE>[, <PARAMETER>] } - */ - if (repcount != 3 && repcount != 4) { -#ifdef DEBUG - prom_printf("WARNING: i8042_rep_put8(): Invalid repetition " - "count (%d)\n", (int)repcount); -#endif - return; - } - - polled = (devaddr == I8042_POLL_CMD_PLUS_PARAM); - - if (polled) { - oaddr = (uint8_t *)I8042_POLL_OUTPUT_DATA; - } else { - oaddr = (uint8_t *)I8042_INT_OUTPUT_DATA; - /* - * Mutexes are only required for the non-polled (polled - * via the virtual registers, NOT via the polling mechanism - * used for systems without 8042 interrupts) case, because - * when polling is used, the system is single-threaded - * with interrupts disabled. - */ - mutex_enter(&global->i8042_out_mutex); - } - - /* Initialize the response and retry bytes from the caller */ - port->intercept[0] = haddr[1]; - port->intercept[1] = haddr[2]; - - mutex_enter(&port->intercept_mutex); - - timedout = i8042_do_intercept(handlep, port, oaddr, haddr[0], haddr[2]); - - /* - * If the first byte was processed before the timeout period, and - * there's an option byte, send it now. - */ - if (!timedout && repcount == 4) { - timedout = i8042_do_intercept(handlep, port, oaddr, haddr[3], - haddr[2]); - } - - mutex_exit(&port->intercept_mutex); - -#ifdef DEBUG - if (timedout && i8042_debug) - prom_printf("WARNING: i8042_rep_put8(): timed out waiting for " - "command response\n"); -#endif - - if (!polled) - mutex_exit(&global->i8042_out_mutex); -} - -static void -i8042_put8_nolock(ddi_acc_impl_t *handlep, uint8_t *addr, uint8_t value) -{ - struct i8042_port *port; - struct i8042 *global; - ddi_acc_hdl_t *h; - - h = (ddi_acc_hdl_t *)handlep; - port = (struct i8042_port *)h->ah_bus_private; global = port->i8042_global; @@ -1523,43 +1302,33 @@ i8042_put8_nolock(ddi_acc_impl_t *handlep, uint8_t *addr, uint8_t value) case I8042_INT_OUTPUT_DATA: case I8042_POLL_OUTPUT_DATA: + if ((uintptr_t)addr == I8042_INT_OUTPUT_DATA) { + mutex_enter(&global->i8042_out_mutex); + + /* + * If no child has exclusive access, then proceed with + * the put8 below. If a child (not the one making the + * call) has exclusive access, wait for it to be + * relinquished. The use of i8042_out_mutex prevents + * children seeking exclusive access from getting it + * while a child is writing to the 8042. + */ + while (global->glock && !port->has_glock) { + cv_wait(&global->glock_cv, + &global->i8042_out_mutex); + } + } + if (port->which == AUX_PORT) i8042_send(global, I8042_CMD, I8042_CMD_WRITE_AUX); i8042_send(global, I8042_DATA, value); - break; - } -} - -static void -i8042_put8(ddi_acc_impl_t *handlep, uint8_t *addr, uint8_t value) -{ - struct i8042 *global; - ddi_acc_hdl_t *h; - - h = (ddi_acc_hdl_t *)handlep; - global = ((struct i8042_port *)h->ah_bus_private)->i8042_global; - - switch ((uintptr_t)addr) { - case I8042_INT_OUTPUT_DATA: - case I8042_POLL_OUTPUT_DATA: - - if ((uintptr_t)addr == I8042_INT_OUTPUT_DATA) - mutex_enter(&global->i8042_out_mutex); - - i8042_put8_nolock(handlep, addr, value); - if ((uintptr_t)addr == I8042_INT_OUTPUT_DATA) mutex_exit(&global->i8042_out_mutex); break; - case I8042_INT_CMD_PLUS_PARAM: - case I8042_POLL_CMD_PLUS_PARAM: - - break; - #if defined(DEBUG) case I8042_INT_INPUT_AVAIL: case I8042_INT_INPUT_DATA: @@ -1915,25 +1684,3 @@ i8042_is_polling_platform(void) return (B_FALSE); } #endif - -/* - * arg1 is the global i8042 state pointer (not used) - * arg2 is the port pointer for the intercepted port - */ -/*ARGSUSED*/ -static uint_t -i8042_intercept_softint(caddr_t arg1, caddr_t arg2) -{ - struct i8042_port *port = (struct i8042_port *)arg2; - ASSERT(port != NULL); - - mutex_enter(&port->intercept_mutex); - if (!port->intercept_complete) { - port->intercept_complete = B_TRUE; - cv_signal(&port->intercept_cv); - mutex_exit(&port->intercept_mutex); - return (DDI_INTR_CLAIMED); - } - mutex_exit(&port->intercept_mutex); - return (DDI_INTR_UNCLAIMED); -} |