root/usr/src/uts/common/io/usb/clients/usbser/usbser.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 *
 * USB generic serial driver (GSD)
 *
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/termio.h>
#include <sys/termiox.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/strtty.h>
#include <sys/policy.h>
#include <sys/consdev.h>

#include <sys/usb/usba.h>
#include <sys/usb/clients/usbser/usbser_var.h>
#include <sys/usb/clients/usbser/usbser_dsdi.h>
#include <sys/usb/clients/usbser/usbser_rseq.h>
#include <sys/usb/usba/genconsole.h>

/* autoconfiguration subroutines */
static int      usbser_rseq_do_cb(rseq_t *, int, uintptr_t);
static int      usbser_free_soft_state(usbser_state_t *);
static int      usbser_init_soft_state(usbser_state_t *);
static int      usbser_fini_soft_state(usbser_state_t *);
static int      usbser_attach_dev(usbser_state_t *);
static void     usbser_detach_dev(usbser_state_t *);
static int      usbser_attach_ports(usbser_state_t *);
static int      usbser_create_port_minor_nodes(usbser_state_t *, int);
static void     usbser_detach_ports(usbser_state_t *);
static int      usbser_create_taskq(usbser_state_t *);
static void     usbser_destroy_taskq(usbser_state_t *);
static void     usbser_set_dev_state_init(usbser_state_t *);

/* hotplugging and power management */
static int      usbser_disconnect_cb(dev_info_t *);
static int      usbser_reconnect_cb(dev_info_t *);
static void     usbser_disconnect_ports(usbser_state_t *);
static int      usbser_cpr_suspend(dev_info_t *);
static int      usbser_suspend_ports(usbser_state_t *);
static void     usbser_cpr_resume(dev_info_t *);
static int      usbser_restore_device_state(usbser_state_t *);
static void     usbser_restore_ports_state(usbser_state_t *);

/* STREAMS subroutines */
static int      usbser_open_setup(queue_t *, usbser_port_t *, int, int,
                cred_t *);
static int      usbser_open_init(usbser_port_t *, int);
static void     usbser_check_port_props(usbser_port_t *);
static void     usbser_open_fini(usbser_port_t *);
static int      usbser_open_line_setup(usbser_port_t *, int, int);
static int      usbser_open_carrier_check(usbser_port_t *, int, int);
static void     usbser_open_queues_init(usbser_port_t *, queue_t *);
static void     usbser_open_queues_fini(usbser_port_t *);
static void     usbser_close_drain(usbser_port_t *);
static void     usbser_close_cancel_break(usbser_port_t *);
static void     usbser_close_hangup(usbser_port_t *);
static void     usbser_close_cleanup(usbser_port_t *);

/* threads */
static void     usbser_thr_dispatch(usbser_thread_t *);
static void     usbser_thr_cancel(usbser_thread_t *);
static void     usbser_thr_wake(usbser_thread_t *);
static void     usbser_wq_thread(void *);
static void     usbser_rq_thread(void *);

/* DSD callbacks */
static void     usbser_tx_cb(caddr_t);
static void     usbser_rx_cb(caddr_t);
static void     usbser_rx_massage_data(usbser_port_t *, mblk_t *);
static void     usbser_rx_massage_mbreak(usbser_port_t *, mblk_t *);
static void     usbser_rx_cb_put(usbser_port_t *, queue_t *, queue_t *,
                mblk_t *);
static void     usbser_status_cb(caddr_t);
static void     usbser_status_proc_cb(usbser_port_t *);

/* serial support */
static void     usbser_wmsg(usbser_port_t *);
static int      usbser_data(usbser_port_t *, mblk_t *);
static int      usbser_ioctl(usbser_port_t *, mblk_t *);
static void     usbser_iocdata(usbser_port_t *, mblk_t *);
static void     usbser_stop(usbser_port_t *, mblk_t *);
static void     usbser_start(usbser_port_t *, mblk_t *);
static void     usbser_stopi(usbser_port_t *, mblk_t *);
static void     usbser_starti(usbser_port_t *, mblk_t *);
static void     usbser_flush(usbser_port_t *, mblk_t *);
static void     usbser_break(usbser_port_t *, mblk_t *);
static void     usbser_delay(usbser_port_t *, mblk_t *);
static void     usbser_restart(void *);
static int      usbser_port_program(usbser_port_t *);
static void     usbser_inbound_flow_ctl(usbser_port_t *);

/* misc */
static int      usbser_dev_is_online(usbser_state_t *);
static void     usbser_serialize_port_act(usbser_port_t *, int);
static void     usbser_release_port_act(usbser_port_t *, int);
#ifdef DEBUG
static char     *usbser_msgtype2str(int);
static char     *usbser_ioctl2str(int);
#endif

/* USBA events */
usb_event_t usbser_usb_events = {
        usbser_disconnect_cb,   /* disconnect */
        usbser_reconnect_cb,    /* reconnect */
        NULL,                   /* pre-suspend */
        NULL,                   /* pre-resume */
};

/* debug support */
uint_t   usbser_errlevel = USB_LOG_L4;
uint_t   usbser_errmask = DPRINT_MASK_ALL;
uint_t   usbser_instance_debug = (uint_t)-1;

/* usb serial console */
static struct usbser_state *usbser_list;
static kmutex_t usbser_lock;
static int usbser_console_abort;
static usb_console_info_t console_input, console_output;
static uchar_t *console_input_buf;
static uchar_t *console_input_start, *console_input_end;

_NOTE(SCHEME_PROTECTS_DATA("unshared", usbser_console_abort))
_NOTE(SCHEME_PROTECTS_DATA("unshared", console_input))
_NOTE(SCHEME_PROTECTS_DATA("unshared", console_output))
_NOTE(SCHEME_PROTECTS_DATA("unshared", console_input_start))
_NOTE(SCHEME_PROTECTS_DATA("unshared", console_input_end))

static void usbser_putchar(cons_polledio_arg_t, uchar_t);
static int usbser_getchar(cons_polledio_arg_t);
static boolean_t usbser_ischar(cons_polledio_arg_t);
static void usbser_polledio_enter(cons_polledio_arg_t);
static void usbser_polledio_exit(cons_polledio_arg_t);
static int usbser_polledio_init(usbser_port_t *);
static void usbser_polledio_fini(usbser_port_t *);

static struct cons_polledio usbser_polledio = {
        CONSPOLLEDIO_V1,
        NULL,   /* to be set later */
        usbser_putchar,
        usbser_getchar,
        usbser_ischar,
        usbser_polledio_enter,
        usbser_polledio_exit
};

/* various statistics. TODO: replace with kstats */
static int usbser_st_tx_data_loss = 0;
static int usbser_st_rx_data_loss = 0;
static int usbser_st_put_stopi = 0;
static int usbser_st_mstop = 0;
static int usbser_st_mstart = 0;
static int usbser_st_mstopi = 0;
static int usbser_st_mstarti = 0;
static int usbser_st_rsrv = 0;
_NOTE(SCHEME_PROTECTS_DATA("monotonic stats", usbser_st_{
        tx_data_loss rx_data_loss put_stopi mstop mstart mstopi mstarti rsrv}))
_NOTE(SCHEME_PROTECTS_DATA("unshared", usb_bulk_req_t))
_NOTE(SCHEME_PROTECTS_DATA("unshared", usb_intr_req_t))

/* taskq parameter */
extern pri_t minclsyspri;

/*
 * tell warlock not to worry about STREAMS structures
 */
_NOTE(SCHEME_PROTECTS_DATA("unique per call", iocblk datab msgb queue copyreq))

/*
 * modload support
 */
extern struct mod_ops mod_miscops;

static struct modlmisc modlmisc = {
        &mod_miscops,   /* Type of module */
        "USB generic serial module"
};

static struct modlinkage modlinkage = {
        MODREV_1, (void *)&modlmisc, NULL
};


#define RSEQ(f1, f2) RSEQE(f1, usbser_rseq_do_cb, f2, NULL)


/*
 * loadable module entry points
 * ----------------------------
 */

int
_init(void)
{
        int err;

        mutex_init(&usbser_lock, NULL, MUTEX_DRIVER, (void *)NULL);

        if ((err = mod_install(&modlinkage)) != 0)
                mutex_destroy(&usbser_lock);

        return (err);
}


int
_fini(void)
{
        int err;

        if ((err = mod_remove(&modlinkage)) != 0)
                return (err);

        mutex_destroy(&usbser_lock);

        return (0);
}


int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}


/*
 * soft state size
 */
int
usbser_soft_state_size()
{
        return (sizeof (usbser_state_t));
}


/*
 * autoconfiguration entry points
 * ------------------------------
 */

/*ARGSUSED*/
int
usbser_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
    void **result, void *statep)
{
        int             instance;
        int             ret = DDI_FAILURE;
        usbser_state_t  *usbserp;

        instance = USBSER_MINOR2INST(getminor((dev_t)arg));

        switch (infocmd) {
        case DDI_INFO_DEVT2DEVINFO:
                *result = NULL;
                usbserp = ddi_get_soft_state(statep, instance);
                if (usbserp != NULL) {
                        *result = usbserp->us_dip;
                        if (*result != NULL) {
                                ret = DDI_SUCCESS;
                        }
                }

                break;
        case DDI_INFO_DEVT2INSTANCE:
                *result = (void *)(uintptr_t)instance;
                ret = DDI_SUCCESS;

                break;
        default:
                break;
        }

        return (ret);
}

/*
 * device attach
 */
static rseq_t rseq_att[] = {
        RSEQ(NULL,                      usbser_free_soft_state),
        RSEQ(usbser_init_soft_state,    usbser_fini_soft_state),
        RSEQ(usbser_attach_dev,         usbser_detach_dev),
        RSEQ(usbser_attach_ports,       usbser_detach_ports),
        RSEQ(usbser_create_taskq,       usbser_destroy_taskq),
        RSEQ(NULL,                      usbser_set_dev_state_init)
};

static void
usbser_insert(struct usbser_state *usp)
{
        struct usbser_state *tmp;

        mutex_enter(&usbser_lock);
        tmp = usbser_list;
        if (tmp == NULL)
                usbser_list = usp;
        else {
                while (tmp->us_next)
                        tmp = tmp->us_next;
                tmp->us_next = usp;
        }
        mutex_exit(&usbser_lock);
}

static void
usbser_remove(struct usbser_state *usp)
{
        struct usbser_state *tmp, *prev = NULL;

        mutex_enter(&usbser_lock);
        tmp = usbser_list;
        while (tmp != usp) {
                prev = tmp;
                tmp = tmp->us_next;
        }
        ASSERT(tmp == usp);     /* must exist, else attach/detach wrong */
        if (prev)
                prev->us_next = usp->us_next;
        else
                usbser_list = usp->us_next;
        usp->us_next = NULL;
        mutex_exit(&usbser_lock);
}

/*
 * Return the first serial device, with dip held. This is called
 * from the console subsystem to place console on usb serial device.
 */
dev_info_t *
usbser_first_device(void)
{
        dev_info_t *dip = NULL;

        mutex_enter(&usbser_lock);
        if (usbser_list) {
                dip = usbser_list->us_dip;
                ndi_hold_devi(dip);
        }
        mutex_exit(&usbser_lock);

        return (dip);
}

int
usbser_attach(dev_info_t *dip, ddi_attach_cmd_t cmd,
    void *statep, ds_ops_t *ds_ops)
{
        int             instance;
        usbser_state_t  *usp;

        instance = ddi_get_instance(dip);

        switch (cmd) {
        case DDI_ATTACH:

                break;
        case DDI_RESUME:
                usbser_cpr_resume(dip);

                return (DDI_SUCCESS);
        default:

                return (DDI_FAILURE);
        }

        /* allocate and get soft state */
        if (ddi_soft_state_zalloc(statep, instance) != DDI_SUCCESS) {

                return (DDI_FAILURE);
        }
        if ((usp = ddi_get_soft_state(statep, instance)) == NULL) {
                ddi_soft_state_free(statep, instance);

                return (DDI_FAILURE);
        }

        usp->us_statep = statep;
        usp->us_dip = dip;
        usp->us_instance = instance;
        usp->us_ds_ops = ds_ops;

        if (rseq_do(rseq_att, NELEM(rseq_att), (uintptr_t)usp, 0) == RSEQ_OK) {
                ddi_report_dev(dip);
                usbser_insert(usp);

                return (DDI_SUCCESS);
        } else {

                return (DDI_FAILURE);
        }
}

/*
 * device detach
 */
int
usbser_detach(dev_info_t *dip, ddi_detach_cmd_t cmd, void *statep)
{
        int             instance = ddi_get_instance(dip);
        usbser_state_t  *usp;
        int             rval;

        usp = ddi_get_soft_state(statep, instance);

        switch (cmd) {
        case DDI_DETACH:
                USB_DPRINTF_L4(DPRINT_DETACH, usp->us_lh, "usbser_detach");
                usbser_remove(usp);
                (void) rseq_undo(rseq_att, NELEM(rseq_att), (uintptr_t)usp, 0);
                USB_DPRINTF_L4(DPRINT_DETACH, NULL,
                    "usbser_detach.%d: end", instance);

                return (DDI_SUCCESS);
        case DDI_SUSPEND:
                rval = usbser_cpr_suspend(dip);

                return ((rval == USB_SUCCESS)? DDI_SUCCESS : DDI_FAILURE);
        default:

                return (DDI_FAILURE);
        }
}

/*
 * STREAMS entry points
 * --------------------
 *
 *
 * port open
 */
/*ARGSUSED*/
int
usbser_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr,
    void *statep)
{
        usbser_state_t  *usp;
        usbser_port_t   *pp;
        int             minor = getminor(*dev);
        int             instance;
        uint_t          port_num;
        int             rval;

        instance = USBSER_MINOR2INST(minor);
        if (instance < 0) {

                return (ENXIO);
        }

        usp = ddi_get_soft_state(statep, instance);
        if (usp == NULL) {

                return (ENXIO);
        }

        /* don't allow to open disconnected device */
        mutex_enter(&usp->us_mutex);
        if (usp->us_dev_state == USB_DEV_DISCONNECTED) {
                mutex_exit(&usp->us_mutex);

                return (ENXIO);
        }
        mutex_exit(&usp->us_mutex);

        /* get port soft state */
        port_num = USBSER_MINOR2PORT(minor);
        if (port_num >= usp->us_port_cnt) {

                return (ENXIO);
        }
        pp = &usp->us_ports[port_num];

        /* set up everything for open */
        rval = usbser_open_setup(rq, pp, minor, flag, cr);

        USB_DPRINTF_L4(DPRINT_OPEN, pp->port_lh, "usbser_open: rval=%d", rval);

        return (rval);
}


/*
 * port close
 *
 * some things driver should do when the last app closes the line:
 *
 *      drain data;
 *      cancel break/delay;
 *      hangup line (if necessary);
 *      DSD close;
 *      cleanup soft state;
 */
/*ARGSUSED*/
int
usbser_close(queue_t *rq, int flag, cred_t *cr)
{
        usbser_port_t   *pp = (usbser_port_t *)rq->q_ptr;
        int             online;

        if (pp == NULL) {

                return (ENXIO);
        }

        online = usbser_dev_is_online(pp->port_usp);

        /*
         * in the closing state new activities will not be initiated
         */
        mutex_enter(&pp->port_mutex);
        pp->port_state = USBSER_PORT_CLOSING;

        if (online) {
                /* drain the data */
                usbser_close_drain(pp);
        }

        /* stop break/delay */
        usbser_close_cancel_break(pp);

        if (online) {
                /* hangup line */
                usbser_close_hangup(pp);
        }

        /*
         * close DSD, cleanup state and transition to 'closed' state
         */
        usbser_close_cleanup(pp);
        mutex_exit(&pp->port_mutex);

        USB_DPRINTF_L4(DPRINT_CLOSE, pp->port_lh, "usbser_close: end");

        return (0);
}


/*
 * read side service routine: send as much as possible messages upstream
 * and if there is still place on the queue, enable receive (if not already)
 */
int
usbser_rsrv(queue_t *q)
{
        usbser_port_t   *pp = (usbser_port_t *)q->q_ptr;
        mblk_t          *mp;

        usbser_st_rsrv++;
        USB_DPRINTF_L4(DPRINT_RQ, pp->port_lh, "usbser_rsrv");

        while (canputnext(q) && (mp = getq(q))) {
                putnext(q, mp);
        }

        if (canputnext(q)) {
                mutex_enter(&pp->port_mutex);
                ASSERT(pp->port_state != USBSER_PORT_CLOSED);

                if (USBSER_PORT_ACCESS_OK(pp)) {
                        usbser_thr_wake(&pp->port_rq_thread);
                }
                mutex_exit(&pp->port_mutex);
        }

        return (0);
}


/*
 * wput: put message on the queue and wake wq thread
 */
int
usbser_wput(queue_t *q, mblk_t *mp)
{
        usbser_port_t   *pp = (usbser_port_t *)q->q_ptr;

        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_wput");

        mutex_enter(&pp->port_mutex);
        ASSERT(pp->port_state != USBSER_PORT_CLOSED);

        /* ignore new messages if port is already closing */
        if (pp->port_state == USBSER_PORT_CLOSING) {
                freemsg(mp);
        } else if (putq(q, mp)) {
                /*
                 * this counter represents amount of tx data on the wq.
                 * each time the data is passed to DSD for transmission,
                 * the counter is decremented accordingly
                 */
                pp->port_wq_data_cnt += msgdsize(mp);
        } else {
                usbser_st_tx_data_loss++;
        }
        mutex_exit(&pp->port_mutex);

        return (0);
}


/*
 * we need wsrv() routine to take advantage of STREAMS flow control:
 * without it the framework will consider we are always able to process msgs
 */
int
usbser_wsrv(queue_t *q)
{
        usbser_port_t   *pp = (usbser_port_t *)q->q_ptr;

        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_wsrv");

        mutex_enter(&pp->port_mutex);
        ASSERT(pp->port_state != USBSER_PORT_CLOSED);

        if (USBSER_PORT_ACCESS_OK(pp)) {
                usbser_thr_wake(&pp->port_wq_thread);
        }
        mutex_exit(&pp->port_mutex);

        return (0);
}


/*
 * power entry point
 */
int
usbser_power(dev_info_t *dip, int comp, int level)
{
        void            *statep;
        usbser_state_t  *usp;
        int             new_state;
        int             rval;

        statep = ddi_get_driver_private(dip);
        usp = ddi_get_soft_state(statep, ddi_get_instance(dip));

        USB_DPRINTF_L3(DPRINT_EVENTS, usp->us_lh,
            "usbser_power: dip=0x%p, comp=%d, level=%d",
            (void *)dip, comp, level);

        mutex_enter(&usp->us_mutex);
        new_state = usp->us_dev_state;
        mutex_exit(&usp->us_mutex);

        /* let DSD do the job */
        rval = USBSER_DS_USB_POWER(usp, comp, level, &new_state);

        /* stay in sync with DSD */
        mutex_enter(&usp->us_mutex);
        usp->us_dev_state = new_state;
        mutex_exit(&usp->us_mutex);

        return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE);
}


/*
 *
 * configuration entry point subroutines
 * -------------------------------------
 *
 * rseq callback
 */
static int
usbser_rseq_do_cb(rseq_t *rseq, int num, uintptr_t arg)
{
        usbser_state_t *usp = (usbser_state_t *)arg;
        int     rval = rseq[num].r_do.s_rval;
        char    *name = rseq[num].r_do.s_name;

        if (rval != DDI_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_ATTACH, usp->us_lh,
                    "do %s failed (%d)", name, rval);

                return (RSEQ_UNDO);
        } else {

                return (RSEQ_OK);
        }
}


/*
 * free soft state
 */
static int
usbser_free_soft_state(usbser_state_t *usp)
{
        ddi_soft_state_free(usp->us_statep, usp->us_instance);

        return (USB_SUCCESS);
}

/*
 * init instance soft state
 */
static int
usbser_init_soft_state(usbser_state_t *usp)
{
        usp->us_lh = usb_alloc_log_hdl(usp->us_dip, "usbs[*].",
            &usbser_errlevel, &usbser_errmask, &usbser_instance_debug,
            0);
        mutex_init(&usp->us_mutex, NULL, MUTEX_DRIVER, (void *)NULL);

        /* save state pointer for use in event callbacks */
        ddi_set_driver_private(usp->us_dip, usp->us_statep);

        usp->us_dev_state = USBSER_DEV_INIT;

        return (DDI_SUCCESS);
}

/*
 * fini instance soft state
 */
static int
usbser_fini_soft_state(usbser_state_t *usp)
{
        usb_free_log_hdl(usp->us_lh);
        mutex_destroy(&usp->us_mutex);
        ddi_set_driver_private(usp->us_dip, NULL);

        return (DDI_SUCCESS);
}

/*
 * attach entire device
 */
static int
usbser_attach_dev(usbser_state_t *usp)
{
        ds_attach_info_t ai;
        int             rval;

        usp->us_dev_state = USB_DEV_ONLINE;

        ai.ai_dip = usp->us_dip;
        ai.ai_usb_events = &usbser_usb_events;
        ai.ai_hdl = &usp->us_ds_hdl;
        ai.ai_port_cnt = &usp->us_port_cnt;

        rval = USBSER_DS_ATTACH(usp, &ai);

        if ((rval != USB_SUCCESS) || (usp->us_ds_hdl == NULL) ||
            (usp->us_port_cnt == 0)) {
                USB_DPRINTF_L4(DPRINT_ATTACH, usp->us_lh, "usbser_attach_dev: "
                    "failed %d %p %d", rval, usp->us_ds_hdl, usp->us_port_cnt);

                return (DDI_FAILURE);
        }

        USB_DPRINTF_L4(DPRINT_ATTACH, usp->us_lh,
            "usbser_attach_dev: port_cnt = %d", usp->us_port_cnt);

        return (DDI_SUCCESS);
}


/*
 * detach entire device
 */
static void
usbser_detach_dev(usbser_state_t *usp)
{
        USBSER_DS_DETACH(usp);
}


/*
 * attach each individual port
 */
static int
usbser_attach_ports(usbser_state_t *usp)
{
        int             i;
        usbser_port_t   *pp;
        ds_cb_t         ds_cb;

        /*
         * allocate port array
         */
        usp->us_ports = kmem_zalloc(usp->us_port_cnt *
            sizeof (usbser_port_t), KM_SLEEP);

        /* callback handlers */
        ds_cb.cb_tx = usbser_tx_cb;
        ds_cb.cb_rx = usbser_rx_cb;
        ds_cb.cb_status = usbser_status_cb;

        /*
         * initialize each port
         */
        for (i = 0; i < usp->us_port_cnt; i++) {
                pp = &usp->us_ports[i];

                /*
                 * initialize data
                 */
                pp->port_num = i;
                pp->port_usp = usp;
                pp->port_ds_ops = usp->us_ds_ops;
                pp->port_ds_hdl = usp->us_ds_hdl;

                /* allocate log handle */
                (void) sprintf(pp->port_lh_name, "usbs[%d].", i);
                pp->port_lh = usb_alloc_log_hdl(usp->us_dip,
                    pp->port_lh_name, &usbser_errlevel, &usbser_errmask,
                    &usbser_instance_debug, 0);

                mutex_init(&pp->port_mutex, NULL, MUTEX_DRIVER, (void *)NULL);
                cv_init(&pp->port_state_cv, NULL, CV_DEFAULT, NULL);
                cv_init(&pp->port_act_cv, NULL, CV_DEFAULT, NULL);
                cv_init(&pp->port_car_cv, NULL, CV_DEFAULT, NULL);

                /*
                 * init threads
                 */
                pp->port_wq_thread.thr_port = pp;
                pp->port_wq_thread.thr_func = usbser_wq_thread;
                pp->port_wq_thread.thr_arg = (void *)&pp->port_wq_thread;
                cv_init(&pp->port_wq_thread.thr_cv, NULL, CV_DEFAULT, NULL);

                pp->port_rq_thread.thr_port = pp;
                pp->port_rq_thread.thr_func = usbser_rq_thread;
                pp->port_rq_thread.thr_arg = (void *)&pp->port_rq_thread;
                cv_init(&pp->port_rq_thread.thr_cv, NULL, CV_DEFAULT, NULL);

                /*
                 * register callbacks
                 */
                ds_cb.cb_arg = (caddr_t)pp;
                USBSER_DS_REGISTER_CB(usp, i, &ds_cb);

                pp->port_state = USBSER_PORT_CLOSED;

                if (usbser_create_port_minor_nodes(usp, i) != USB_SUCCESS) {
                        usbser_detach_ports(usp);

                        return (DDI_FAILURE);
                }
        }

        return (DDI_SUCCESS);
}


/*
 * create a pair of minor nodes for the port
 */
static int
usbser_create_port_minor_nodes(usbser_state_t *usp, int port_num)
{
        int     instance = usp->us_instance;
        minor_t minor;
        char    name[16];

        /*
         * tty node
         */
        (void) sprintf(name, "%d", port_num);
        minor = USBSER_MAKEMINOR(instance, port_num, 0);

        if (ddi_create_minor_node(usp->us_dip, name,
            S_IFCHR, minor, DDI_NT_SERIAL, 0) != DDI_SUCCESS) {

                return (USB_FAILURE);
        }

        /*
         * dial-out node
         */
        (void) sprintf(name, "%d,cu", port_num);
        minor = USBSER_MAKEMINOR(instance, port_num, OUTLINE);

        if (ddi_create_minor_node(usp->us_dip, name,
            S_IFCHR, minor, DDI_NT_SERIAL_DO, 0) != DDI_SUCCESS) {

                return (USB_FAILURE);
        }

        return (USB_SUCCESS);
}


/*
 * detach each port individually
 */
static void
usbser_detach_ports(usbser_state_t *usp)
{
        int             i;
        int             sz;
        usbser_port_t   *pp;

        /*
         * remove all minor nodes
         */
        ddi_remove_minor_node(usp->us_dip, NULL);

        for (i = 0; i < usp->us_port_cnt; i++) {
                pp = &usp->us_ports[i];

                if (pp->port_state != USBSER_PORT_CLOSED) {
                        ASSERT(pp->port_state == USBSER_PORT_NOT_INIT);

                        continue;
                }

                USBSER_DS_UNREGISTER_CB(usp, i);

                mutex_destroy(&pp->port_mutex);
                cv_destroy(&pp->port_state_cv);
                cv_destroy(&pp->port_act_cv);
                cv_destroy(&pp->port_car_cv);

                cv_destroy(&pp->port_wq_thread.thr_cv);
                cv_destroy(&pp->port_rq_thread.thr_cv);

                usb_free_log_hdl(pp->port_lh);
        }

        /*
         * free memory
         */
        sz = usp->us_port_cnt * sizeof (usbser_port_t);
        kmem_free(usp->us_ports, sz);
        usp->us_ports = NULL;
}


/*
 * create a taskq with two threads per port (read and write sides)
 */
static int
usbser_create_taskq(usbser_state_t *usp)
{
        int     nthr = usp->us_port_cnt * 2;

        usp->us_taskq = ddi_taskq_create(usp->us_dip, "usbser_taskq",
            nthr, TASKQ_DEFAULTPRI, 0);

        return ((usp->us_taskq == NULL) ? DDI_FAILURE : DDI_SUCCESS);
}


static void
usbser_destroy_taskq(usbser_state_t *usp)
{
        ddi_taskq_destroy(usp->us_taskq);
}


static void
usbser_set_dev_state_init(usbser_state_t *usp)
{
        mutex_enter(&usp->us_mutex);
        usp->us_dev_state = USBSER_DEV_INIT;
        mutex_exit(&usp->us_mutex);
}

/*
 * hotplugging and power management
 * ---------------------------------
 *
 * disconnect event callback
 */
/*ARGSUSED*/
static int
usbser_disconnect_cb(dev_info_t *dip)
{
        void            *statep;
        usbser_state_t  *usp;

        statep = ddi_get_driver_private(dip);
        usp = ddi_get_soft_state(statep, ddi_get_instance(dip));

        USB_DPRINTF_L3(DPRINT_EVENTS, usp->us_lh,
            "usbser_disconnect_cb: dip=%p", (void *)dip);

        mutex_enter(&usp->us_mutex);
        switch (usp->us_dev_state) {
        case USB_DEV_ONLINE:
        case USB_DEV_PWRED_DOWN:
                /* prevent further activity */
                usp->us_dev_state = USB_DEV_DISCONNECTED;
                mutex_exit(&usp->us_mutex);

                /* see if any of the ports are open and do necessary handling */
                usbser_disconnect_ports(usp);

                /* call DSD to do any necessary work */
                if (USBSER_DS_DISCONNECT(usp) != USB_DEV_DISCONNECTED) {
                        USB_DPRINTF_L2(DPRINT_EVENTS, usp->us_lh,
                            "usbser_disconnect_cb: ds_disconnect failed");
                }

                break;
        case USB_DEV_SUSPENDED:
                /* we remain suspended */
        default:
                mutex_exit(&usp->us_mutex);

                break;
        }

        return (USB_SUCCESS);
}


/*
 * reconnect event callback
 */
/*ARGSUSED*/
static int
usbser_reconnect_cb(dev_info_t *dip)
{
        void            *statep;
        usbser_state_t  *usp;

        statep = ddi_get_driver_private(dip);
        usp = ddi_get_soft_state(statep, ddi_get_instance(dip));

        USB_DPRINTF_L3(DPRINT_EVENTS, usp->us_lh,
            "usbser_reconnect_cb: dip=%p", (void *)dip);

        (void) usbser_restore_device_state(usp);

        return (USB_SUCCESS);
}


/*
 * if any of the ports is open during disconnect,
 * send M_HANGUP message upstream and log a warning
 */
static void
usbser_disconnect_ports(usbser_state_t *usp)
{
        usbser_port_t   *pp;
        queue_t         *rq;
        int             complain = 0;
        int             hangup = 0;
        timeout_id_t    delay_id = 0;
        int             i;

        if (usp->us_ports == NULL) {
                return;
        }

        for (i = 0; i < usp->us_port_cnt; i++) {
                pp = &usp->us_ports[i];

                mutex_enter(&pp->port_mutex);
                if (pp->port_state == USBSER_PORT_OPEN ||
                    USBSER_IS_OPENING(pp) ||
                    pp->port_state == USBSER_PORT_CLOSING) {
                        complain = 1;
                }

                if (pp->port_state == USBSER_PORT_OPEN) {
                        rq = pp->port_ttycommon.t_readq;

                        /*
                         * hangup the stream; will send actual
                         * M_HANGUP message after releasing mutex
                         */
                        pp->port_flags |= USBSER_FL_HUNGUP;
                        hangup = 1;

                        /*
                         * cancel all activities
                         */
                        usbser_release_port_act(pp, USBSER_ACT_ALL);

                        delay_id = pp->port_delay_id;
                        pp->port_delay_id = 0;

                        /* mark disconnected */
                        pp->port_state = USBSER_PORT_DISCONNECTED;
                        cv_broadcast(&pp->port_state_cv);
                }
                mutex_exit(&pp->port_mutex);

                if (hangup) {
                        (void) putnextctl(rq, M_HANGUP);
                        hangup = 0;
                }

                /*
                 * we couldn't untimeout while holding the mutex - do it now
                 */
                if (delay_id) {
                        (void) untimeout(delay_id);
                        delay_id = 0;
                }
        }

        /*
         * complain about disconnecting device while open
         */
        if (complain) {
                USB_DPRINTF_L0(DPRINT_EVENTS, usp->us_lh, "device was "
                    "disconnected while open. Data may have been lost");
        }
}


/*
 * do CPR suspend
 *
 * We use a trivial CPR strategy - fail if any of the device's ports are open.
 * The problem with more sophisticated strategies is that each open port uses
 * two threads that sit in the loop until the port is closed, while CPR has to
 * stop all kernel threads to succeed. Stopping port threads is a rather
 * intrusive and delicate procedure; I leave it as an RFE for now.
 *
 */
static int
usbser_cpr_suspend(dev_info_t *dip)
{
        void            *statep;
        usbser_state_t  *usp;
        int             new_state;
        int             rval;

        statep = ddi_get_driver_private(dip);
        usp = ddi_get_soft_state(statep, ddi_get_instance(dip));

        USB_DPRINTF_L4(DPRINT_EVENTS, usp->us_lh, "usbser_cpr_suspend");

        /* suspend each port first */
        if (usbser_suspend_ports(usp) != USB_SUCCESS) {
                USB_DPRINTF_L3(DPRINT_EVENTS, usp->us_lh,
                    "usbser_cpr_suspend: GSD failure");

                return (USB_FAILURE);
        }

        new_state = USBSER_DS_SUSPEND(usp);     /* let DSD do its part */

        mutex_enter(&usp->us_mutex);
        if (new_state == USB_DEV_SUSPENDED) {
                rval = USB_SUCCESS;
        } else {
                ASSERT(new_state == USB_DEV_ONLINE);
                rval = USB_FAILURE;
        }
        usp->us_dev_state = new_state;
        mutex_exit(&usp->us_mutex);

        return (rval);
}


static int
usbser_suspend_ports(usbser_state_t *usp)
{
        usbser_port_t   *pp;
        int             i;

        for (i = 0; i < usp->us_port_cnt; i++) {
                pp = &usp->us_ports[i];

                mutex_enter(&pp->port_mutex);
                if (pp->port_state != USBSER_PORT_CLOSED) {
                        mutex_exit(&pp->port_mutex);

                        return (USB_FAILURE);
                }
                mutex_exit(&pp->port_mutex);
        }

        return (USB_SUCCESS);
}


/*
 * do CPR resume
 *
 * DSD will return USB_DEV_ONLINE in case of success
 */
static void
usbser_cpr_resume(dev_info_t *dip)
{
        void            *statep;
        usbser_state_t  *usp;

        statep = ddi_get_driver_private(dip);
        usp = ddi_get_soft_state(statep, ddi_get_instance(dip));

        USB_DPRINTF_L3(DPRINT_EVENTS, usp->us_lh, "usbser_cpr_resume");

        (void) usbser_restore_device_state(usp);
}


/*
 * restore device state after CPR resume or reconnect
 */
static int
usbser_restore_device_state(usbser_state_t *usp)
{
        int     new_state, current_state;

        /* needed as power up state of dev is "unknown" to system */
        (void) pm_busy_component(usp->us_dip, 0);
        (void) pm_raise_power(usp->us_dip, 0, USB_DEV_OS_FULL_PWR);

        mutex_enter(&usp->us_mutex);
        current_state = usp->us_dev_state;
        mutex_exit(&usp->us_mutex);

        ASSERT((current_state == USB_DEV_DISCONNECTED) ||
            (current_state == USB_DEV_SUSPENDED));

        /*
         * call DSD to perform device-specific work
         */
        if (current_state == USB_DEV_DISCONNECTED) {
                new_state = USBSER_DS_RECONNECT(usp);
        } else {
                new_state = USBSER_DS_RESUME(usp);
        }

        mutex_enter(&usp->us_mutex);
        usp->us_dev_state = new_state;
        mutex_exit(&usp->us_mutex);

        if (new_state == USB_DEV_ONLINE) {
                /*
                 * restore ports state
                 */
                usbser_restore_ports_state(usp);
        }

        (void) pm_idle_component(usp->us_dip, 0);

        return (USB_SUCCESS);
}


/*
 * restore ports state after device reconnect/resume
 */
static void
usbser_restore_ports_state(usbser_state_t *usp)
{
        usbser_port_t   *pp;
        queue_t         *rq;
        int             i;

        for (i = 0; i < usp->us_port_cnt; i++) {
                pp = &usp->us_ports[i];

                mutex_enter(&pp->port_mutex);
                /*
                 * only care about ports that are open
                 */
                if ((pp->port_state != USBSER_PORT_SUSPENDED) &&
                    (pp->port_state != USBSER_PORT_DISCONNECTED)) {
                        mutex_exit(&pp->port_mutex);

                        continue;
                }

                pp->port_state = USBSER_PORT_OPEN;

                /*
                 * if the stream was hung up during disconnect, restore it
                 */
                if (pp->port_flags & USBSER_FL_HUNGUP) {
                        pp->port_flags &= ~USBSER_FL_HUNGUP;
                        rq = pp->port_ttycommon.t_readq;

                        mutex_exit(&pp->port_mutex);
                        (void) putnextctl(rq, M_UNHANGUP);
                        mutex_enter(&pp->port_mutex);
                }

                /*
                 * restore serial parameters
                 */
                (void) usbser_port_program(pp);

                /*
                 * wake anything that might be sleeping
                 */
                cv_broadcast(&pp->port_state_cv);
                cv_broadcast(&pp->port_act_cv);
                usbser_thr_wake(&pp->port_wq_thread);
                usbser_thr_wake(&pp->port_rq_thread);
                mutex_exit(&pp->port_mutex);
        }
}


/*
 * STREAMS subroutines
 * -------------------
 *
 *
 * port open state machine
 *
 * here's a list of things that the driver has to do while open;
 * because device can be opened any number of times,
 * initial open has additional responsibilities:
 *
 *      if (initial_open) {
 *              initialize soft state;  \
 *              DSD open;               - see usbser_open_init()
 *              dispatch threads;       /
 *      }
 *      raise DTR;
 *      wait for carrier (if necessary);
 *
 * we should also take into consideration that two threads can try to open
 * the same physical port simultaneously (/dev/term/N and /dev/cua/N).
 *
 * return values:
 *      0       - success;
 *      >0      - fail with this error code;
 */
static int
usbser_open_setup(queue_t *rq, usbser_port_t *pp, int minor, int flag,
    cred_t *cr)
{
        int     rval = USBSER_CONTINUE;

        mutex_enter(&pp->port_mutex);
        /*
         * refer to port state diagram in the header file
         */
loop:
        switch (pp->port_state) {
        case USBSER_PORT_CLOSED:
                /*
                 * initial open
                 */
                rval = usbser_open_init(pp, minor);

                break;
        case USBSER_PORT_OPENING_TTY:
                /*
                 * dial-out thread can overtake the port
                 * if tty open thread is sleeping waiting for carrier
                 */
                if ((minor & OUTLINE) && (pp->port_flags & USBSER_FL_WOPEN)) {
                        pp->port_state = USBSER_PORT_OPENING_OUT;

                        USB_DPRINTF_L3(DPRINT_OPEN, pp->port_lh,
                            "usbser_open_state: overtake");
                }

                /* FALLTHRU */
        case USBSER_PORT_OPENING_OUT:
                /*
                 * if no other open in progress, setup the line
                 */
                if (USBSER_NO_OTHER_OPEN(pp, minor)) {
                        rval = usbser_open_line_setup(pp, minor, flag);

                        break;
                }

                /* FALLTHRU */
        case USBSER_PORT_CLOSING:
                /*
                 * wait until close active phase ends
                 */
                if (cv_wait_sig(&pp->port_state_cv, &pp->port_mutex) == 0) {
                        rval = EINTR;
                }

                break;
        case USBSER_PORT_OPEN:
                if ((pp->port_ttycommon.t_flags & TS_XCLUDE) &&
                    secpolicy_excl_open(cr) != 0) {
                        /*
                         * exclusive use
                         */
                        rval = EBUSY;
                } else if (USBSER_OPEN_IN_OTHER_MODE(pp, minor)) {
                        /*
                         * tty and dial-out modes are mutually exclusive
                         */
                        rval = EBUSY;
                } else {
                        /*
                         * port is being re-open in the same mode
                         */
                        rval = usbser_open_line_setup(pp, minor, flag);
                }

                break;
        default:
                rval = ENXIO;

                break;
        }

        if (rval == USBSER_CONTINUE) {

                goto loop;
        }

        /*
         * initial open requires additional handling
         */
        if (USBSER_IS_OPENING(pp)) {
                if (rval == USBSER_COMPLETE) {
                        if (pp->port_state == USBSER_PORT_OPENING_OUT) {
                                pp->port_flags |= USBSER_FL_OUT;
                        }
                        pp->port_state = USBSER_PORT_OPEN;
                        cv_broadcast(&pp->port_state_cv);

                        usbser_open_queues_init(pp, rq);
                } else {
                        usbser_open_fini(pp);
                }
        }
        mutex_exit(&pp->port_mutex);

        return (rval);
}


/*
 * initialize the port when opened for the first time
 */
static int
usbser_open_init(usbser_port_t *pp, int minor)
{
        usbser_state_t  *usp = pp->port_usp;
        tty_common_t    *tp = &pp->port_ttycommon;
        int             rval = ENXIO;

        ASSERT(pp->port_state == USBSER_PORT_CLOSED);

        /*
         * init state
         */
        pp->port_act = 0;
        pp->port_flags &= USBSER_FL_PRESERVE;
        pp->port_flowc = '\0';
        pp->port_wq_data_cnt = 0;

        if (minor & OUTLINE) {
                pp->port_state = USBSER_PORT_OPENING_OUT;
        } else {
                pp->port_state = USBSER_PORT_OPENING_TTY;
        }

        /*
         * init termios settings
         */
        tp->t_iflag = 0;
        tp->t_iocpending = NULL;
        tp->t_size.ws_row = tp->t_size.ws_col = 0;
        tp->t_size.ws_xpixel = tp->t_size.ws_ypixel = 0;
        tp->t_startc = CSTART;
        tp->t_stopc = CSTOP;

        usbser_check_port_props(pp);

        /*
         * dispatch wq and rq threads:
         * although queues are not enabled at this point,
         * we will need wq to run status processing callback
         */
        usbser_thr_dispatch(&pp->port_wq_thread);
        usbser_thr_dispatch(&pp->port_rq_thread);

        /*
         * open DSD port
         */
        mutex_exit(&pp->port_mutex);
        rval = USBSER_DS_OPEN_PORT(usp, pp->port_num);
        mutex_enter(&pp->port_mutex);

        if (rval != USB_SUCCESS) {

                return (ENXIO);
        }
        pp->port_flags |= USBSER_FL_DSD_OPEN;

        /*
         * program port with default parameters
         */
        if ((rval = usbser_port_program(pp)) != 0) {

                return (ENXIO);
        }

        return (USBSER_CONTINUE);
}


/*
 * create a pair of minor nodes for the port
 */
static void
usbser_check_port_props(usbser_port_t *pp)
{
        dev_info_t      *dip = pp->port_usp->us_dip;
        tty_common_t    *tp = &pp->port_ttycommon;
        struct termios  *termiosp;
        uint_t          len;
        char            name[20];

        /*
         * take default modes from "ttymodes" property if it exists
         */
        if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, ddi_root_node(), 0,
            "ttymodes", (uchar_t **)&termiosp, &len) == DDI_PROP_SUCCESS) {

                if (len == sizeof (struct termios)) {
                        tp->t_cflag = termiosp->c_cflag;

                        if (termiosp->c_iflag & (IXON | IXANY)) {
                                tp->t_iflag =
                                    termiosp->c_iflag & (IXON | IXANY);
                                tp->t_startc = termiosp->c_cc[VSTART];
                                tp->t_stopc = termiosp->c_cc[VSTOP];
                        }
                }
                ddi_prop_free(termiosp);
        }

        /*
         * look for "ignore-cd" or "port-N-ignore-cd" property
         */
        (void) sprintf(name, "port-%d-ignore-cd", pp->port_num);
        if (ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "ignore-cd", 0) ||
            ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, name, 0)) {
                pp->port_flags |= USBSER_FL_IGNORE_CD;
        } else {
                pp->port_flags &= ~USBSER_FL_IGNORE_CD;
        }
}


/*
 * undo what was done in usbser_open_init()
 */
static void
usbser_open_fini(usbser_port_t *pp)
{
        uint_t          port_num = pp->port_num;
        usbser_state_t  *usp = pp->port_usp;

        /*
         * close DSD if it is open
         */
        if (pp->port_flags & USBSER_FL_DSD_OPEN) {
                mutex_exit(&pp->port_mutex);
                if (USBSER_DS_CLOSE_PORT(usp, port_num) != USB_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_CLOSE, pp->port_lh,
                            "usbser_open_fini: CLOSE_PORT fail");
                }
                mutex_enter(&pp->port_mutex);
        }

        /*
         * cancel threads
         */
        usbser_thr_cancel(&pp->port_wq_thread);
        usbser_thr_cancel(&pp->port_rq_thread);

        /*
         * unpdate soft state
         */
        pp->port_state = USBSER_PORT_CLOSED;
        cv_broadcast(&pp->port_state_cv);
        cv_broadcast(&pp->port_car_cv);
}


/*
 * setup serial line
 */
static int
usbser_open_line_setup(usbser_port_t *pp, int minor, int flag)
{
        int     rval;

        mutex_exit(&pp->port_mutex);
        /*
         * prevent opening a disconnected device
         */
        if (!usbser_dev_is_online(pp->port_usp)) {
                mutex_enter(&pp->port_mutex);

                return (ENXIO);
        }

        /* raise DTR on every open */
        (void) USBSER_DS_SET_MODEM_CTL(pp, TIOCM_DTR, TIOCM_DTR);

        mutex_enter(&pp->port_mutex);
        /*
         * check carrier
         */
        rval = usbser_open_carrier_check(pp, minor, flag);

        return (rval);
}


/*
 * check carrier and wait if needed
 */
static int
usbser_open_carrier_check(usbser_port_t *pp, int minor, int flag)
{
        tty_common_t    *tp = &pp->port_ttycommon;
        int             val = 0;
        int             rval;

        if (pp->port_flags & USBSER_FL_IGNORE_CD) {
                tp->t_flags |= TS_SOFTCAR;
        }

        /*
         * check carrier
         */
        if (tp->t_flags & TS_SOFTCAR) {
                pp->port_flags |= USBSER_FL_CARR_ON;
        } else if (USBSER_DS_GET_MODEM_CTL(pp, TIOCM_CD, &val) != USB_SUCCESS) {

                return (ENXIO);
        } else if (val & TIOCM_CD) {
                pp->port_flags |= USBSER_FL_CARR_ON;
        } else {
                pp->port_flags &= ~USBSER_FL_CARR_ON;
        }

        /*
         * don't block if 1) not allowed to, 2) this is a local device,
         * 3) opening in dial-out mode, or 4) carrier is already on
         */
        if ((flag & (FNDELAY | FNONBLOCK)) || (tp->t_cflag & CLOCAL) ||
            (minor & OUTLINE) || (pp->port_flags & USBSER_FL_CARR_ON)) {

                return (USBSER_COMPLETE);
        }

        /*
         * block until carrier up (only in tty mode)
         */
        USB_DPRINTF_L4(DPRINT_OPEN, pp->port_lh,
            "usbser_open_carrier_check: waiting for carrier...");

        pp->port_flags |= USBSER_FL_WOPEN;

        rval = cv_wait_sig(&pp->port_car_cv, &pp->port_mutex);

        pp->port_flags &= ~USBSER_FL_WOPEN;

        if (rval == 0) {
                /*
                 * interrupted with a signal
                 */
                return (EINTR);
        } else {
                /*
                 * try again
                 */
                return (USBSER_CONTINUE);
        }
}


/*
 * during open, setup queues and message processing
 */
static void
usbser_open_queues_init(usbser_port_t *pp, queue_t *rq)
{
        pp->port_ttycommon.t_readq = rq;
        pp->port_ttycommon.t_writeq = WR(rq);
        rq->q_ptr = WR(rq)->q_ptr = (caddr_t)pp;

        qprocson(rq);
}


/*
 * clean up queues and message processing
 */
static void
usbser_open_queues_fini(usbser_port_t *pp)
{
        queue_t *rq = pp->port_ttycommon.t_readq;

        mutex_exit(&pp->port_mutex);
        /*
         * clean up queues
         */
        qprocsoff(rq);

        /*
         * free unused messages
         */
        flushq(rq, FLUSHALL);
        flushq(WR(rq), FLUSHALL);

        rq->q_ptr = WR(rq)->q_ptr = NULL;
        ttycommon_close(&pp->port_ttycommon);
        mutex_enter(&pp->port_mutex);
}


/*
 * during close, wait until pending data is gone or the signal is sent
 */
static void
usbser_close_drain(usbser_port_t *pp)
{
        int     need_drain;
        clock_t until;
        int     rval = USB_SUCCESS;

        /*
         * port_wq_data_cnt indicates amount of data on the write queue,
         * which becomes zero when all data is submitted to DSD. But usbser
         * stays busy until it gets tx callback from DSD, signalling that
         * data has been sent over USB. To be continued in the next comment...
         */
        until = ddi_get_lbolt() +
            drv_usectohz(USBSER_WQ_DRAIN_TIMEOUT * 1000000);

        while ((pp->port_wq_data_cnt > 0) && USBSER_PORT_IS_BUSY(pp)) {
                if ((rval = cv_timedwait_sig(&pp->port_act_cv, &pp->port_mutex,
                    until)) <= 0) {

                        break;
                }
        }

        /* don't drain if timed out or received a signal */
        need_drain = (pp->port_wq_data_cnt == 0) || !USBSER_PORT_IS_BUSY(pp) ||
            (rval != USB_SUCCESS);

        mutex_exit(&pp->port_mutex);
        /*
         * Once the data reaches USB serial box, it may still be stored in its
         * internal output buffer (FIFO). We call DSD drain to ensure that all
         * the data is transmitted transmitted over the serial line.
         */
        if (need_drain) {
                rval = USBSER_DS_FIFO_DRAIN(pp, USBSER_TX_FIFO_DRAIN_TIMEOUT);
                if (rval != USB_SUCCESS) {
                        (void) USBSER_DS_FIFO_FLUSH(pp, DS_TX);
                }
        } else {
                (void) USBSER_DS_FIFO_FLUSH(pp, DS_TX);
        }
        mutex_enter(&pp->port_mutex);
}


/*
 * during close, cancel break/delay
 */
static void
usbser_close_cancel_break(usbser_port_t *pp)
{
        timeout_id_t    delay_id;

        if (pp->port_act & USBSER_ACT_BREAK) {
                delay_id = pp->port_delay_id;
                pp->port_delay_id = 0;

                mutex_exit(&pp->port_mutex);
                (void) untimeout(delay_id);
                (void) USBSER_DS_BREAK_CTL(pp, DS_OFF);
                mutex_enter(&pp->port_mutex);

                pp->port_act &= ~USBSER_ACT_BREAK;
        }
}


/*
 * during close, drop RTS/DTR if necessary
 */
static void
usbser_close_hangup(usbser_port_t *pp)
{
        /*
         * drop DTR and RTS if HUPCL is set
         */
        if (pp->port_ttycommon.t_cflag & HUPCL) {
                mutex_exit(&pp->port_mutex);
                (void) USBSER_DS_SET_MODEM_CTL(pp, TIOCM_RTS | TIOCM_DTR, 0);
                mutex_enter(&pp->port_mutex);
        }
}


/*
 * state cleanup during close
 */
static void
usbser_close_cleanup(usbser_port_t *pp)
{
        usbser_open_queues_fini(pp);

        usbser_open_fini(pp);
}


/*
 *
 * thread management
 * -----------------
 *
 *
 * dispatch a thread
 */
static void
usbser_thr_dispatch(usbser_thread_t *thr)
{
        usbser_port_t   *pp = thr->thr_port;
        usbser_state_t  *usp = pp->port_usp;
        int             rval;

        ASSERT(mutex_owned(&pp->port_mutex));
        ASSERT((thr->thr_flags & USBSER_THR_RUNNING) == 0);

        thr->thr_flags = USBSER_THR_RUNNING;

        rval = ddi_taskq_dispatch(usp->us_taskq, thr->thr_func, thr->thr_arg,
            DDI_SLEEP);
        ASSERT(rval == DDI_SUCCESS);
}


/*
 * cancel a thread
 */
static void
usbser_thr_cancel(usbser_thread_t *thr)
{
        usbser_port_t   *pp = thr->thr_port;

        ASSERT(mutex_owned(&pp->port_mutex));

        thr->thr_flags &= ~USBSER_THR_RUNNING;
        cv_signal(&thr->thr_cv);

        /* wait until the thread actually exits */
        do {
                cv_wait(&thr->thr_cv, &pp->port_mutex);

        } while ((thr->thr_flags & USBSER_THR_EXITED) == 0);
}


/*
 * wake thread
 */
static void
usbser_thr_wake(usbser_thread_t *thr)
{
        ASSERT(mutex_owned(&thr->thr_port->port_mutex));

        thr->thr_flags |= USBSER_THR_WAKE;
        cv_signal(&thr->thr_cv);
}


/*
 * thread handling write queue requests
 */
static void
usbser_wq_thread(void *arg)
{
        usbser_thread_t *thr = (usbser_thread_t *)arg;
        usbser_port_t   *pp = thr->thr_port;

        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_wq_thread: enter");

        mutex_enter(&pp->port_mutex);
        while (thr->thr_flags & USBSER_THR_RUNNING) {
                /*
                 * when woken, see what we should do
                 */
                if (thr->thr_flags & USBSER_THR_WAKE) {
                        thr->thr_flags &= ~USBSER_THR_WAKE;

                        /*
                         * status callback pending?
                         */
                        if (pp->port_flags & USBSER_FL_STATUS_CB) {
                                usbser_status_proc_cb(pp);
                        }

                        usbser_wmsg(pp);
                } else {
                        /*
                         * sleep until woken up to do some work, e.g:
                         * - new message arrives;
                         * - data transmit completes;
                         * - status callback pending;
                         * - wq thread is cancelled;
                         */
                        cv_wait(&thr->thr_cv, &pp->port_mutex);
                        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh,
                            "usbser_wq_thread: wakeup");
                }
        }
        thr->thr_flags |= USBSER_THR_EXITED;
        cv_signal(&thr->thr_cv);
        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_wq_thread: exit");
        mutex_exit(&pp->port_mutex);
}


/*
 * thread handling read queue requests
 */
static void
usbser_rq_thread(void *arg)
{
        usbser_thread_t *thr = (usbser_thread_t *)arg;
        usbser_port_t   *pp = thr->thr_port;

        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_rq_thread: enter");

        mutex_enter(&pp->port_mutex);
        while (thr->thr_flags & USBSER_THR_RUNNING) {
                /*
                 * read service routine will wake us when
                 * more space is available on the read queue
                 */
                if (thr->thr_flags & USBSER_THR_WAKE) {
                        thr->thr_flags &= ~USBSER_THR_WAKE;

                        /*
                         * don't process messages until queue is enabled
                         */
                        if (!pp->port_ttycommon.t_readq) {

                                continue;
                        }

                        /*
                         * check whether we need to resume receive
                         */
                        if (pp->port_flags & USBSER_FL_RX_STOPPED) {
                                pp->port_flowc = pp->port_ttycommon.t_startc;
                                usbser_inbound_flow_ctl(pp);
                        }

                        /*
                         * grab more data if available
                         */
                        mutex_exit(&pp->port_mutex);
                        usbser_rx_cb((caddr_t)pp);
                        mutex_enter(&pp->port_mutex);
                } else {
                        cv_wait(&thr->thr_cv, &pp->port_mutex);
                        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh,
                            "usbser_rq_thread: wakeup");
                }
        }
        thr->thr_flags |= USBSER_THR_EXITED;
        cv_signal(&thr->thr_cv);
        USB_DPRINTF_L4(DPRINT_RQ, pp->port_lh, "usbser_rq_thread: exit");
        mutex_exit(&pp->port_mutex);
}


/*
 * DSD callbacks
 * -------------
 *
 * Note: to avoid deadlocks with DSD, these callbacks
 * should not call DSD functions that can block.
 *
 *
 * transmit callback
 *
 * invoked by DSD when the last byte of data is transmitted over USB
 */
static void
usbser_tx_cb(caddr_t arg)
{
        usbser_port_t   *pp = (usbser_port_t *)arg;
        int             online;

        online = usbser_dev_is_online(pp->port_usp);

        mutex_enter(&pp->port_mutex);
        USB_DPRINTF_L4(DPRINT_TX_CB, pp->port_lh,
            "usbser_tx_cb: act=%x curthread=%p", pp->port_act,
            (void *)curthread);

        usbser_release_port_act(pp, USBSER_ACT_TX);

        /*
         * as long as port access is ok and the port is not busy on
         * TX, break, ctrl or delay, the wq_thread should be waken
         * to do further process for next message
         */
        if (online && USBSER_PORT_ACCESS_OK(pp) &&
            !USBSER_PORT_IS_BUSY_NON_RX(pp)) {
                /*
                 * wake wq thread for further data/ioctl processing
                 */
                usbser_thr_wake(&pp->port_wq_thread);
        }
        mutex_exit(&pp->port_mutex);
}


/*
 * receive callback
 *
 * invoked by DSD when there is more data for us to pick
 */
static void
usbser_rx_cb(caddr_t arg)
{
        usbser_port_t   *pp = (usbser_port_t *)arg;
        queue_t         *rq, *wq;
        mblk_t          *mp;            /* current mblk */
        mblk_t          *data, *data_tail; /* M_DATA mblk list and its tail */
        mblk_t          *emp;           /* error (M_BREAK) mblk */

        USB_DPRINTF_L4(DPRINT_RX_CB, pp->port_lh, "usbser_rx_cb");

        if (!usbser_dev_is_online(pp->port_usp)) {

                return;
        }

        /* get data from DSD */
        if ((mp = USBSER_DS_RX(pp)) == NULL) {

                return;
        }

        mutex_enter(&pp->port_mutex);
        if ((!USBSER_PORT_ACCESS_OK(pp)) ||
            ((pp->port_ttycommon.t_cflag & CREAD) == 0)) {
                freemsg(mp);
                mutex_exit(&pp->port_mutex);
                USB_DPRINTF_L3(DPRINT_RX_CB, pp->port_lh,
                    "usbser_rx_cb: access not ok or receiver disabled");

                return;
        }

        usbser_serialize_port_act(pp, USBSER_ACT_RX);

        rq = pp->port_ttycommon.t_readq;
        wq = pp->port_ttycommon.t_writeq;
        mutex_exit(&pp->port_mutex);

        /*
         * DSD data is a b_cont-linked list of M_DATA and M_BREAK blocks.
         * M_DATA is correctly received data.
         * M_BREAK is a character with either framing or parity error.
         *
         * this loop runs through the list of mblks. when it meets an M_BREAK,
         * it sends all leading M_DATA's in one shot, then sends M_BREAK.
         * in the trivial case when list contains only M_DATA's, the loop
         * does nothing but set data variable.
         */
        data = data_tail = NULL;
        while (mp) {
                /*
                 * skip data until we meet M_BREAK or end of list
                 */
                if (DB_TYPE(mp) == M_DATA) {
                        if (data == NULL) {
                                data = mp;
                        }
                        data_tail = mp;
                        mp = mp->b_cont;

                        continue;
                }

                /* detach data list from mp */
                if (data_tail) {
                        data_tail->b_cont = NULL;
                }
                /* detach emp from the list */
                emp = mp;
                mp = mp->b_cont;
                emp->b_cont = NULL;

                /* DSD shouldn't send anything but M_DATA or M_BREAK */
                if ((DB_TYPE(emp) != M_BREAK) || (MBLKL(emp) != 2)) {
                        freemsg(emp);
                        USB_DPRINTF_L2(DPRINT_RX_CB, pp->port_lh,
                            "usbser_rx_cb: bad message");

                        continue;
                }

                /*
                 * first tweak and send M_DATA's
                 */
                if (data) {
                        usbser_rx_massage_data(pp, data);
                        usbser_rx_cb_put(pp, rq, wq, data);
                        data = data_tail = NULL;
                }

                /*
                 * now tweak and send M_BREAK
                 */
                mutex_enter(&pp->port_mutex);
                usbser_rx_massage_mbreak(pp, emp);
                mutex_exit(&pp->port_mutex);
                usbser_rx_cb_put(pp, rq, wq, emp);
        }

        /* send the rest of the data, if any */
        if (data) {
                usbser_rx_massage_data(pp, data);
                usbser_rx_cb_put(pp, rq, wq, data);
        }

        mutex_enter(&pp->port_mutex);
        usbser_release_port_act(pp, USBSER_ACT_RX);
        mutex_exit(&pp->port_mutex);
}

/*
 * the joys of termio -- this is to accomodate Unix98 assertion:
 *
 *   If PARENB is supported and is set, when PARMRK is set, and CSIZE is
 *   set to CS8, and IGNPAR is clear, and ISTRIP is clear, a valid
 *   character of '\377' is read as '\377', '\377'.
 *
 *   Posix Ref: Assertion 7.1.2.2-16(C)
 *
 * this requires the driver to scan every incoming valid character
 */
static void
usbser_rx_massage_data(usbser_port_t *pp, mblk_t *mp)
{
        tty_common_t    *tp = &pp->port_ttycommon;
        uchar_t         *p;
        mblk_t          *newmp;
        int             tailsz;

        /* avoid scanning if possible */
        mutex_enter(&pp->port_mutex);
        if (!((tp->t_cflag & PARENB) && (tp->t_iflag & PARMRK) &&
            ((tp->t_cflag & CSIZE) == CS8) &&
            ((tp->t_iflag & (IGNPAR|ISTRIP)) == 0))) {
                mutex_exit(&pp->port_mutex);

                return;
        }
        mutex_exit(&pp->port_mutex);

        while (mp) {
                for (p = mp->b_rptr; p < mp->b_wptr; ) {
                        if (*p++ != 0377) {

                                continue;
                        }
                        USB_DPRINTF_L4(DPRINT_RX_CB, pp->port_lh,
                            "usbser_rx_massage_data: mp=%p off=%ld(%ld)",
                            (void *)mp, _PTRDIFF(p,  mp->b_rptr) - 1,
                            (long)MBLKL(mp));

                        /*
                         * insert another 0377 after this one. all data after
                         * the original 0377 have to be copied to the new mblk
                         */
                        tailsz = _PTRDIFF(mp->b_wptr, p);
                        if ((newmp = allocb(tailsz + 1, BPRI_HI)) == NULL) {
                                USB_DPRINTF_L2(DPRINT_RX_CB, pp->port_lh,
                                    "usbser_rx_massage_data: allocb failed");

                                continue;
                        }

                        /* fill in the new mblk */
                        *newmp->b_wptr++ = 0377;
                        if (tailsz > 0) {
                                bcopy(p, newmp->b_wptr, tailsz);
                                newmp->b_wptr += tailsz;
                        }
                        /* shrink the original mblk */
                        mp->b_wptr = p;

                        newmp->b_cont = mp->b_cont;
                        mp->b_cont = newmp;
                        p = newmp->b_rptr + 1;
                        mp = newmp;
                }
                mp = mp->b_cont;
        }
}

/*
 * more joys of termio
 */
static void
usbser_rx_massage_mbreak(usbser_port_t *pp, mblk_t *mp)
{
        tty_common_t    *tp = &pp->port_ttycommon;
        uchar_t         err, c;

        err = *mp->b_rptr;
        c = *(mp->b_rptr + 1);

        if ((err & (DS_FRAMING_ERR | DS_BREAK_ERR)) && (c == 0)) {
                /* break */
                mp->b_rptr += 2;
        } else if (!(tp->t_iflag & INPCK) && (err & (DS_PARITY_ERR))) {
                /* Posix Ref: Assertion 7.1.2.2-20(C) */
                mp->b_rptr++;
                DB_TYPE(mp) = M_DATA;
        } else {
                /* for ldterm to handle */
                mp->b_rptr++;
        }

        USB_DPRINTF_L4(DPRINT_RX_CB, pp->port_lh,
            "usbser_rx_massage_mbreak: type=%x len=%ld [0]=0%o",
            DB_TYPE(mp), (long)MBLKL(mp), (MBLKL(mp) > 0) ? *mp->b_rptr : 45);
}


/*
 * in rx callback, try to send an mblk upstream
 */
static void
usbser_rx_cb_put(usbser_port_t *pp, queue_t *rq, queue_t *wq, mblk_t *mp)
{
        if (canputnext(rq)) {
                putnext(rq, mp);
        } else if (canput(rq) && putq(rq, mp)) {
                /*
                 * full queue indicates the need for inbound flow control
                 */
                (void) putctl(wq, M_STOPI);
                usbser_st_put_stopi++;

                USB_DPRINTF_L3(DPRINT_RX_CB, pp->port_lh,
                    "usbser_rx_cb: cannot putnext, flow ctl");
        } else {
                freemsg(mp);
                usbser_st_rx_data_loss++;
                (void) putctl(wq, M_STOPI);
                usbser_st_put_stopi++;

                USB_DPRINTF_L1(DPRINT_RX_CB, pp->port_lh,
                    "input overrun");
        }
}


/*
 * modem status change callback
 *
 * each time external status lines are changed, DSD calls this routine
 */
static void
usbser_status_cb(caddr_t arg)
{
        usbser_port_t   *pp = (usbser_port_t *)arg;

        USB_DPRINTF_L4(DPRINT_STATUS_CB, pp->port_lh, "usbser_status_cb");

        if (!usbser_dev_is_online(pp->port_usp)) {

                return;
        }

        /*
         * actual processing will be done in usbser_status_proc_cb()
         * running in wq thread
         */
        mutex_enter(&pp->port_mutex);
        if (USBSER_PORT_ACCESS_OK(pp) || USBSER_IS_OPENING(pp)) {
                pp->port_flags |= USBSER_FL_STATUS_CB;
                usbser_thr_wake(&pp->port_wq_thread);
        }
        mutex_exit(&pp->port_mutex);
}


/*
 * modem status change
 */
static void
usbser_status_proc_cb(usbser_port_t *pp)
{
        tty_common_t    *tp = &pp->port_ttycommon;
        queue_t         *rq, *wq;
        int             status;
        int             drop_dtr = 0;
        int             rq_msg = 0, wq_msg = 0;

        USB_DPRINTF_L4(DPRINT_STATUS_CB, pp->port_lh, "usbser_status_proc_cb");

        pp->port_flags &= ~USBSER_FL_STATUS_CB;

        mutex_exit(&pp->port_mutex);
        if (!usbser_dev_is_online(pp->port_usp)) {
                mutex_enter(&pp->port_mutex);

                return;
        }

        /* get modem status */
        if (USBSER_DS_GET_MODEM_CTL(pp, -1, &status) != USB_SUCCESS) {
                mutex_enter(&pp->port_mutex);

                return;
        }

        mutex_enter(&pp->port_mutex);
        usbser_serialize_port_act(pp, USBSER_ACT_CTL);

        rq = pp->port_ttycommon.t_readq;
        wq = pp->port_ttycommon.t_writeq;

        /*
         * outbound flow control
         */
        if (tp->t_cflag & CRTSCTS) {
                if (!(status & TIOCM_CTS)) {
                        /*
                         * CTS dropped, stop xmit
                         */
                        if (!(pp->port_flags & USBSER_FL_TX_STOPPED)) {
                                wq_msg = M_STOP;
                        }
                } else if (pp->port_flags & USBSER_FL_TX_STOPPED) {
                        /*
                         * CTS raised, resume xmit
                         */
                        wq_msg = M_START;
                }
        }

        /*
         * check carrier
         */
        if ((status & TIOCM_CD) || (tp->t_flags & TS_SOFTCAR)) {
                /*
                 * carrier present
                 */
                if ((pp->port_flags & USBSER_FL_CARR_ON) == 0) {
                        pp->port_flags |= USBSER_FL_CARR_ON;

                        rq_msg = M_UNHANGUP;
                        /*
                         * wake open
                         */
                        if (pp->port_flags & USBSER_FL_WOPEN) {
                                cv_broadcast(&pp->port_car_cv);
                        }

                        USB_DPRINTF_L4(DPRINT_STATUS_CB, pp->port_lh,
                            "usbser_status_cb: carr on");
                }
        } else if (pp->port_flags & USBSER_FL_CARR_ON) {
                pp->port_flags &= ~USBSER_FL_CARR_ON;
                /*
                 * carrier went away: if not local line, drop DTR
                 */
                if (!(tp->t_cflag & CLOCAL)) {
                        drop_dtr = 1;
                        rq_msg = M_HANGUP;
                }
                if ((pp->port_flags & USBSER_FL_TX_STOPPED) && (wq_msg == 0)) {
                        wq_msg = M_START;
                }

                USB_DPRINTF_L4(DPRINT_STATUS_CB, pp->port_lh,
                    "usbser_status_cb: carr off");
        }
        mutex_exit(&pp->port_mutex);

        USB_DPRINTF_L4(DPRINT_STATUS_CB, pp->port_lh,
            "usbser_status_cb: rq_msg=%d wq_msg=%d", rq_msg, wq_msg);

        /*
         * commit postponed actions now
         * do so only if port is fully open (queues are enabled)
         */
        if (rq) {
                if (rq_msg) {
                        (void) putnextctl(rq, rq_msg);
                }
                if (drop_dtr) {
                        (void) USBSER_DS_SET_MODEM_CTL(pp, TIOCM_DTR, 0);
                }
                if (wq_msg) {
                        (void) putctl(wq, wq_msg);
                }
        }

        mutex_enter(&pp->port_mutex);
        usbser_release_port_act(pp, USBSER_ACT_CTL);
}


/*
 * serial support
 * --------------
 *
 *
 * this routine is run by wq thread every time it's woken,
 * i.e. when the queue contains messages to process
 */
static void
usbser_wmsg(usbser_port_t *pp)
{
        queue_t         *q = pp->port_ttycommon.t_writeq;
        mblk_t          *mp;
        int             msgtype;

        ASSERT(mutex_owned(&pp->port_mutex));

        if (q == NULL) {
                USB_DPRINTF_L3(DPRINT_WQ, pp->port_lh, "usbser_wmsg: q=NULL");

                return;
        }
        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_wmsg: q=%p act=%x 0x%x",
            (void *)q, pp->port_act, q->q_first ? DB_TYPE(q->q_first) : 0xff);

        while ((mp = getq(q)) != NULL) {
                msgtype = DB_TYPE(mp);
                USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_wmsg: "
                    "type=%s (0x%x)", usbser_msgtype2str(msgtype), msgtype);

                switch (msgtype) {
                /*
                 * high-priority messages
                 */
                case M_STOP:
                        usbser_stop(pp, mp);

                        break;
                case M_START:
                        usbser_start(pp, mp);

                        break;
                case M_STOPI:
                        usbser_stopi(pp, mp);

                        break;
                case M_STARTI:
                        usbser_starti(pp, mp);

                        break;
                case M_IOCDATA:
                        usbser_iocdata(pp, mp);

                        break;
                case M_FLUSH:
                        usbser_flush(pp, mp);

                        break;
                /*
                 * normal-priority messages
                 */
                case M_BREAK:
                        usbser_break(pp, mp);

                        break;
                case M_DELAY:
                        usbser_delay(pp, mp);

                        break;
                case M_DATA:
                        if (usbser_data(pp, mp) != USB_SUCCESS) {
                                (void) putbq(q, mp);

                                return;
                        }

                        break;
                case M_IOCTL:
                        if (usbser_ioctl(pp, mp) != USB_SUCCESS) {
                                (void) putbq(q, mp);

                                return;
                        }

                        break;
                default:
                        freemsg(mp);

                        break;
                }
        }
}


/*
 * process M_DATA message
 */
static int
usbser_data(usbser_port_t *pp, mblk_t *mp)
{
        /* put off until current transfer ends or delay is over */
        if ((pp->port_act & USBSER_ACT_TX) ||
            (pp->port_act & USBSER_ACT_DELAY)) {

                return (USB_FAILURE);
        }
        if (MBLKL(mp) <= 0) {
                freemsg(mp);

                return (USB_SUCCESS);
        }

        pp->port_act |= USBSER_ACT_TX;
        pp->port_wq_data_cnt -= msgdsize(mp);

        mutex_exit(&pp->port_mutex);
        /* DSD is required to accept data block in any case */
        (void) USBSER_DS_TX(pp, mp);
        mutex_enter(&pp->port_mutex);

        return (USB_SUCCESS);
}


/*
 * process an M_IOCTL message
 */
static int
usbser_ioctl(usbser_port_t *pp, mblk_t *mp)
{
        tty_common_t    *tp = &pp->port_ttycommon;
        queue_t         *q = tp->t_writeq;
        struct iocblk   *iocp;
        int             cmd;
        mblk_t          *datamp;
        int             error = 0, rval = USB_SUCCESS;
        int             val;

        ASSERT(mutex_owned(&pp->port_mutex));
        ASSERT(DB_TYPE(mp) == M_IOCTL);

        iocp = (struct iocblk *)mp->b_rptr;
        cmd = iocp->ioc_cmd;

        USB_DPRINTF_L4(DPRINT_IOCTL, pp->port_lh, "usbser_ioctl: "
            "mp=%p %s (0x%x)", (void *)mp, usbser_ioctl2str(cmd), cmd);

        if (tp->t_iocpending != NULL) {
                /*
                 * We were holding an ioctl response pending the
                 * availability of an mblk to hold data to be passed up;
                 * another ioctl came through, which means that ioctl
                 * must have timed out or been aborted.
                 */
                freemsg(tp->t_iocpending);
                tp->t_iocpending = NULL;
        }

        switch (cmd) {
        case TIOCMGET:
        case TIOCMBIC:
        case TIOCMBIS:
        case TIOCMSET:
        case CONSOPENPOLLEDIO:
        case CONSCLOSEPOLLEDIO:
        case CONSSETABORTENABLE:
        case CONSGETABORTENABLE:
                /*
                 * For the above ioctls do not call ttycommon_ioctl() because
                 * this function frees up the message block (mp->b_cont) that
                 * contains the address of the user variable where we need to
                 * pass back the bit array.
                 */
                error = -1;
                usbser_serialize_port_act(pp, USBSER_ACT_CTL);
                mutex_exit(&pp->port_mutex);
                break;

        case TCSBRK:
                /* serialize breaks */
                if (pp->port_act & USBSER_ACT_BREAK)
                        return (USB_FAILURE);
                /*FALLTHRU*/
        default:
                usbser_serialize_port_act(pp, USBSER_ACT_CTL);
                mutex_exit(&pp->port_mutex);
                (void) ttycommon_ioctl(tp, q, mp, &error);
                break;
        }

        if (error == 0) {
                /*
                 * ttycommon_ioctl() did most of the work
                 * we just use the data it set up
                 */
                switch (cmd) {
                case TCSETSF:
                case TCSETSW:
                case TCSETA:
                case TCSETAW:
                case TCSETAF:
                        (void) USBSER_DS_FIFO_DRAIN(pp, DS_TX);
                        /*FALLTHRU*/

                case TCSETS:
                        mutex_enter(&pp->port_mutex);
                        error = usbser_port_program(pp);
                        mutex_exit(&pp->port_mutex);
                        break;
                }
                goto end;

        } else if (error > 0) {
                USB_DPRINTF_L3(DPRINT_IOCTL, pp->port_lh, "usbser_ioctl: "
                    "ttycommon_ioctl returned %d", error);
                goto end;
        }

        /*
         * error < 0: ttycommon_ioctl() didn't do anything, we process it here
         */
        error = 0;
        switch (cmd) {
        case TCSBRK:
                if ((error = miocpullup(mp, sizeof (int))) != 0)
                        break;

                /* drain output */
                (void) USBSER_DS_FIFO_DRAIN(pp, USBSER_TX_FIFO_DRAIN_TIMEOUT);

                /*
                 * if required, set break
                 */
                if (*(int *)mp->b_cont->b_rptr == 0) {
                        if (USBSER_DS_BREAK_CTL(pp, DS_ON) != USB_SUCCESS) {
                                error = EIO;
                                break;
                        }

                        mutex_enter(&pp->port_mutex);
                        pp->port_act |= USBSER_ACT_BREAK;
                        pp->port_delay_id = timeout(usbser_restart, pp,
                            drv_usectohz(250000));
                        mutex_exit(&pp->port_mutex);
                }
                mioc2ack(mp, NULL, 0, 0);
                break;

        case TIOCSBRK:  /* set break */
                if (USBSER_DS_BREAK_CTL(pp, DS_ON) != USB_SUCCESS)
                        error = EIO;
                else
                        mioc2ack(mp, NULL, 0, 0);
                break;

        case TIOCCBRK:  /* clear break */
                if (USBSER_DS_BREAK_CTL(pp, DS_OFF) != USB_SUCCESS)
                        error = EIO;
                else
                        mioc2ack(mp, NULL, 0, 0);
                break;

        case TIOCMSET:  /* set all modem bits */
        case TIOCMBIS:  /* bis modem bits */
        case TIOCMBIC:  /* bic modem bits */
                if (iocp->ioc_count == TRANSPARENT) {
                        mcopyin(mp, NULL, sizeof (int), NULL);
                        break;
                }
                if ((error = miocpullup(mp, sizeof (int))) != 0)
                        break;

                val = *(int *)mp->b_cont->b_rptr;
                if (cmd == TIOCMSET) {
                        rval = USBSER_DS_SET_MODEM_CTL(pp, -1, val);
                } else if (cmd == TIOCMBIS) {
                        rval = USBSER_DS_SET_MODEM_CTL(pp, val, -1);
                } else if (cmd == TIOCMBIC) {
                        rval = USBSER_DS_SET_MODEM_CTL(pp, val, 0);
                }
                if (rval == USB_SUCCESS)
                        mioc2ack(mp, NULL, 0, 0);
                else
                        error = EIO;
                break;

        case TIOCSILOOP:
                if (USBSER_DS_LOOPBACK_SUPPORTED(pp)) {
                        if (USBSER_DS_LOOPBACK(pp, DS_ON) == USB_SUCCESS)
                                mioc2ack(mp, NULL, 0, 0);
                        else
                                error = EIO;
                } else {
                        error = EINVAL;
                }
                break;

        case TIOCCILOOP:
                if (USBSER_DS_LOOPBACK_SUPPORTED(pp)) {
                        if (USBSER_DS_LOOPBACK(pp, DS_OFF) == USB_SUCCESS)
                                mioc2ack(mp, NULL, 0, 0);
                        else
                                error = EIO;
                } else {
                        error = EINVAL;
                }
                break;

        case TIOCMGET:  /* get all modem bits */
                if ((datamp = allocb(sizeof (int), BPRI_MED)) == NULL) {
                        error = EAGAIN;
                        break;
                }
                rval = USBSER_DS_GET_MODEM_CTL(pp, -1, (int *)datamp->b_rptr);
                if (rval != USB_SUCCESS) {
                        error = EIO;
                        break;
                }
                if (iocp->ioc_count == TRANSPARENT)
                        mcopyout(mp, NULL, sizeof (int), NULL, datamp);
                else
                        mioc2ack(mp, datamp, sizeof (int), 0);
                break;

        case CONSOPENPOLLEDIO:
                error = usbser_polledio_init(pp);
                if (error != 0)
                        break;

                error = miocpullup(mp, sizeof (struct cons_polledio *));
                if (error != 0)
                        break;

                *(struct cons_polledio **)mp->b_cont->b_rptr = &usbser_polledio;

                mp->b_datap->db_type = M_IOCACK;
                break;

        case CONSCLOSEPOLLEDIO:
                usbser_polledio_fini(pp);
                mp->b_datap->db_type = M_IOCACK;
                iocp->ioc_error = 0;
                iocp->ioc_rval = 0;
                break;

        case CONSSETABORTENABLE:
                error = secpolicy_console(iocp->ioc_cr);
                if (error != 0)
                        break;

                if (iocp->ioc_count != TRANSPARENT) {
                        error = EINVAL;
                        break;
                }

                /*
                 * To do: implement console abort support
                 * This involves adding a console flag to usbser
                 * state structure. If flag is set, parse input stream
                 * for abort sequence (see asy for example).
                 *
                 * For now, run mdb -K to get kmdb prompt.
                 */
                if (*(intptr_t *)mp->b_cont->b_rptr)
                        usbser_console_abort = 1;
                else
                        usbser_console_abort = 0;

                mp->b_datap->db_type = M_IOCACK;
                iocp->ioc_error = 0;
                iocp->ioc_rval = 0;
                break;

        case CONSGETABORTENABLE:
                /*CONSTANTCONDITION*/
                ASSERT(sizeof (boolean_t) <= sizeof (boolean_t *));
                /*
                 * Store the return value right in the payload
                 * we were passed.  Crude.
                 */
                mcopyout(mp, NULL, sizeof (boolean_t), NULL, NULL);
                *(boolean_t *)mp->b_cont->b_rptr = (usbser_console_abort != 0);
                break;

        default:
                error = EINVAL;
                break;
        }
end:
        if (error != 0)
                miocnak(q, mp, 0, error);
        else
                qreply(q, mp);

        mutex_enter(&pp->port_mutex);
        usbser_release_port_act(pp, USBSER_ACT_CTL);

        return (USB_SUCCESS);
}


/*
 * process M_IOCDATA message
 */
static void
usbser_iocdata(usbser_port_t *pp, mblk_t *mp)
{
        tty_common_t    *tp = &pp->port_ttycommon;
        queue_t         *q = tp->t_writeq;
        struct copyresp *csp;
        int             cmd;
        int             val;
        int             rval = USB_FAILURE;

        ASSERT(mutex_owned(&pp->port_mutex));

        csp = (struct copyresp *)mp->b_rptr;
        cmd = csp->cp_cmd;

        if (csp->cp_rval != 0) {
                freemsg(mp);
                return;
        }

        switch (cmd) {
        case TIOCMSET:  /* set all modem bits */
        case TIOCMBIS:  /* bis modem bits */
        case TIOCMBIC:  /* bic modem bits */
                if ((mp->b_cont == NULL) ||
                    (MBLKL(mp->b_cont) < sizeof (int))) {
                        miocnak(q, mp, 0, EINVAL);
                        break;
                }
                val = *(int *)mp->b_cont->b_rptr;

                usbser_serialize_port_act(pp, USBSER_ACT_CTL);
                mutex_exit(&pp->port_mutex);

                if (cmd == TIOCMSET) {
                        rval = USBSER_DS_SET_MODEM_CTL(pp, -1, val);
                } else if (cmd == TIOCMBIS) {
                        rval = USBSER_DS_SET_MODEM_CTL(pp, val, -1);
                } else if (cmd == TIOCMBIC) {
                        rval = USBSER_DS_SET_MODEM_CTL(pp, val, 0);
                }

                if (mp->b_cont) {
                        freemsg(mp->b_cont);
                        mp->b_cont = NULL;
                }

                if (rval == USB_SUCCESS)
                        miocack(q, mp, 0, 0);
                else
                        miocnak(q, mp, 0, EIO);

                mutex_enter(&pp->port_mutex);
                usbser_release_port_act(pp, USBSER_ACT_CTL);
                break;

        case TIOCMGET:  /* get all modem bits */
                mutex_exit(&pp->port_mutex);
                miocack(q, mp, 0, 0);
                mutex_enter(&pp->port_mutex);
                break;

        default:
                mutex_exit(&pp->port_mutex);
                miocnak(q, mp, 0, EINVAL);
                mutex_enter(&pp->port_mutex);
                break;
        }
}


/*
 * handle M_START[I]/M_STOP[I] messages
 */
static void
usbser_stop(usbser_port_t *pp, mblk_t *mp)
{
        usbser_st_mstop++;
        if (!(pp->port_flags & USBSER_FL_TX_STOPPED)) {
                usbser_serialize_port_act(pp, USBSER_ACT_CTL);
                pp->port_flags |= USBSER_FL_TX_STOPPED;

                mutex_exit(&pp->port_mutex);
                USBSER_DS_STOP(pp, DS_TX);
                mutex_enter(&pp->port_mutex);

                usbser_release_port_act(pp, USBSER_ACT_TX);
                usbser_release_port_act(pp, USBSER_ACT_CTL);
        }
        freemsg(mp);
}


static void
usbser_start(usbser_port_t *pp, mblk_t *mp)
{
        usbser_st_mstart++;
        if (pp->port_flags & USBSER_FL_TX_STOPPED) {
                usbser_serialize_port_act(pp, USBSER_ACT_CTL);
                pp->port_flags &= ~USBSER_FL_TX_STOPPED;

                mutex_exit(&pp->port_mutex);
                USBSER_DS_START(pp, DS_TX);
                mutex_enter(&pp->port_mutex);
                usbser_release_port_act(pp, USBSER_ACT_CTL);
        }
        freemsg(mp);
}


static void
usbser_stopi(usbser_port_t *pp, mblk_t *mp)
{
        usbser_st_mstopi++;
        usbser_serialize_port_act(pp, USBSER_ACT_CTL);
        pp->port_flowc = pp->port_ttycommon.t_stopc;
        usbser_inbound_flow_ctl(pp);
        usbser_release_port_act(pp, USBSER_ACT_CTL);
        freemsg(mp);
}

static void
usbser_starti(usbser_port_t *pp, mblk_t *mp)
{
        usbser_st_mstarti++;
        usbser_serialize_port_act(pp, USBSER_ACT_CTL);
        pp->port_flowc = pp->port_ttycommon.t_startc;
        usbser_inbound_flow_ctl(pp);
        usbser_release_port_act(pp, USBSER_ACT_CTL);
        freemsg(mp);
}

/*
 * process M_FLUSH message
 */
static void
usbser_flush(usbser_port_t *pp, mblk_t *mp)
{
        queue_t *q = pp->port_ttycommon.t_writeq;

        if (*mp->b_rptr & FLUSHW) {
                mutex_exit(&pp->port_mutex);
                (void) USBSER_DS_FIFO_FLUSH(pp, DS_TX); /* flush FIFO buffers */
                flushq(q, FLUSHDATA);                   /* flush write queue */
                mutex_enter(&pp->port_mutex);

                usbser_release_port_act(pp, USBSER_ACT_TX);

                *mp->b_rptr &= ~FLUSHW;
        }
        if (*mp->b_rptr & FLUSHR) {
                /*
                 * flush FIFO buffers
                 */
                mutex_exit(&pp->port_mutex);
                (void) USBSER_DS_FIFO_FLUSH(pp, DS_RX);
                flushq(RD(q), FLUSHDATA);
                qreply(q, mp);
                mutex_enter(&pp->port_mutex);
        } else {
                freemsg(mp);
        }
}

/*
 * process M_BREAK message
 */
static void
usbser_break(usbser_port_t *pp, mblk_t *mp)
{
        int     rval;

        /*
         * set the break and arrange for usbser_restart() to be called in 1/4 s
         */
        mutex_exit(&pp->port_mutex);
        rval = USBSER_DS_BREAK_CTL(pp, DS_ON);
        mutex_enter(&pp->port_mutex);

        if (rval == USB_SUCCESS) {
                pp->port_act |= USBSER_ACT_BREAK;
                pp->port_delay_id = timeout(usbser_restart, pp,
                    drv_usectohz(250000));
        }
        freemsg(mp);
}


/*
 * process M_DELAY message
 */
static void
usbser_delay(usbser_port_t *pp, mblk_t *mp)
{
        /*
         * arrange for usbser_restart() to be called when the delay expires
         */
        pp->port_act |= USBSER_ACT_DELAY;
        pp->port_delay_id = timeout(usbser_restart, pp,
            (clock_t)(*(uchar_t *)mp->b_rptr + 6));
        freemsg(mp);
}


/*
 * restart output on a line after a delay or break timer expired
 */
static void
usbser_restart(void *arg)
{
        usbser_port_t   *pp = (usbser_port_t *)arg;

        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh, "usbser_restart");

        mutex_enter(&pp->port_mutex);
        /* if cancelled, return immediately */
        if (pp->port_delay_id == 0) {
                mutex_exit(&pp->port_mutex);

                return;
        }
        pp->port_delay_id = 0;

        /* clear break if necessary */
        if (pp->port_act & USBSER_ACT_BREAK) {
                mutex_exit(&pp->port_mutex);
                (void) USBSER_DS_BREAK_CTL(pp, DS_OFF);
                mutex_enter(&pp->port_mutex);
        }

        usbser_release_port_act(pp, USBSER_ACT_BREAK | USBSER_ACT_DELAY);

        /* wake wq thread to resume message processing */
        usbser_thr_wake(&pp->port_wq_thread);
        mutex_exit(&pp->port_mutex);
}


/*
 * program port hardware with the chosen parameters
 * most of the operation is based on the values of 'c_iflag' and 'c_cflag'
 */
static int
usbser_port_program(usbser_port_t *pp)
{
        tty_common_t            *tp = &pp->port_ttycommon;
        int                     baudrate;
        int                     c_flag;
        ds_port_param_entry_t   pe[6];
        ds_port_params_t        params;
        int                     flow_ctl, ctl_val;
        int                     err = 0;

        baudrate = tp->t_cflag & CBAUD;
        if (tp->t_cflag & CBAUDEXT) {
                baudrate += 16;
        }

        /*
         * set input speed same as output, as split speed not supported
         */
        if (tp->t_cflag & (CIBAUD|CIBAUDEXT)) {
                tp->t_cflag &= ~(CIBAUD);
                if (baudrate > CBAUD) {
                        tp->t_cflag |= CIBAUDEXT;
                        tp->t_cflag |=
                            (((baudrate - CBAUD - 1) << IBSHIFT) & CIBAUD);
                } else {
                        tp->t_cflag &= ~CIBAUDEXT;
                        tp->t_cflag |= ((baudrate << IBSHIFT) & CIBAUD);
                }
        }

        c_flag = tp->t_cflag;

        /*
         * flow control
         */
        flow_ctl = tp->t_iflag & (IXON | IXANY | IXOFF);
        if (c_flag & CRTSCTS) {
                flow_ctl |= CTSXON;
        }
        if (c_flag & CRTSXOFF) {
                flow_ctl |= RTSXOFF;
        }

        /*
         * fill in port parameters we need to set:
         *
         * baud rate
         */
        pe[0].param = DS_PARAM_BAUD;
        pe[0].val.ui = baudrate;

        /* stop bits */
        pe[1].param = DS_PARAM_STOPB;
        pe[1].val.ui = c_flag & CSTOPB;

        /* parity */
        pe[2].param = DS_PARAM_PARITY;
        pe[2].val.ui = c_flag & (PARENB | PARODD);

        /* char size */
        pe[3].param = DS_PARAM_CHARSZ;
        pe[3].val.ui = c_flag & CSIZE;

        /* start & stop chars */
        pe[4].param = DS_PARAM_XON_XOFF;
        pe[4].val.uc[0] = tp->t_startc;
        pe[4].val.uc[1] = tp->t_stopc;

        /* flow control */
        pe[5].param = DS_PARAM_FLOW_CTL;
        pe[5].val.ui = flow_ctl;

        params.tp_entries = &pe[0];
        params.tp_cnt = 6;

        /* control signals */
        ctl_val = TIOCM_DTR | TIOCM_RTS;
        if (baudrate == 0) {
                ctl_val &= ~TIOCM_DTR;  /* zero baudrate means drop DTR */
        }
        if (pp->port_flags & USBSER_FL_RX_STOPPED) {
                ctl_val &= ~TIOCM_RTS;
        }

        /* submit */
        mutex_exit(&pp->port_mutex);
        err = USBSER_DS_SET_PORT_PARAMS(pp, &params);
        if (err != USB_SUCCESS) {
                mutex_enter(&pp->port_mutex);

                return (EINVAL);
        }

        err = USBSER_DS_SET_MODEM_CTL(pp, TIOCM_DTR | TIOCM_RTS, ctl_val);
        mutex_enter(&pp->port_mutex);

        return ((err == USB_SUCCESS) ? 0 : EIO);
}


/*
 * check if any inbound flow control action needed
 */
static void
usbser_inbound_flow_ctl(usbser_port_t *pp)
{
        tcflag_t        need_hw;
        int             rts;
        char            c = pp->port_flowc;
        mblk_t          *mp = NULL;

        USB_DPRINTF_L4(DPRINT_WQ, pp->port_lh,
            "usbser_inbound_flow_ctl: c=%x cflag=%x port_flags=%x",
            c, pp->port_ttycommon.t_cflag, pp->port_flags);

        if (c == '\0') {

                return;
        }
        pp->port_flowc = '\0';

        /*
         * if inbound hardware flow control enabled, we need to frob RTS
         */
        need_hw = (pp->port_ttycommon.t_cflag & CRTSXOFF);
        if (c == pp->port_ttycommon.t_startc) {
                rts = TIOCM_RTS;
                pp->port_flags &= ~USBSER_FL_RX_STOPPED;
        } else {
                rts = 0;
                pp->port_flags |= USBSER_FL_RX_STOPPED;
        }

        /*
         * if character flow control active, transmit a start or stop char,
         */
        if (pp->port_ttycommon.t_iflag & IXOFF) {
                if ((mp = allocb(1, BPRI_LO)) == NULL) {
                        USB_DPRINTF_L2(DPRINT_WQ, pp->port_lh,
                            "usbser_inbound_flow_ctl: allocb failed");
                } else {
                        *mp->b_wptr++ = c;
                        pp->port_flags |= USBSER_ACT_TX;
                }
        }

        mutex_exit(&pp->port_mutex);
        if (need_hw) {
                (void) USBSER_DS_SET_MODEM_CTL(pp, TIOCM_RTS, rts);
        }
        if (mp) {
                (void) USBSER_DS_TX(pp, mp);
        }
        mutex_enter(&pp->port_mutex);
}


/*
 * misc
 * ----
 *
 *
 * returns != 0 if device is online, 0 otherwise
 */
static int
usbser_dev_is_online(usbser_state_t *usp)
{
        int     rval;

        mutex_enter(&usp->us_mutex);
        rval = (usp->us_dev_state == USB_DEV_ONLINE);
        mutex_exit(&usp->us_mutex);

        return (rval);
}

/*
 * serialize port activities defined by 'act' mask
 */
static void
usbser_serialize_port_act(usbser_port_t *pp, int act)
{
        while (pp->port_act & act)
                cv_wait(&pp->port_act_cv, &pp->port_mutex);
        pp->port_act |= act;
}


/*
 * indicate that port activity is finished
 */
static void
usbser_release_port_act(usbser_port_t *pp, int act)
{
        pp->port_act &= ~act;
        cv_broadcast(&pp->port_act_cv);
}

#ifdef DEBUG
/*
 * message type to string and back conversion.
 *
 * pardon breaks on the same line, but as long as cstyle doesn't
 * complain, I'd like to keep this form for trivial cases like this.
 * associative arrays in the kernel, anyone?
 */
static char *
usbser_msgtype2str(int type)
{
        char    *str;

        switch (type) {
        case M_STOP:    str = "M_STOP";         break;
        case M_START:   str = "M_START";        break;
        case M_STOPI:   str = "M_STOPI";        break;
        case M_STARTI:  str = "M_STARTI";       break;
        case M_DATA:    str = "M_DATA";         break;
        case M_DELAY:   str = "M_DELAY";        break;
        case M_BREAK:   str = "M_BREAK";        break;
        case M_IOCTL:   str = "M_IOCTL";        break;
        case M_IOCDATA: str = "M_IOCDATA";      break;
        case M_FLUSH:   str = "M_FLUSH";        break;
        case M_CTL:     str = "M_CTL";          break;
        case M_READ:    str = "M_READ";         break;
        default:        str = "unknown";        break;
        }

        return (str);
}

static char *
usbser_ioctl2str(int ioctl)
{
        char    *str;

        switch (ioctl) {
        case TCGETA:    str = "TCGETA";         break;
        case TCSETA:    str = "TCSETA";         break;
        case TCSETAF:   str = "TCSETAF";        break;
        case TCSETAW:   str = "TCSETAW";        break;
        case TCSBRK:    str = "TCSBRK";         break;
        case TCXONC:    str = "TCXONC";         break;
        case TCFLSH:    str = "TCFLSH";         break;
        case TCGETS:    str = "TCGETS";         break;
        case TCSETS:    str = "TCSETS";         break;
        case TCSETSF:   str = "TCSETSF";        break;
        case TCSETSW:   str = "TCSETSW";        break;
        case TIOCSBRK:  str = "TIOCSBRK";       break;
        case TIOCCBRK:  str = "TIOCCBRK";       break;
        case TIOCMSET:  str = "TIOCMSET";       break;
        case TIOCMBIS:  str = "TIOCMBIS";       break;
        case TIOCMBIC:  str = "TIOCMBIC";       break;
        case TIOCMGET:  str = "TIOCMGET";       break;
        case TIOCSILOOP: str = "TIOCSILOOP";    break;
        case TIOCCILOOP: str = "TIOCCILOOP";    break;
        case TCGETX:    str = "TCGETX";         break;
        case TCSETX:    str = "TCGETX";         break;
        case TCSETXW:   str = "TCGETX";         break;
        case TCSETXF:   str = "TCGETX";         break;
        default:        str = "unknown";        break;
        }

        return (str);
}
#endif
/*
 * Polled IO support
 */

/* called once  by consconfig() when polledio is opened */
static int
usbser_polledio_init(usbser_port_t *pp)
{
        int err;
        usb_pipe_handle_t hdl;
        ds_ops_t *ds_ops = pp->port_ds_ops;

        /* only one serial line console supported */
        if (console_input != NULL)
                return (USB_FAILURE);

        /* check if underlying driver supports polled io */
        if (ds_ops->ds_version < DS_OPS_VERSION_V1 ||
            ds_ops->ds_out_pipe == NULL || ds_ops->ds_in_pipe == NULL)
                return (USB_FAILURE);

        /* init polled input pipe */
        hdl = ds_ops->ds_in_pipe(pp->port_ds_hdl, pp->port_num);
        err = usb_console_input_init(pp->port_usp->us_dip, hdl,
            &console_input_buf, &console_input);
        if (err)
                return (USB_FAILURE);

        /* init polled output pipe */
        hdl = ds_ops->ds_out_pipe(pp->port_ds_hdl, pp->port_num);
        err = usb_console_output_init(pp->port_usp->us_dip, hdl,
            &console_output);
        if (err) {
                (void) usb_console_input_fini(console_input);
                console_input = NULL;
                return (USB_FAILURE);
        }

        return (USB_SUCCESS);
}

/* called once  by consconfig() when polledio is closed */
/*ARGSUSED*/
static void usbser_polledio_fini(usbser_port_t *pp)
{
        /* Since we can't move the console, there is nothing to do. */
}

/*ARGSUSED*/
static void
usbser_polledio_enter(cons_polledio_arg_t arg)
{
        (void) usb_console_input_enter(console_input);
        (void) usb_console_output_enter(console_output);
}

/*ARGSUSED*/
static void
usbser_polledio_exit(cons_polledio_arg_t arg)
{
        (void) usb_console_output_exit(console_output);
        (void) usb_console_input_exit(console_input);
}

/*ARGSUSED*/
static void
usbser_putchar(cons_polledio_arg_t arg, uchar_t c)
{
        static uchar_t cr[2] = {'\r', '\n'};
        uint_t nout;

        if (c == '\n')
                (void) usb_console_write(console_output, cr, 2, &nout);
        else
                (void) usb_console_write(console_output, &c, 1, &nout);
}

/*ARGSUSED*/
static int
usbser_getchar(cons_polledio_arg_t arg)
{
        while (!usbser_ischar(arg))
                ;

        return (*console_input_start++);
}

/*ARGSUSED*/
static boolean_t
usbser_ischar(cons_polledio_arg_t arg)
{
        uint_t num_bytes;

        if (console_input_start < console_input_end)
                return (B_TRUE);

        if (usb_console_read(console_input, &num_bytes) != USB_SUCCESS)
                return (B_FALSE);

        console_input_start = console_input_buf;
        console_input_end = console_input_buf + num_bytes;

        return (num_bytes != 0);
}