root/usr/src/uts/common/io/mouse8042.c
/*
 * 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, 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;

        /* 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);
}