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