| /* |
| * 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 (c) 1990, 1991 UNIX System Laboratories, Inc. */ |
| /* Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T */ |
| /* All Rights Reserved */ |
| |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| |
| /* |
| * PS/2 type Mouse Module - Streams |
| */ |
| |
| #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 /* 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); |
| |
| |
| /* |
| * |
| * Local Static Data |
| * |
| */ |
| |
| /* |
| * We only support one instance. Yes, it's theoretically possible to |
| * plug in more than one, but it's not worth the implementation cost. |
| * |
| * The introduction of USB keyboards might make it worth reassessing |
| * this decision, as they might free up the keyboard port for a second |
| * PS/2 style mouse. |
| */ |
| 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; |
| 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); |
| |
| |
| /* |
| * Streams module info. |
| */ |
| #define MODULE_NAME "mouse8042" |
| |
| static struct module_info mouse8042_minfo = { |
| 23, /* Module ID number */ |
| MODULE_NAME, |
| 0, INFPSZ, /* minimum & maximum packet sizes */ |
| 256, 128 /* hi and low water marks */ |
| }; |
| |
| static struct qinit mouse8042_rinit = { |
| NULL, /* put */ |
| NULL, /* service */ |
| mouse8042_open, |
| mouse8042_close, |
| NULL, /* admin */ |
| &mouse8042_minfo, |
| NULL /* statistics */ |
| }; |
| |
| static struct qinit mouse8042_winit = { |
| mouse8042_wput, /* put */ |
| mouse8042_wsrv, /* service */ |
| NULL, /* open */ |
| NULL, /* close */ |
| NULL, /* admin */ |
| &mouse8042_minfo, |
| NULL /* statistics */ |
| }; |
| |
| static struct streamtab mouse8042_strinfo = { |
| &mouse8042_rinit, |
| &mouse8042_winit, |
| NULL, /* muxrinit */ |
| NULL, /* muxwinit */ |
| }; |
| |
| /* |
| * Local Function Declarations |
| */ |
| |
| static struct cb_ops mouse8042_cb_ops = { |
| nodev, /* open */ |
| nodev, /* close */ |
| nodev, /* strategy */ |
| nodev, /* print */ |
| nodev, /* dump */ |
| nodev, /* read */ |
| nodev, /* write */ |
| nodev, /* ioctl */ |
| nodev, /* devmap */ |
| nodev, /* mmap */ |
| nodev, /* segmap */ |
| nochpoll, /* poll */ |
| ddi_prop_op, /* cb_prop_op */ |
| &mouse8042_strinfo, /* streamtab */ |
| D_MP | D_NEW |
| }; |
| |
| |
| static struct dev_ops mouse8042_ops = { |
| DEVO_REV, /* devo_rev, */ |
| 0, /* refcnt */ |
| mouse8042_getinfo, /* getinfo */ |
| nulldev, /* identify */ |
| nulldev, /* probe */ |
| mouse8042_attach, /* attach */ |
| mouse8042_detach, /* detach */ |
| nodev, /* reset */ |
| &mouse8042_cb_ops, /* driver operations */ |
| (struct bus_ops *)0, /* bus operations */ |
| NULL, /* power */ |
| ddi_quiesce_not_needed, /* quiesce */ |
| }; |
| |
| /* |
| * This is the loadable module wrapper. |
| */ |
| #include <sys/modctl.h> |
| |
| extern struct mod_ops mod_driverops; |
| |
| /* |
| * Module linkage information for the kernel. |
| */ |
| |
| static struct modldrv modldrv = { |
| &mod_driverops, /* Type of module. This one is a driver */ |
| "PS/2 Mouse", |
| &mouse8042_ops, /* driver ops */ |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, |
| (void *)&modldrv, |
| NULL |
| }; |
| |
| /* |
| * This is the driver initialization routine. |
| */ |
| 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); |
| |
| /* 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. |
| */ |
| 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); |
| |
| /* 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); |
| |
| /* |
| * In order to support virtual keyboard/mouse, we should distinguish |
| * between internal virtual open and external physical open. |
| * |
| * When the physical devices are opened by application, they will |
| * be unlinked from the virtual device and their data stream will |
| * not be sent to the virtual device. When the opened physical |
| * devices are closed, they will be relinked to the virtual devices. |
| * |
| * All these automatic switch between virtual and physical are |
| * transparent. |
| * |
| * So we change minor node numbering scheme to be: |
| * external node minor num == instance * 2 |
| * internal node minor num == instance * 2 + 1 |
| */ |
| rc = ddi_create_minor_node(dip, "mouse", S_IFCHR, instance * 2, |
| DDI_NT_MOUSE, NULL); |
| 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; |
| |
| /* 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); |
| 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); |
| } |
| |
| /*ARGSUSED*/ |
| 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: |
| /* 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; |
| 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); |
| } |
| } |
| |
| |
| /* ARGSUSED */ |
| 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); |
| } |
| |
| /*ARGSUSED*/ |
| 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) { |
| /* |
| * Exit if the same minor node is already open |
| */ |
| if (state->ms_minor == minor) { |
| mutex_exit(&state->ms_mutex); |
| return (0); |
| } |
| |
| /* |
| * Check whether it is switch between physical and virtual |
| * |
| * Opening from virtual while the device is being physically |
| * opened by an application should not happen. So we ASSERT |
| * this in DEBUG version, and return error in the non-DEBUG |
| * case. |
| */ |
| ASSERT(!MOUSE8042_INTERNAL_OPEN(minor)); |
| |
| if (MOUSE8042_INTERNAL_OPEN(minor)) { |
| mutex_exit(&state->ms_mutex); |
| return (EINVAL); |
| } |
| |
| /* |
| * Opening the physical one while it is being underneath |
| * the virtual one. |
| * |
| * consconfig_unlink is called to unlink this device from |
| * the virtual one, thus the old stream serving for this |
| * device under the virtual one is closed, and then the |
| * lower driver's close routine (here is mouse8042_close) |
| * is also called to accomplish the whole stream close. |
| * Here we have to drop the lock because mouse8042_close |
| * also needs the lock. |
| * |
| * For mouse, the old stream is: |
| * consms->["pushmod"->]"mouse_vp driver" |
| * |
| * After the consconfig_unlink returns, the old stream is closed |
| * and we grab the lock again to reopen this device as normal. |
| */ |
| mutex_exit(&state->ms_mutex); |
| |
| /* |
| * If unlink fails, fail the physical open. |
| */ |
| 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); |
| } |
| |
| |
| /*ARGSUSED*/ |
| 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; |
| |
| /* |
| * Disable queue processing now, so that another reset cannot get in |
| * after we wait for the current reset (if any) to complete. |
| */ |
| 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; |
| } |
| |
| 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)) { |
| /* |
| * Closing physical PS/2 mouse |
| * |
| * Link it back to virtual mouse, and |
| * mouse8042_open will be called as a result |
| * of the consconfig_link call. Do NOT try |
| * this if the mouse is about to be detached! |
| * |
| * If linking back fails, this specific mouse |
| * will not be available underneath the virtual |
| * mouse, and can only be accessed via physical |
| * open. |
| */ |
| 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 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; |
| 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); |
| } |
| |
| /* |
| * Returns 1 if the caller should put the message (bp) back on the queue |
| */ |
| static int |
| mouse8042_initiate_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); |
| |
| 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, (state->reply_mp == NULL) ? 2 : 1, |
| 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); |
| } else { |
| /* Bufcall completed successfully (or wasn't needed) */ |
| state->bc_id = 0; |
| } |
| |
| /* |
| * 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) |
| { |
| mblk_t *bp; |
| mblk_t *next; |
| |
| 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_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_initiate_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; |
| |
| 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); |
| } |
| |
| /* |
| * 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) |
| { |
| 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 |
| 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 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 (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 |
| * 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). |
| */ |
| |
| 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); |
| } |