root/usr/src/uts/common/io/usb/usba/usba_ugen.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 */

/*
 * Copyright 2019 Joyent, Inc.
 */

/*
 * UGEN: USB Generic Driver support code
 *
 * This code provides entry points called by the ugen driver or other
 * drivers that want to export a ugen interface
 *
 * The "Universal Generic Driver"  (UGEN) for USB devices provides interfaces
 * to  talk to  USB  devices.  This is  very  useful for  Point of Sale sale
 * devices and other simple  devices like  USB  scanner, USB palm  pilot.
 * The UGEN provides a system call interface to USB  devices  enabling
 * a USB device vendor to write an application for their
 * device instead of  writing a driver. This facilitates the vendor to write
 * device management s/w quickly in userland.
 *
 * UGEN supports read/write/poll entry points. An application can be written
 * using  read/write/aioread/aiowrite/poll  system calls to communicate
 * with the device.
 *
 * XXX Theory of Operations
 */
#include <sys/usb/usba/usbai_version.h>
#include <sys/usb/usba.h>
#include <sys/sysmacros.h>
#include <sys/strsun.h>

#include "sys/usb/clients/ugen/usb_ugen.h"
#include "sys/usb/usba/usba_ugen.h"
#include "sys/usb/usba/usba_ugend.h"

/* Debugging information */
uint_t  ugen_errmask            = (uint_t)UGEN_PRINT_ALL;
uint_t  ugen_errlevel           = USB_LOG_L4;
uint_t  ugen_instance_debug     = (uint_t)-1;

/* default endpoint descriptor */
static usb_ep_descr_t  ugen_default_ep_descr =
        {7, 5, 0, USB_EP_ATTR_CONTROL, 8, 0};

/* tunables */
int     ugen_busy_loop          = 60;   /* secs */
int     ugen_ctrl_timeout       = 10;
int     ugen_bulk_timeout       = 10;
int     ugen_intr_timeout       = 10;
int     ugen_enable_pm          = 0;
int     ugen_isoc_buf_limit     = 1000; /* ms */


/* local function prototypes */
static int      ugen_cleanup(ugen_state_t *);
static int      ugen_cpr_suspend(ugen_state_t *);
static void     ugen_cpr_resume(ugen_state_t *);

static void     ugen_restore_state(ugen_state_t *);
static int      ugen_check_open_flags(ugen_state_t *, dev_t, int);
static int      ugen_strategy(struct buf *);
static void     ugen_minphys(struct buf *);

static void     ugen_pm_init(ugen_state_t *);
static void     ugen_pm_destroy(ugen_state_t *);
static void     ugen_pm_busy_component(ugen_state_t *);
static void     ugen_pm_idle_component(ugen_state_t *);

/* endpoint xfer and status management */
static int      ugen_epxs_init(ugen_state_t *);
static void     ugen_epxs_destroy(ugen_state_t *);
static int      ugen_epxs_data_init(ugen_state_t *, usb_ep_data_t *,
                                        uchar_t, uchar_t, uchar_t, uchar_t);
static void     ugen_epxs_data_destroy(ugen_state_t *, ugen_ep_t *);
static int      ugen_epxs_minor_nodes_create(ugen_state_t *,
                                        usb_ep_descr_t *, uchar_t,
                                        uchar_t, uchar_t, uchar_t);
static int      ugen_epxs_check_open_nodes(ugen_state_t *);

static int      ugen_epx_open(ugen_state_t *, dev_t, int);
static void     ugen_epx_close(ugen_state_t *, dev_t, int);
static void     ugen_epx_shutdown(ugen_state_t *);

static int      ugen_epx_open_pipe(ugen_state_t *, ugen_ep_t *, int);
static void     ugen_epx_close_pipe(ugen_state_t *, ugen_ep_t *);

static int      ugen_epx_req(ugen_state_t *, struct buf *);
static int      ugen_epx_ctrl_req(ugen_state_t *, ugen_ep_t *,
                                        struct buf *, boolean_t *);
static void     ugen_epx_ctrl_req_cb(usb_pipe_handle_t, usb_ctrl_req_t *);
static int      ugen_epx_bulk_req(ugen_state_t *, ugen_ep_t *,
                                        struct buf *, boolean_t *);
static void     ugen_epx_bulk_req_cb(usb_pipe_handle_t, usb_bulk_req_t *);
static int      ugen_epx_intr_IN_req(ugen_state_t *, ugen_ep_t *,
                                        struct buf *, boolean_t *);
static int      ugen_epx_intr_IN_start_polling(ugen_state_t *, ugen_ep_t *);
static void     ugen_epx_intr_IN_stop_polling(ugen_state_t *, ugen_ep_t *);
static void     ugen_epx_intr_IN_req_cb(usb_pipe_handle_t, usb_intr_req_t *);
static int      ugen_epx_intr_OUT_req(ugen_state_t *, ugen_ep_t *,
                                        struct buf *, boolean_t *);
static void     ugen_epx_intr_OUT_req_cb(usb_pipe_handle_t, usb_intr_req_t *);
static int      ugen_epx_isoc_IN_req(ugen_state_t *, ugen_ep_t *,
                                        struct buf *, boolean_t *);
static int      ugen_epx_isoc_IN_start_polling(ugen_state_t *, ugen_ep_t *);
static void     ugen_epx_isoc_IN_stop_polling(ugen_state_t *, ugen_ep_t *);
static void     ugen_epx_isoc_IN_req_cb(usb_pipe_handle_t, usb_isoc_req_t *);
static int      ugen_epx_isoc_OUT_req(ugen_state_t *, ugen_ep_t *,
                                        struct buf *, boolean_t *);
static void     ugen_epx_isoc_OUT_req_cb(usb_pipe_handle_t, usb_isoc_req_t *);

static int      ugen_eps_open(ugen_state_t *, dev_t, int);
static void     ugen_eps_close(ugen_state_t *, dev_t, int);
static int      ugen_eps_req(ugen_state_t *, struct buf *);
static void     ugen_update_ep_descr(ugen_state_t *, ugen_ep_t *);

/* device status management */
static int      ugen_ds_init(ugen_state_t *);
static void     ugen_ds_destroy(ugen_state_t *);
static int      ugen_ds_open(ugen_state_t *, dev_t, int);
static void     ugen_ds_close(ugen_state_t *, dev_t, int);
static int      ugen_ds_req(ugen_state_t *, struct buf *);
static void     ugen_ds_change(ugen_state_t *);
static int      ugen_ds_minor_nodes_create(ugen_state_t *);
static void     ugen_ds_poll_wakeup(ugen_state_t *);

/* utility functions */
static int      ugen_minor_index_create(ugen_state_t *, ugen_minor_t);
static ugen_minor_t ugen_devt2minor(ugen_state_t *, dev_t);
static void     ugen_minor_node_table_create(ugen_state_t *);
static void     ugen_minor_node_table_destroy(ugen_state_t *);
static void     ugen_minor_node_table_shrink(ugen_state_t *);
static int      ugen_cr2lcstat(int);
static void     ugen_check_mask(uint_t, uint_t *, uint_t *);
static int      ugen_is_valid_minor_node(ugen_state_t *, dev_t);

static kmutex_t ugen_devt_list_mutex;
static ugen_devt_list_entry_t ugen_devt_list;
static ugen_devt_cache_entry_t ugen_devt_cache[UGEN_DEVT_CACHE_SIZE];
static uint_t   ugen_devt_cache_index;
static void     ugen_store_devt(ugen_state_t *, minor_t);
static ugen_state_t *ugen_devt2state(dev_t);
static void     ugen_free_devt(ugen_state_t *);

/*
 * usb_ugen entry points
 *
 * usb_ugen_get_hdl:
 *      allocate and initialize handle
 */
usb_ugen_hdl_t
usb_ugen_get_hdl(dev_info_t *dip, usb_ugen_info_t *usb_ugen_info)
{
        usb_ugen_hdl_impl_t     *hdl = kmem_zalloc(sizeof (*hdl), KM_SLEEP);
        ugen_state_t            *ugenp = kmem_zalloc(sizeof (ugen_state_t),
            KM_SLEEP);
        uint_t                  len, shift, limit;
        int                     rval;

        hdl->hdl_ugenp = ugenp;

        /* masks may not overlap */
        if (usb_ugen_info->usb_ugen_minor_node_ugen_bits_mask &
            usb_ugen_info->usb_ugen_minor_node_instance_mask) {
                usb_ugen_release_hdl((usb_ugen_hdl_t)hdl);

                return (NULL);
        }

        if ((rval = usb_get_dev_data(dip, &ugenp->ug_dev_data,
            usb_owns_device(dip) ? USB_PARSE_LVL_ALL : USB_PARSE_LVL_IF,
            0)) != USB_SUCCESS) {
                USB_DPRINTF_L2(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "usb_ugen_attach: usb_get_dev_data failed, rval=%d", rval);
                usb_ugen_release_hdl((usb_ugen_hdl_t)hdl);

                return (NULL);
        }

        /* Initialize state structure for this instance */
        mutex_init(&ugenp->ug_mutex, NULL, MUTEX_DRIVER,
            ugenp->ug_dev_data->dev_iblock_cookie);

        mutex_enter(&ugenp->ug_mutex);
        ugenp->ug_dip           = dip;
        ugenp->ug_instance      = ddi_get_instance(dip);
        ugenp->ug_hdl           = hdl;

        /* Allocate a log handle for debug/error messages */
        if (strcmp(ddi_driver_name(dip), "ugen") != 0) {
                char    *name;

                len = strlen(ddi_driver_name(dip)) + sizeof ("_ugen") + 1;
                name = kmem_alloc(len, KM_SLEEP);
                (void) snprintf(name, len, "%s_ugen", ddi_driver_name(dip));

                ugenp->ug_log_hdl = usb_alloc_log_hdl(dip, name, &ugen_errlevel,
                    &ugen_errmask, &ugen_instance_debug, 0);
                hdl->hdl_log_name = name;
                hdl->hdl_log_name_length = len;
        } else {
                ugenp->ug_log_hdl = usb_alloc_log_hdl(dip, "ugen",
                    &ugen_errlevel,
                    &ugen_errmask, &ugen_instance_debug, 0);
        }

        hdl->hdl_dip = dip;
        hdl->hdl_flags = usb_ugen_info->usb_ugen_flags;

        ugen_check_mask(usb_ugen_info->usb_ugen_minor_node_ugen_bits_mask,
            &shift, &limit);
        if (limit == 0) {
                usb_ugen_release_hdl((usb_ugen_hdl_t)hdl);
                mutex_exit(&ugenp->ug_mutex);

                return (NULL);
        }
        hdl->hdl_minor_node_ugen_bits_mask = usb_ugen_info->
            usb_ugen_minor_node_ugen_bits_mask;
        hdl->hdl_minor_node_ugen_bits_shift = shift;
        hdl->hdl_minor_node_ugen_bits_limit = limit;

        ugen_check_mask(usb_ugen_info->usb_ugen_minor_node_instance_mask,
            &shift, &limit);
        if (limit == 0) {
                usb_ugen_release_hdl((usb_ugen_hdl_t)hdl);
                mutex_exit(&ugenp->ug_mutex);

                return (NULL);
        }

        hdl->hdl_minor_node_instance_mask = usb_ugen_info->
            usb_ugen_minor_node_instance_mask;
        hdl->hdl_minor_node_instance_shift = shift;
        hdl->hdl_minor_node_instance_limit = limit;

        USB_DPRINTF_L4(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
            "usb_ugen_get_hdl: instance shift=%d instance limit=%d",
            hdl->hdl_minor_node_instance_shift,
            hdl->hdl_minor_node_instance_limit);

        USB_DPRINTF_L4(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
            "usb_ugen_get_hdl: bits shift=%d bits limit=%d",
            hdl->hdl_minor_node_ugen_bits_shift,
            hdl->hdl_minor_node_ugen_bits_limit);

        mutex_exit(&ugenp->ug_mutex);

        return ((usb_ugen_hdl_t)hdl);
}


/*
 * usb_ugen_release_hdl:
 *      deallocate a handle
 */
void
usb_ugen_release_hdl(usb_ugen_hdl_t usb_ugen_hdl)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;

        if (usb_ugen_hdl_impl) {
                ugen_state_t *ugenp = usb_ugen_hdl_impl->hdl_ugenp;

                if (ugenp) {
                        mutex_destroy(&ugenp->ug_mutex);
                        usb_free_log_hdl(ugenp->ug_log_hdl);
                        usb_free_dev_data(usb_ugen_hdl_impl->hdl_dip,
                            ugenp->ug_dev_data);
                        kmem_free(ugenp, sizeof (*ugenp));
                }
                if (usb_ugen_hdl_impl->hdl_log_name) {
                        kmem_free(usb_ugen_hdl_impl->hdl_log_name,
                            usb_ugen_hdl_impl->hdl_log_name_length);
                }
                kmem_free(usb_ugen_hdl_impl, sizeof (*usb_ugen_hdl_impl));
        }
}


/*
 * usb_ugen_attach()
 */
int
usb_ugen_attach(usb_ugen_hdl_t usb_ugen_hdl, ddi_attach_cmd_t cmd)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        ugen_state_t            *ugenp;
        dev_info_t              *dip;

        if (usb_ugen_hdl == NULL) {

                return (USB_FAILURE);
        }

        ugenp = usb_ugen_hdl_impl->hdl_ugenp;
        dip = usb_ugen_hdl_impl->hdl_dip;


        USB_DPRINTF_L4(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
            "usb_ugen_attach: cmd=%d", cmd);

        switch (cmd) {
        case DDI_ATTACH:

                break;
        case DDI_RESUME:
                ugen_cpr_resume(ugenp);

                return (USB_SUCCESS);
        default:
                USB_DPRINTF_L2(UGEN_PRINT_ATTA, NULL,
                    "usb_ugen_attach: unknown command");

                return (USB_FAILURE);
        }

        mutex_enter(&ugenp->ug_mutex);
        ugenp->ug_ser_cookie =
            usb_init_serialization(dip, USB_INIT_SER_CHECK_SAME_THREAD);
        ugenp->ug_cleanup_flags |= UGEN_INIT_LOCKS;

        /* Get maximum bulk transfer size supported by the HCD */
        if (usb_pipe_get_max_bulk_transfer_size(dip,
            &ugenp->ug_max_bulk_xfer_sz) != USB_SUCCESS) {
                USB_DPRINTF_L2(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "usb_ugen_attach: Getting max bulk xfer sz failed");
                mutex_exit(&ugenp->ug_mutex);

                goto fail;
        }

        /* table for mapping 48 bit minor codes to 9 bit index (for ugen) */
        ugen_minor_node_table_create(ugenp);

        /* prepare device status node handling */
        if (ugen_ds_init(ugenp) != USB_SUCCESS) {
                USB_DPRINTF_L2(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "usb_ugen_attach: preparing dev status failed");
                mutex_exit(&ugenp->ug_mutex);

                goto fail;
        }

        /* prepare all available xfer and status endpoints nodes */
        if (ugen_epxs_init(ugenp) != USB_SUCCESS) {
                USB_DPRINTF_L2(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "usb_ugen_attach: preparing endpoints failed");
                mutex_exit(&ugenp->ug_mutex);

                goto fail;
        }

        /* reduce table size if not all entries are used */
        ugen_minor_node_table_shrink(ugenp);

        /* we are ready to go */
        ugenp->ug_dev_state = USB_DEV_ONLINE;

        mutex_exit(&ugenp->ug_mutex);

        /* prepare PM */
        if (ugenp->ug_hdl->hdl_flags & USB_UGEN_ENABLE_PM) {
                ugen_pm_init(ugenp);
        }

        /*
         * if ugen driver, kill all child nodes otherwise set cfg fails
         * if requested
         */
        if (usb_owns_device(dip) &&
            (usb_ugen_hdl_impl->hdl_flags & USB_UGEN_REMOVE_CHILDREN)) {
                dev_info_t *cdip;

                /* save cfgidx so we can restore on detach */
                mutex_enter(&ugenp->ug_mutex);
                ugenp->ug_initial_cfgidx = usb_get_current_cfgidx(dip);
                mutex_exit(&ugenp->ug_mutex);

                for (cdip = ddi_get_child(dip); cdip; ) {
                        dev_info_t *next = ddi_get_next_sibling(cdip);
                        (void) ddi_remove_child(cdip, 0);
                        cdip = next;
                }
        }

        return (DDI_SUCCESS);
fail:
        USB_DPRINTF_L2(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
            "attach fail");
        (void) ugen_cleanup(ugenp);

        return (DDI_FAILURE);
}


/*
 * usb_ugen_detach()
 */
int
usb_ugen_detach(usb_ugen_hdl_t usb_ugen_hdl, ddi_detach_cmd_t cmd)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        int                     rval = USB_FAILURE;

        if (usb_ugen_hdl) {
                ugen_state_t *ugenp = usb_ugen_hdl_impl->hdl_ugenp;

                USB_DPRINTF_L4(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "usb_ugen_detach cmd %d", cmd);

                switch (cmd) {
                case DDI_DETACH:
                        rval = ugen_cleanup(ugenp);

                        break;
                case DDI_SUSPEND:
                        rval = ugen_cpr_suspend(ugenp);

                        break;
                default:

                        break;
                }
        }

        return (rval);
}


/*
 * ugen_cleanup()
 */
static int
ugen_cleanup(ugen_state_t *ugenp)
{
        dev_info_t *dip = ugenp->ug_dip;

        USB_DPRINTF_L4(UGEN_PRINT_ATTA, ugenp->ug_log_hdl, "ugen_cleanup");

        if (ugenp->ug_cleanup_flags & UGEN_INIT_LOCKS) {

                /* shutdown all endpoints */
                ugen_epx_shutdown(ugenp);

                /*
                 * At this point, no new activity can be initiated.
                 * The driver has disabled hotplug callbacks.
                 * The Solaris framework has disabled
                 * new opens on a device being detached, and does not
                 * allow detaching an open device. PM should power
                 * down while we are detaching
                 *
                 * The following ensures that any other driver
                 * activity must have drained (paranoia)
                 */
                (void) usb_serialize_access(ugenp->ug_ser_cookie,
                    USB_WAIT, 0);
                usb_release_access(ugenp->ug_ser_cookie);

                mutex_enter(&ugenp->ug_mutex);
                ASSERT(ugenp->ug_open_count == 0);
                ASSERT(ugenp->ug_pending_cmds == 0);

                /* dismantle in reverse order */
                ugen_pm_destroy(ugenp);
                ugen_epxs_destroy(ugenp);
                ugen_ds_destroy(ugenp);
                ugen_minor_node_table_destroy(ugenp);


                /* restore to initial configuration */
                if (usb_owns_device(dip) &&
                    (ugenp->ug_dev_state != USB_DEV_DISCONNECTED)) {
                        int idx = ugenp->ug_initial_cfgidx;
                        mutex_exit(&ugenp->ug_mutex);
                        (void) usb_set_cfg(dip, idx,
                            USB_FLAGS_SLEEP, NULL, NULL);
                } else {
                        mutex_exit(&ugenp->ug_mutex);
                }

                usb_fini_serialization(ugenp->ug_ser_cookie);
        }

        ddi_prop_remove_all(dip);
        ddi_remove_minor_node(dip, NULL);

        ugen_free_devt(ugenp);

        return (USB_SUCCESS);
}


/*
 * ugen_cpr_suspend
 */
static int
ugen_cpr_suspend(ugen_state_t *ugenp)
{
        int             rval = USB_FAILURE;
        int             i;
        int             prev_state;

        USB_DPRINTF_L4(UGEN_PRINT_CPR, ugenp->ug_log_hdl,
            "ugen_cpr_suspend:");

        mutex_enter(&ugenp->ug_mutex);
        switch (ugenp->ug_dev_state) {
        case USB_DEV_ONLINE:
        case USB_DEV_DISCONNECTED:
                USB_DPRINTF_L4(UGEN_PRINT_CPR, ugenp->ug_log_hdl,
                    "ugen_cpr_suspend:");

                prev_state = ugenp->ug_dev_state;
                ugenp->ug_dev_state = USB_DEV_SUSPENDED;

                if (ugenp->ug_open_count) {
                        /* drain outstanding cmds */
                        for (i = 0; i < ugen_busy_loop; i++) {
                                if (ugenp->ug_pending_cmds == 0) {

                                        break;
                                }
                                mutex_exit(&ugenp->ug_mutex);
                                delay(drv_usectohz(100000));
                                mutex_enter(&ugenp->ug_mutex);
                        }

                        /* if still outstanding cmds, fail suspend */
                        if (ugenp->ug_pending_cmds) {
                                ugenp->ug_dev_state = prev_state;

                                USB_DPRINTF_L2(UGEN_PRINT_CPR,
                                    ugenp->ug_log_hdl,
                                    "ugen_cpr_suspend: pending %d",
                                    ugenp->ug_pending_cmds);

                                rval =  USB_FAILURE;
                                break;
                        }

                        mutex_exit(&ugenp->ug_mutex);
                        (void) usb_serialize_access(ugenp->ug_ser_cookie,
                            USB_WAIT, 0);
                        /* close all pipes */
                        ugen_epx_shutdown(ugenp);

                        usb_release_access(ugenp->ug_ser_cookie);

                        mutex_enter(&ugenp->ug_mutex);
                }

                /* wakeup devstat reads and polls */
                ugen_ds_change(ugenp);
                ugen_ds_poll_wakeup(ugenp);

                rval = USB_SUCCESS;
                break;
        case USB_DEV_SUSPENDED:
        case USB_UGEN_DEV_UNAVAILABLE_RESUME:
        case USB_UGEN_DEV_UNAVAILABLE_RECONNECT:
        default:

                break;
        }
        mutex_exit(&ugenp->ug_mutex);

        return (rval);
}

/*
 * ugen_cpr_resume
 */
static void
ugen_cpr_resume(ugen_state_t *ugenp)
{
        USB_DPRINTF_L4(UGEN_PRINT_CPR, ugenp->ug_log_hdl,
            "ugen_cpr_resume:");

        ugen_restore_state(ugenp);
}

/*
 * usb_ugen_disconnect_ev_cb:
 */
int
usb_ugen_disconnect_ev_cb(usb_ugen_hdl_t usb_ugen_hdl)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        ugen_state_t            *ugenp;

        if (usb_ugen_hdl_impl == NULL) {

                return (USB_FAILURE);
        }

        ugenp = usb_ugen_hdl_impl->hdl_ugenp;

        USB_DPRINTF_L4(UGEN_PRINT_HOTPLUG, ugenp->ug_log_hdl,
            "usb_ugen_disconnect_ev_cb:");

        /* get exclusive access */
        (void) usb_serialize_access(ugenp->ug_ser_cookie, USB_WAIT, 0);

        mutex_enter(&ugenp->ug_mutex);
        ugenp->ug_dev_state = USB_DEV_DISCONNECTED;
        if (ugenp->ug_open_count) {
                mutex_exit(&ugenp->ug_mutex);

                /* close all pipes */
                (void) ugen_epx_shutdown(ugenp);

                mutex_enter(&ugenp->ug_mutex);
        }


        /* wakeup devstat reads and polls */
        ugen_ds_change(ugenp);
        ugen_ds_poll_wakeup(ugenp);

        mutex_exit(&ugenp->ug_mutex);
        usb_release_access(ugenp->ug_ser_cookie);

        return (USB_SUCCESS);
}


/*
 * usb_ugen_reconnect_ev_cb:
 */
int
usb_ugen_reconnect_ev_cb(usb_ugen_hdl_t usb_ugen_hdl)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        ugen_state_t            *ugenp = usb_ugen_hdl_impl->hdl_ugenp;

        USB_DPRINTF_L4(UGEN_PRINT_HOTPLUG, ugenp->ug_log_hdl,
            "usb_ugen_reconnect_ev_cb:");

        ugen_restore_state(ugenp);

        return (USB_SUCCESS);
}


/*
 * ugen_restore_state:
 *      Check for same device; if a different device is attached, set
 *      the device status to disconnected.
 *      If we were open, then set to UNAVAILABLE until all endpoints have
 *      be closed.
 */
static void
ugen_restore_state(ugen_state_t *ugenp)
{
        dev_info_t *dip = ugenp->ug_dip;

        USB_DPRINTF_L4(UGEN_PRINT_HOTPLUG, ugenp->ug_log_hdl,
            "ugen_restore_state");

        /* first raise power */
        if (ugenp->ug_hdl->hdl_flags & USB_UGEN_ENABLE_PM) {
                ugen_pm_busy_component(ugenp);
                (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
        }

        /* Check if we are talking to the same device */
        if (usb_check_same_device(dip, ugenp->ug_log_hdl,
            USB_LOG_L0, UGEN_PRINT_HOTPLUG, USB_CHK_ALL, NULL) ==
            USB_FAILURE) {
                mutex_enter(&ugenp->ug_mutex);
                ugenp->ug_dev_state = USB_DEV_DISCONNECTED;

                /* wakeup devstat reads and polls */
                ugen_ds_change(ugenp);
                ugen_ds_poll_wakeup(ugenp);

                mutex_exit(&ugenp->ug_mutex);

                if (ugenp->ug_hdl->hdl_flags & USB_UGEN_ENABLE_PM) {
                        ugen_pm_idle_component(ugenp);
                }

                return;
        }

        /*
         * get exclusive access, we don't want to change state in the
         * middle of some other actions
         */
        (void) usb_serialize_access(ugenp->ug_ser_cookie, USB_WAIT, 0);

        mutex_enter(&ugenp->ug_mutex);
        switch (ugenp->ug_dev_state) {
        case USB_DEV_DISCONNECTED:
                ugenp->ug_dev_state = (ugenp->ug_open_count == 0) ?
                    USB_DEV_ONLINE : USB_UGEN_DEV_UNAVAILABLE_RECONNECT;

                break;
        case USB_DEV_SUSPENDED:
                ugenp->ug_dev_state = (ugenp->ug_open_count == 0) ?
                    USB_DEV_ONLINE : USB_UGEN_DEV_UNAVAILABLE_RESUME;

                break;
        }
        USB_DPRINTF_L4(UGEN_PRINT_HOTPLUG, ugenp->ug_log_hdl,
            "ugen_restore_state: state=%d, opencount=%d",
            ugenp->ug_dev_state, ugenp->ug_open_count);

        /* wakeup devstat reads and polls */
        ugen_ds_change(ugenp);
        ugen_ds_poll_wakeup(ugenp);

        mutex_exit(&ugenp->ug_mutex);
        usb_release_access(ugenp->ug_ser_cookie);

        if (ugenp->ug_hdl->hdl_flags & USB_UGEN_ENABLE_PM) {
                ugen_pm_idle_component(ugenp);
        }
}


/*
 * usb_ugen_open:
 */
/* ARGSUSED */
int
usb_ugen_open(usb_ugen_hdl_t usb_ugen_hdl, dev_t *devp, int flag, int sflag,
    cred_t *cr)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        ugen_state_t            *ugenp;
        int                     rval;
        int                     minor_node_type;

        if (usb_ugen_hdl == NULL) {

                return (EINVAL);
        }

        ugenp = usb_ugen_hdl_impl->hdl_ugenp;

        if (ugen_is_valid_minor_node(ugenp, *devp) != USB_SUCCESS) {

                return (EINVAL);
        }

        minor_node_type = UGEN_MINOR_TYPE(ugenp, *devp);

        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "usb_ugen_open: minor=%u", getminor(*devp));
        USB_DPRINTF_L3(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "cfgval=%" PRIu64 " cfgidx=%" PRIu64 " if=%" PRIu64
            " alt=%" PRIu64 " epidx=%" PRIu64 " type=0x%" PRIx64,
            UGEN_MINOR_CFGVAL(ugenp, *devp), UGEN_MINOR_CFGIDX(ugenp, *devp),
            UGEN_MINOR_IF(ugenp, *devp), UGEN_MINOR_ALT(ugenp, *devp),
            UGEN_MINOR_EPIDX(ugenp, *devp), UGEN_MINOR_TYPE(ugenp, *devp));

        /* first check for legal open flags */
        if ((rval = ugen_check_open_flags(ugenp, *devp, flag)) != 0) {
                USB_DPRINTF_L2(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "usb_ugen_open: check failed, rval=%d", rval);

                return (rval);
        }

        /* exclude other threads including other opens */
        if (usb_serialize_access(ugenp->ug_ser_cookie,
            USB_WAIT_SIG, 0) <= 0) {
                USB_DPRINTF_L2(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "usb_ugen_open: interrupted");

                return (EINTR);
        }

        mutex_enter(&ugenp->ug_mutex);

        /* always allow open of dev stat node */
        if (minor_node_type != UGEN_MINOR_DEV_STAT_NODE) {

                /* if we are not online or powered down, fail open */
                switch (ugenp->ug_dev_state) {
                case USB_DEV_ONLINE:

                        break;
                case USB_DEV_DISCONNECTED:
                        rval = ENODEV;
                        mutex_exit(&ugenp->ug_mutex);

                        goto done;
                case USB_DEV_SUSPENDED:
                case USB_UGEN_DEV_UNAVAILABLE_RESUME:
                case USB_UGEN_DEV_UNAVAILABLE_RECONNECT:
                default:
                        rval = EBADF;
                        mutex_exit(&ugenp->ug_mutex);

                        goto done;
                }
        }
        mutex_exit(&ugenp->ug_mutex);

        /* open node depending on type */
        switch (minor_node_type) {
        case UGEN_MINOR_EP_XFER_NODE:
                if (ugenp->ug_hdl->hdl_flags & USB_UGEN_ENABLE_PM) {
                        ugen_pm_busy_component(ugenp);
                        (void) pm_raise_power(ugenp->ug_dip, 0,
                            USB_DEV_OS_FULL_PWR);
                }

                rval = ugen_epx_open(ugenp, *devp, flag);
                if (rval == 0) {
                        mutex_enter(&ugenp->ug_mutex);
                        ugenp->ug_open_count++;
                        mutex_exit(&ugenp->ug_mutex);
                } else {
                        if (ugenp->ug_hdl->hdl_flags &
                            USB_UGEN_ENABLE_PM) {
                                ugen_pm_idle_component(ugenp);
                        }
                }

                break;
        case UGEN_MINOR_EP_STAT_NODE:
                rval = ugen_eps_open(ugenp, *devp, flag);
                if (rval == 0) {
                        mutex_enter(&ugenp->ug_mutex);
                        ugenp->ug_open_count++;
                        mutex_exit(&ugenp->ug_mutex);
                }

                break;
        case UGEN_MINOR_DEV_STAT_NODE:
                rval = ugen_ds_open(ugenp, *devp, flag);

                break;
        default:
                rval = EINVAL;

                break;
        }
done:
        mutex_enter(&ugenp->ug_mutex);

        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "usb_ugen_open: minor=0x%x rval=%d state=%d cnt=%d",
            getminor(*devp), rval, ugenp->ug_dev_state,
            ugenp->ug_open_count);

        mutex_exit(&ugenp->ug_mutex);

        usb_release_access(ugenp->ug_ser_cookie);

        return (rval);
}


/*
 * usb_ugen_close()
 */
/* ARGSUSED */
int
usb_ugen_close(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, int flag, int otype,
    cred_t *cr)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        ugen_state_t            *ugenp;
        int                     minor_node_type;

        if (usb_ugen_hdl == NULL) {

                return (EINVAL);
        }

        ugenp = usb_ugen_hdl_impl->hdl_ugenp;
        if (ugen_is_valid_minor_node(ugenp, dev) != USB_SUCCESS) {

                return (EINVAL);
        }

        minor_node_type = UGEN_MINOR_TYPE(ugenp, dev);

        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "usb_ugen_close: minor=0x%x", getminor(dev));

        /* exclude other threads, including other opens */
        if (usb_serialize_access(ugenp->ug_ser_cookie,
            USB_WAIT_SIG, 0) <= 0) {
                USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "usb_ugen_close: interrupted");

                return (EINTR);
        }

        /* close node depending on type */
        switch (minor_node_type) {
        case UGEN_MINOR_EP_XFER_NODE:
                ugen_epx_close(ugenp, dev, flag);
                if (ugenp->ug_hdl->hdl_flags & USB_UGEN_ENABLE_PM) {
                        ugen_pm_idle_component(ugenp);
                }

                break;
        case UGEN_MINOR_EP_STAT_NODE:
                ugen_eps_close(ugenp, dev, flag);

                break;
        case UGEN_MINOR_DEV_STAT_NODE:
                ugen_ds_close(ugenp, dev, flag);

                break;
        default:
                usb_release_access(ugenp->ug_ser_cookie);

                return (EINVAL);
        }

        mutex_enter(&ugenp->ug_mutex);
        if (minor_node_type != UGEN_MINOR_DEV_STAT_NODE) {
                ASSERT(ugenp->ug_open_count > 0);
                if ((--ugenp->ug_open_count == 0) &&
                    ((ugenp->ug_dev_state == USB_UGEN_DEV_UNAVAILABLE_RESUME) ||
                    (ugenp->ug_dev_state ==
                    USB_UGEN_DEV_UNAVAILABLE_RECONNECT))) {
                        ugenp->ug_dev_state = USB_DEV_ONLINE;

                        /* wakeup devstat reads and polls */
                        ugen_ds_change(ugenp);
                        ugen_ds_poll_wakeup(ugenp);
                }
        }

        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "usb_ugen_close: minor=0x%x state=%d cnt=%d",
            getminor(dev), ugenp->ug_dev_state, ugenp->ug_open_count);

        if (ugenp->ug_open_count == 0) {
                ASSERT(ugen_epxs_check_open_nodes(ugenp) == USB_FAILURE);
        }

        mutex_exit(&ugenp->ug_mutex);

        usb_release_access(ugenp->ug_ser_cookie);

        return (0);
}


/*
 * usb_ugen_read/write()
 */
/*ARGSUSED*/
int
usb_ugen_read(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, struct uio *uiop,
    cred_t *credp)
{
        ugen_state_t            *ugenp;
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;

        if (usb_ugen_hdl == NULL) {

                return (EINVAL);
        }
        ugenp = usb_ugen_hdl_impl->hdl_ugenp;

        if (ugen_is_valid_minor_node(ugenp, dev) != USB_SUCCESS) {

                return (EINVAL);
        }

        return (physio(ugen_strategy,
            (struct buf *)0, dev, B_READ, ugen_minphys, uiop));
}


/*ARGSUSED*/
int
usb_ugen_write(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, struct uio *uiop,
    cred_t *credp)
{
        ugen_state_t            *ugenp;
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;

        if (usb_ugen_hdl == NULL) {

                return (EINVAL);
        }
        ugenp = usb_ugen_hdl_impl->hdl_ugenp;

        if (ugen_is_valid_minor_node(ugenp, dev) != USB_SUCCESS) {

                return (EINVAL);
        }

        return (physio(ugen_strategy,
            (struct buf *)0, dev, B_WRITE, ugen_minphys, uiop));
}


/*
 * usb_ugen_poll
 */
int
usb_ugen_poll(usb_ugen_hdl_t usb_ugen_hdl, dev_t dev, short events,
    int anyyet,  short *reventsp, struct pollhead **phpp)
{
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        ugen_state_t            *ugenp;
        int                     minor_node_type;
        uint_t                  ep_index;
        ugen_ep_t               *epp;

        if (usb_ugen_hdl == NULL) {

                return (EINVAL);
        }

        ugenp = usb_ugen_hdl_impl->hdl_ugenp;
        if (ugen_is_valid_minor_node(ugenp, dev) != USB_SUCCESS) {

                return (EINVAL);
        }

        minor_node_type = UGEN_MINOR_TYPE(ugenp, dev);
        ep_index        = UGEN_MINOR_EPIDX(ugenp, dev);
        epp             = &ugenp->ug_ep[ep_index];

        mutex_enter(&ugenp->ug_mutex);

        USB_DPRINTF_L4(UGEN_PRINT_POLL, ugenp->ug_log_hdl,
            "usb_ugen_poll: "
            "dev=0x%lx events=0x%x anyyet=0x%x rev=0x%p type=%d "
            "devstat=0x%x devstate=0x%x",
            dev, events, anyyet, (void *)reventsp, minor_node_type,
            ugenp->ug_ds.dev_stat, ugenp->ug_ds.dev_state);

        *reventsp = 0;

        if (ugenp->ug_dev_state == USB_DEV_ONLINE) {
                switch (minor_node_type) {
                case UGEN_MINOR_EP_XFER_NODE:
                        /* if interrupt IN ep and there is data, set POLLIN */
                        if ((UGEN_XFER_TYPE(epp) == USB_EP_ATTR_INTR) &&
                            (UGEN_XFER_DIR(epp) & USB_EP_DIR_IN)) {

                                /*
                                 * if we are not polling, force another
                                 * read to kick off polling
                                 */
                                mutex_enter(&epp->ep_mutex);
                                if ((epp->ep_data) ||
                                    ((epp->ep_state &
                                    UGEN_EP_STATE_INTR_IN_POLLING_ON) == 0)) {
                                        *reventsp |= POLLIN;
                                }

                                if ((!*reventsp && !anyyet) ||
                                    (events & POLLET)) {
                                        *phpp = &epp->ep_pollhead;
                                        epp->ep_state |=
                                            UGEN_EP_STATE_INTR_IN_POLL_PENDING;
                                }
                                mutex_exit(&epp->ep_mutex);

                        } else if ((UGEN_XFER_TYPE(epp) == USB_EP_ATTR_ISOCH) &&
                            (UGEN_XFER_DIR(epp) & USB_EP_DIR_IN)) {

                                /*
                                 * if we are not polling, force another
                                 * read to kick off polling
                                 */
                                mutex_enter(&epp->ep_mutex);
                                if ((epp->ep_data) ||
                                    ((epp->ep_state &
                                    UGEN_EP_STATE_ISOC_IN_POLLING_ON) == 0)) {
                                        *reventsp |= POLLIN;
                                }

                                if ((!*reventsp && !anyyet) ||
                                    (events & POLLET)) {
                                        *phpp = &epp->ep_pollhead;
                                        epp->ep_state |=
                                            UGEN_EP_STATE_ISOC_IN_POLL_PENDING;
                                }
                                mutex_exit(&epp->ep_mutex);

                        } else {
                                /* no poll on other ep nodes */
                                *reventsp |= POLLERR;
                        }

                        break;
                case UGEN_MINOR_DEV_STAT_NODE:
                        if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED)
                                *reventsp |= POLLIN;

                        if ((!*reventsp && !anyyet) || (events & POLLET)) {
                                *phpp = &ugenp->ug_ds.dev_pollhead;
                                ugenp->ug_ds.dev_stat |=
                                    UGEN_DEV_STATUS_POLL_PENDING;
                        }

                        break;
                case UGEN_MINOR_EP_STAT_NODE:
                default:
                        *reventsp |= POLLERR;

                        break;
                }
        } else {
                if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED)
                        *reventsp |= POLLHUP|POLLIN;

                if ((!*reventsp && !anyyet) || (events & POLLET)) {
                        *phpp = &ugenp->ug_ds.dev_pollhead;
                        ugenp->ug_ds.dev_stat |=
                            UGEN_DEV_STATUS_POLL_PENDING;
                }
        }

        mutex_exit(&ugenp->ug_mutex);

        USB_DPRINTF_L4(UGEN_PRINT_POLL, ugenp->ug_log_hdl,
            "usb_ugen_poll end: reventsp=0x%x", *reventsp);

        return (0);
}


/*
 * ugen_strategy
 */
static int
ugen_strategy(struct buf *bp)
{
        dev_t           dev = bp->b_edev;
        int             rval = 0;
        ugen_state_t    *ugenp = ugen_devt2state(dev);
        int             minor_node_type = UGEN_MINOR_TYPE(ugenp, dev);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_strategy: bp=0x%p minor=0x%x", (void *)bp, getminor(dev));

        if (ugen_is_valid_minor_node(ugenp, dev) != USB_SUCCESS) {

                return (EINVAL);
        }

        mutex_enter(&ugenp->ug_mutex);
        ugenp->ug_pending_cmds++;
        mutex_exit(&ugenp->ug_mutex);

        bp_mapin(bp);

        switch (minor_node_type) {
        case UGEN_MINOR_EP_XFER_NODE:
                rval = ugen_epx_req(ugenp, bp);

                break;
        case UGEN_MINOR_EP_STAT_NODE:
                rval = ugen_eps_req(ugenp, bp);

                break;
        case UGEN_MINOR_DEV_STAT_NODE:
                rval = ugen_ds_req(ugenp, bp);

                break;
        default:
                rval = EINVAL;

                break;
        }

        mutex_enter(&ugenp->ug_mutex);
        ugenp->ug_pending_cmds--;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_strategy: "
            "bp=0x%p cnt=%lu resid=%lu err=%d minor=0x%x rval=%d #cmds=%d",
            (void *)bp, bp->b_bcount, bp->b_resid, geterror(bp),
            getminor(dev), rval, ugenp->ug_pending_cmds);

        mutex_exit(&ugenp->ug_mutex);

        if (rval) {
                if (geterror(bp) == 0) {
                        bioerror(bp, rval);
                }
        }

        biodone(bp);

        return (0);
}


/*
 * ugen_minphys:
 */
static void
ugen_minphys(struct buf *bp)
{
        dev_t           dev = bp->b_edev;
        ugen_state_t    *ugenp = ugen_devt2state(dev);
        int             minor_node_type = UGEN_MINOR_TYPE(ugenp, dev);
        uint_t          ep_index = UGEN_MINOR_EPIDX(ugenp, dev);
        ugen_ep_t       *epp = &ugenp->ug_ep[ep_index];

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_phys: bp=0x%p dev=0x%lx index=%d type=0x%x",
            (void *)bp, dev, ep_index, minor_node_type);

        switch (minor_node_type) {
        case UGEN_MINOR_EP_XFER_NODE:
                switch (UGEN_XFER_TYPE(epp)) {
                case USB_EP_ATTR_BULK:
                        if (bp->b_bcount > ugenp->ug_max_bulk_xfer_sz) {
                                bp->b_bcount = ugenp->ug_max_bulk_xfer_sz;
                        }

                        break;
                case USB_EP_ATTR_INTR:
                case USB_EP_ATTR_CONTROL:
                case USB_EP_ATTR_ISOCH:
                default:

                        break;
                }
                break;
        case UGEN_MINOR_EP_STAT_NODE:
        case UGEN_MINOR_DEV_STAT_NODE:
        default:

                break;
        }
}

/*
 * Get bmAttributes and bAddress of the endpoint which is going to
 * be opened
 */
static int
ugen_get_ep_descr(ugen_state_t *ugenp, dev_t dev, uint8_t *bmAttr,
    uint8_t *bAddr)
{
        uint_t  alt = UGEN_MINOR_ALT(ugenp, dev);
        uint_t  ifc = UGEN_MINOR_IF(ugenp, dev);
        uint_t  cfgidx = UGEN_MINOR_CFGIDX(ugenp, dev);
        usb_cfg_data_t  *dev_cfg;
        usb_if_data_t   *if_data;
        usb_alt_if_data_t *alt_if_data;
        usb_ep_data_t   *ep_data;
        int ep;
        int epidx = UGEN_MINOR_EPIDX(ugenp, dev);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "cfg=%d, if=%d, alt=%d, ep=0x%x", cfgidx, ifc,
            alt, epidx);

        dev_cfg = &ugenp->ug_dev_data->dev_cfg[cfgidx];
        if_data = &dev_cfg->cfg_if[ifc];
        alt_if_data = &if_data->if_alt[alt];
        for (ep = 0; ep < alt_if_data->altif_n_ep; ep++) {
                ep_data = &alt_if_data->altif_ep[ep];

                if (usb_get_ep_index(ep_data->ep_descr.
                    bEndpointAddress) == epidx) {

                        *bmAttr = ep_data->ep_descr.bmAttributes;
                        *bAddr = ep_data->ep_descr.bEndpointAddress;

                        return (USB_SUCCESS);
                }
        }

        return (USB_FAILURE);
}

/*
 * check whether flag is appropriate for node type
 */
static int
ugen_check_open_flags(ugen_state_t *ugenp, dev_t dev, int flag)
{
        ugen_ep_t *epp;
        int     minor_node_type = UGEN_MINOR_TYPE(ugenp, dev);
        int     rval = 0;
        uint8_t bmAttribute;
        uint8_t bAddress;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_check_open_flags: "
            "dev=0x%lx, type=0x%x flag=0x%x idx=%" PRIu64,
            dev, minor_node_type, flag, UGEN_MINOR_EPIDX(ugenp, dev));

        switch (minor_node_type) {
        case UGEN_MINOR_EP_XFER_NODE:
                epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, dev)];

                /*
                 * Endpoints in two altsetting happen to have the same
                 * bEndpointAddress, but they are different type, e.g,
                 * one is BULK and the other is ISOC. They use the same
                 * slot of ug_ep array. It's OK after switch_alt, because
                 * after alt switch, ep info is updated to the new endpoint.
                 * But it's not right here to use the other EP's info for
                 * checking.
                 */
                if (UGEN_MINOR_EPIDX(ugenp, dev) != 0) {
                        if ((rval = ugen_get_ep_descr(ugenp, dev, &bmAttribute,
                            &bAddress)) != USB_SUCCESS) {
                                USB_DPRINTF_L2(UGEN_PRINT_XFER,
                                    ugenp->ug_log_hdl, "ugen_get_descr: fail");

                                return (ENODEV);
                        }
                } else {
                        bmAttribute = ugen_default_ep_descr.bmAttributes;
                        bAddress = ugen_default_ep_descr.bEndpointAddress;
                }

                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_check_open_flags: epp = %p,"
                    "epp type = %d, bmAttr =0x%x, bAddr = 0x%02x", (void *)epp,
                    UGEN_XFER_TYPE(epp), bmAttribute, bAddress);

                switch (bmAttribute & USB_EP_ATTR_MASK) {
                case USB_EP_ATTR_CONTROL:
                        /* read and write must be set, ndelay not allowed */
                        if (((flag & (FREAD | FWRITE)) != (FREAD | FWRITE)) ||
                            (flag & (FNDELAY | FNONBLOCK))) {
                                rval = EACCES;
                        }

                        break;
                case USB_EP_ATTR_ISOCH:
                        /* read and write must be set */
                        if ((flag & (FREAD | FWRITE)) != (FREAD | FWRITE)) {
                                rval = EACCES;
                        }

                        break;
                case USB_EP_ATTR_BULK:
                        /* ndelay not allowed */
                        if (flag & (FNDELAY | FNONBLOCK)) {
                                rval = EACCES;

                                break;
                        }
                        /*FALLTHRU*/
                case USB_EP_ATTR_INTR:
                        /* check flag versus direction */
                        if ((flag & FWRITE) && (bAddress & USB_EP_DIR_IN)) {
                                rval = EACCES;
                        }
                        if ((flag & FREAD) &&
                            ((bAddress & USB_EP_DIR_IN) == 0)) {
                                rval = EACCES;
                        }

                        break;
                default:
                        rval = EINVAL;

                        break;
                }
                break;
        case UGEN_MINOR_DEV_STAT_NODE:
                /* only reads are supported */
                if (flag & FWRITE) {
                        rval = EACCES;
                }

                break;
        case UGEN_MINOR_EP_STAT_NODE:

                break;
        default:
                rval = EINVAL;

                break;
        }

        return (rval);
}


/*
 * endpoint management
 *
 * create/initialize all endpoint xfer/stat structures
 */
static int
ugen_epxs_init(ugen_state_t *ugenp)
{
        usb_cfg_data_t  *dev_cfg = ugenp->ug_dev_data->dev_cfg;
        uchar_t         cfgidx, cfgval, iface, alt, ep;
        usb_if_data_t   *if_data;
        usb_alt_if_data_t *alt_if_data;
        usb_ep_data_t   *ep_data;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epxs_init:");

        /* initialize each ep's mutex first */
        for (ep = 0; ep < UGEN_N_ENDPOINTS; ep++) {
                mutex_init(&ugenp->ug_ep[ep].ep_mutex, NULL, MUTEX_DRIVER,
                    ugenp->ug_dev_data->dev_iblock_cookie);
        }

        /* init default ep as it does not have a descriptor */
        if (ugen_epxs_data_init(ugenp, NULL, 0, 0,
            ugenp->ug_dev_data->dev_curr_if, 0) != USB_SUCCESS) {
                USB_DPRINTF_L2(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "creating default endpoint failed");

                return (USB_FAILURE);
        }

        /*
         * walk all endpoints of all alternates of all interfaces of
         * all cfs
         */
        for (cfgidx = 0; cfgidx < ugenp->ug_dev_data->dev_n_cfg; cfgidx++) {
                dev_cfg = &ugenp->ug_dev_data->dev_cfg[cfgidx];
                cfgval = dev_cfg->cfg_descr.bConfigurationValue;
                for (iface = 0; iface < dev_cfg->cfg_n_if; iface++) {
                        if_data = &dev_cfg->cfg_if[iface];
                        for (alt = 0; alt < if_data->if_n_alt; alt++) {
                                alt_if_data = &if_data->if_alt[alt];
                                for (ep = 0; ep < alt_if_data->altif_n_ep;
                                    ep++) {
                                        ep_data = &alt_if_data->altif_ep[ep];
                                        if (ugen_epxs_data_init(ugenp, ep_data,
                                            cfgval, cfgidx, iface, alt) !=
                                            USB_SUCCESS) {

                                                return (USB_FAILURE);
                                        }
                                }
                        }
                }
        }

        return (USB_SUCCESS);
}


/*
 * initialize one endpoint structure
 */
static int
ugen_epxs_data_init(ugen_state_t *ugenp, usb_ep_data_t *ep_data,
    uchar_t cfgval, uchar_t cfgidx, uchar_t iface, uchar_t alt)
{
        int                     ep_index;
        ugen_ep_t               *epp;
        usb_ep_descr_t          *ep_descr;

        /* is this the default endpoint */
        ep_index = (ep_data == NULL) ? 0 :
            usb_get_ep_index(ep_data->ep_descr.bEndpointAddress);
        epp = &ugenp->ug_ep[ep_index];

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epxs_data_init: "
            "cfgval=%d cfgidx=%d iface=%d alt=%d ep_index=%d",
            cfgval, cfgidx, iface, alt, ep_index);

        ep_descr = (ep_data == NULL) ? &ugen_default_ep_descr :
            &ep_data->ep_descr;

        mutex_init(&epp->ep_mutex, NULL, MUTEX_DRIVER,
            ugenp->ug_dev_data->dev_iblock_cookie);

        mutex_enter(&epp->ep_mutex);

        /* initialize if not yet init'ed */
        if (epp->ep_state == UGEN_EP_STATE_NONE) {
                epp->ep_descr           = *ep_descr;
                epp->ep_cfgidx          = cfgidx;
                epp->ep_if              = iface;
                epp->ep_alt             = alt;
                epp->ep_state           = UGEN_EP_STATE_ACTIVE;
                epp->ep_lcmd_status     = USB_LC_STAT_NOERROR;
                epp->ep_pipe_policy.pp_max_async_reqs = 1;

                if (ep_data == NULL) {
                        bzero(&epp->ep_xdescr, sizeof (usb_ep_xdescr_t));
                        epp->ep_xdescr.uex_version =
                            USB_EP_XDESCR_CURRENT_VERSION;
                        epp->ep_xdescr.uex_ep = *ep_descr;
                } else {
                        /*
                         * The only way this could fail is we have a bad
                         * version, which shouldn't be possible inside of the
                         * usba module itself.
                         */
                        (void) usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
                            ugenp->ug_dip, ep_data, &epp->ep_xdescr);
                }

                cv_init(&epp->ep_wait_cv, NULL, CV_DRIVER, NULL);
                epp->ep_ser_cookie      = usb_init_serialization(
                    ugenp->ug_dip, 0);
        }

        mutex_exit(&epp->ep_mutex);

        /* create minor nodes for all alts */

        return (ugen_epxs_minor_nodes_create(ugenp, ep_descr,
            cfgval, cfgidx, iface, alt));
}


/*
 * undo all endpoint initializations
 */
static void
ugen_epxs_destroy(ugen_state_t *ugenp)
{
        int     i;

        for (i = 0; i < UGEN_N_ENDPOINTS; i++) {
                ugen_epxs_data_destroy(ugenp, &ugenp->ug_ep[i]);
        }
}


static void
ugen_epxs_data_destroy(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        if (epp) {
                ASSERT(epp->ep_ph == NULL);
                mutex_enter(&epp->ep_mutex);
                if (epp->ep_state != UGEN_EP_STATE_NONE) {
                        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_epxs_destroy: addr=0x%x",
                            UGEN_XFER_ADDR(epp));
                        cv_destroy(&epp->ep_wait_cv);
                }
                mutex_exit(&epp->ep_mutex);

                mutex_destroy(&epp->ep_mutex);
                usb_fini_serialization(epp->ep_ser_cookie);
        }
}


/*
 * create endpoint status and xfer minor nodes
 *
 * The actual minor node needs more than 18 bits. We create a table
 * and store the full minor node in this table and use the
 * index in the table as minor node. This allows 256 minor nodes
 * and 1024 instances
 */
static int
ugen_epxs_minor_nodes_create(ugen_state_t *ugenp, usb_ep_descr_t *ep_descr,
    uchar_t cfgval, uchar_t cfgidx, uchar_t iface, uchar_t alt)
{
        char            node_name[32], *type;
        int             vid = ugenp->ug_dev_data->dev_descr->idVendor;
        int             pid = ugenp->ug_dev_data->dev_descr->idProduct;
        minor_t         minor;
        int             minor_index;
        ugen_minor_t    minor_code, minor_code_base;
        int             owns_device = (usb_owns_device(ugenp->ug_dip) ?
            UGEN_OWNS_DEVICE : 0);
        int             ep_index =
            usb_get_ep_index(ep_descr->bEndpointAddress);
        int             ep_addr =
            ep_descr->bEndpointAddress & USB_EP_NUM_MASK;
        int             ep_type =
            ep_descr->bmAttributes & USB_EP_ATTR_MASK;
        int             ep_dir =
            ep_descr->bEndpointAddress & USB_EP_DIR_IN;

        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "ugen_epxs_minor_nodes_create: "
            "cfgval=%d cfgidx=%d if=%d alt=%d ep=0x%x",
            cfgval, cfgidx, iface, alt, ep_addr);

        if (ugenp->ug_instance >= UGEN_MINOR_INSTANCE_LIMIT(ugenp)) {
                USB_DPRINTF_L0(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "instance number too high (%d)", ugenp->ug_instance);

                return (USB_FAILURE);
        }

        /* create stat and xfer minor node */
        minor_code_base =
            ((ugen_minor_t)cfgval) << UGEN_MINOR_CFGVAL_SHIFT |
            ((ugen_minor_t)cfgidx) << UGEN_MINOR_CFGIDX_SHIFT |
            iface << UGEN_MINOR_IF_SHIFT |
            alt << UGEN_MINOR_ALT_SHIFT |
            ep_index << UGEN_MINOR_EPIDX_SHIFT | owns_device;
        minor_code = minor_code_base | UGEN_MINOR_EP_XFER_NODE;

        minor_index = ugen_minor_index_create(ugenp, minor_code);
        if (minor_index < 0) {
                USB_DPRINTF_L1(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "too many minor nodes, "
                    "cannot create %d.%d.%d.%x",
                    cfgval, iface, alt, ep_addr);
                /* carry on regardless */

                return (USB_SUCCESS);
        }
        minor = (minor_index << UGEN_MINOR_IDX_SHIFT(ugenp)) |
            ugenp->ug_instance << UGEN_MINOR_INSTANCE_SHIFT(ugenp);

        if (ep_type == USB_EP_ATTR_CONTROL) {
                type = "cntrl";
        } else {
                type = (ep_dir & USB_EP_DIR_IN) ? "in" : "out";
        }

        /*
         * xfer ep node name:
         * vid.pid.[in|out|cntrl].[<cfg>.][if<iface>.][<alt>.]<ep addr>
         */
        if ((ep_addr == 0) && owns_device) {
                (void) sprintf(node_name, "%x.%x.%s%d",
                    vid, pid, type, ep_addr);
        } else if (cfgidx == 0 && alt == 0) {
                (void) sprintf(node_name, "%x.%x.if%d%s%d",
                    vid, pid, iface, type, ep_addr);
        } else if (cfgidx == 0 && alt != 0) {
                (void) sprintf(node_name, "%x.%x.if%d.%d%s%d",
                    vid, pid, iface, alt, type, ep_addr);
        } else if (cfgidx != 0 && alt == 0) {
                (void) sprintf(node_name, "%x.%x.cfg%dif%d%s%d",
                    vid, pid, cfgval, iface, type, ep_addr);
        } else if (cfgidx != 0 && alt != 0) {
                (void) sprintf(node_name, "%x.%x.cfg%dif%d.%d%s%d",
                    vid, pid, cfgval, iface, alt,
                    type, ep_addr);
        }

        USB_DPRINTF_L3(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "minor=0x%x index=%d code=0x%" PRIx64 " name=%s",
            minor, minor_index, minor_code, node_name);

        ASSERT(minor < L_MAXMIN);

        if ((ddi_create_minor_node(ugenp->ug_dip, node_name,
            S_IFCHR, minor, DDI_NT_UGEN, 0)) != DDI_SUCCESS) {

                return (USB_FAILURE);
        }

        ugen_store_devt(ugenp, minor);

        minor_code = minor_code_base | UGEN_MINOR_EP_STAT_NODE;
        minor_index = ugen_minor_index_create(ugenp, minor_code);
        if (minor_index < 0) {
                USB_DPRINTF_L1(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "too many minor nodes, "
                    "cannot create %d.%d.%d.%x stat",
                    cfgval, iface, alt,
                    ep_descr->bEndpointAddress);
                /* carry on regardless */

                return (USB_SUCCESS);
        }
        minor = (minor_index << UGEN_MINOR_IDX_SHIFT(ugenp)) |
            ugenp->ug_instance << UGEN_MINOR_INSTANCE_SHIFT(ugenp);

        (void) strcat(node_name, "stat");

        USB_DPRINTF_L3(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "minor=0x%x index=%d code=0x%" PRIx64 " name=%s",
            minor, minor_index, minor_code, node_name);

        ASSERT(minor < L_MAXMIN);

        if ((ddi_create_minor_node(ugenp->ug_dip, node_name,
            S_IFCHR, minor, DDI_NT_UGEN, 0)) != DDI_SUCCESS) {

                return (USB_FAILURE);
        }

        ugen_store_devt(ugenp, minor);

        return (USB_SUCCESS);
}


/*
 * close all non-default pipes and drain default pipe
 */
static void
ugen_epx_shutdown(ugen_state_t *ugenp)
{
        int     i;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_shutdown:");

        for (i = 0; i < UGEN_N_ENDPOINTS; i++) {
                ugen_ep_t *epp = &ugenp->ug_ep[i];
                mutex_enter(&epp->ep_mutex);
                if (epp->ep_state != UGEN_EP_STATE_NONE) {
                        mutex_exit(&epp->ep_mutex);
                        (void) usb_serialize_access(epp->ep_ser_cookie,
                            USB_WAIT, 0);
                        (void) ugen_epx_close_pipe(ugenp, epp);
                        usb_release_access(epp->ep_ser_cookie);
                } else {
                        mutex_exit(&epp->ep_mutex);
                }
        }
}


/*
 * find cfg index corresponding to cfg value
 */
static int
ugen_cfgval2idx(ugen_state_t *ugenp, uint_t cfgval)
{
        usb_cfg_data_t  *dev_cfg = ugenp->ug_dev_data->dev_cfg;
        int             cfgidx;

        for (cfgidx = 0; cfgidx < ugenp->ug_dev_data->dev_n_cfg; cfgidx++) {
                dev_cfg = &ugenp->ug_dev_data->dev_cfg[cfgidx];
                if (cfgval == dev_cfg->cfg_descr.bConfigurationValue) {

                        return (cfgidx);
                }
        }

        ASSERT(cfgidx < ugenp->ug_dev_data->dev_n_cfg);

        return (0);
}


/*
 * check if any node is open
 */
static int
ugen_epxs_check_open_nodes(ugen_state_t *ugenp)
{
        int     i;

        for (i = 1; i < UGEN_N_ENDPOINTS; i++) {
                ugen_ep_t *epp = &ugenp->ug_ep[i];

                mutex_enter(&epp->ep_mutex);

                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epxs_check_open_nodes: epp=%d, ep_state=0x%x",
                    i, epp->ep_state);

                if (epp->ep_state & UGEN_EP_STATE_XS_OPEN) {
                        mutex_exit(&epp->ep_mutex);

                        return (USB_SUCCESS);
                }
                mutex_exit(&epp->ep_mutex);
        }

        return (USB_FAILURE);
}


/*
 * check if we can switch alternate
 */
static int
ugen_epxs_check_alt_switch(ugen_state_t *ugenp, uchar_t iface, uchar_t cfgidx)
{
        int     i;

        for (i = 1; i < UGEN_N_ENDPOINTS; i++) {
                ugen_ep_t *epp = &ugenp->ug_ep[i];

                mutex_enter(&epp->ep_mutex);

                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epxs_check_alt_switch: epp=%d, ep_state=0x%x",
                    i, epp->ep_state);

                /*
                 * if the endpoint is open and part of this cfg and interface
                 * then we cannot switch alternates
                 */
                if ((epp->ep_state & UGEN_EP_STATE_XS_OPEN) &&
                    (epp->ep_cfgidx == cfgidx) &&
                    (epp->ep_if == iface)) {
                        mutex_exit(&epp->ep_mutex);

                        return (USB_FAILURE);
                }
                mutex_exit(&epp->ep_mutex);
        }

        return (USB_SUCCESS);
}


/*
 * implicit switch to new cfg and alt
 * If a crummy device fails usb_get_cfg or usb_get_alt_if, we carry on
 * regardless so at least the device can be opened.
 */
static int
ugen_epxs_switch_cfg_alt(ugen_state_t *ugenp, ugen_ep_t *epp, dev_t dev)
{
        int     rval = USB_SUCCESS;
        uint_t  alt;
        uint_t  new_alt = UGEN_MINOR_ALT(ugenp, dev);
        uint_t  new_if = UGEN_MINOR_IF(ugenp, dev);
        uint_t  cur_if = epp->ep_if;
        uint_t  new_cfgidx = UGEN_MINOR_CFGIDX(ugenp, dev);
        uint_t  cur_cfgidx;
        uint_t  cfgval;
        int     switched = 0;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epxs_switch_cfg_alt: old cfgidx=%d, if=%d alt=%d",
            epp->ep_cfgidx, epp->ep_if, epp->ep_alt);
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "new cfgidx=%d, if=%d alt=%d ep_state=0x%x",
            new_cfgidx, new_if, new_alt, epp->ep_state);

        /* no need to switch if there is only 1 cfg, 1 iface and no alts */
        if ((new_if == 0) && (new_alt == 0) &&
            (ugenp->ug_dev_data->dev_n_cfg == 1) &&
            (ugenp->ug_dev_data->dev_cfg[0].cfg_n_if == 1) &&
            (ugenp->ug_dev_data->
            dev_cfg[0].cfg_if[new_if].if_n_alt == 1)) {
                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "no need for switching: n_cfg=%d n_alt=%d",
                    ugenp->ug_dev_data->dev_n_cfg,
                    ugenp->ug_dev_data->
                    dev_cfg[0].cfg_if[new_if].if_n_alt);

                ASSERT(epp->ep_alt == new_alt);
                ASSERT(epp->ep_cfgidx == new_cfgidx);
                ASSERT(epp->ep_if == new_if);

                return (rval);
        }

        /* no switch for default endpoint */
        if (epp->ep_descr.bEndpointAddress == 0) {

                return (rval);
        }

        mutex_exit(&epp->ep_mutex);
        if ((ugenp->ug_dev_data->dev_n_cfg > 1) &&
            usb_get_cfg(ugenp->ug_dip, &cfgval,
            USB_FLAGS_SLEEP) == USB_SUCCESS) {

                mutex_enter(&epp->ep_mutex);

                cur_cfgidx = ugen_cfgval2idx(ugenp, cfgval);

                if (new_cfgidx != cur_cfgidx) {
                        mutex_exit(&epp->ep_mutex);

                        /*
                         * we can't change config if any node
                         * is open
                         */
                        if (ugen_epxs_check_open_nodes(ugenp) ==
                            USB_SUCCESS) {
                                mutex_enter(&epp->ep_mutex);

                                return (USB_BUSY);
                        }

                        /*
                         * we are going to do this synchronously to
                         * keep it simple.
                         * This should never hang forever.
                         */
                        if ((rval = usb_set_cfg(ugenp->ug_dip,
                            new_cfgidx, USB_FLAGS_SLEEP, NULL,
                            NULL)) != USB_SUCCESS) {
                                USB_DPRINTF_L2(UGEN_PRINT_XFER,
                                    ugenp->ug_log_hdl,
                                    "implicit set cfg (%" PRId64
                                    ") failed (%d)",
                                    UGEN_MINOR_CFGIDX(ugenp, dev), rval);
                                mutex_enter(&epp->ep_mutex);

                                return (rval);
                        }
                        mutex_enter(&epp->ep_mutex);
                        epp->ep_if = (uchar_t)new_if;
                        switched++;
                }
                epp->ep_cfgidx = (uchar_t)new_cfgidx;

                mutex_exit(&epp->ep_mutex);
        }

        /*
         * implicitly switch to new alternate if
         * - we have not switched configuration (if we
         *   we switched config, the alternate must be 0)
         * - n_alts is > 1
         * - if the device supports get_alternate iface
         */
        if ((switched && (new_alt > 0)) ||
            ((ugenp->ug_dev_data->dev_cfg[new_cfgidx].
            cfg_if[new_if].if_n_alt > 1) &&
            (usb_get_alt_if(ugenp->ug_dip, new_if, &alt,
            USB_FLAGS_SLEEP) == USB_SUCCESS))) {
                if (switched || (alt != new_alt)) {
                        if (ugen_epxs_check_alt_switch(ugenp, cur_if,
                            new_cfgidx) != USB_SUCCESS) {
                                mutex_enter(&epp->ep_mutex);

                                return (USB_BUSY);
                        }
                        if ((rval = usb_set_alt_if(ugenp->ug_dip, new_if,
                            new_alt, USB_FLAGS_SLEEP, NULL, NULL)) !=
                            USB_SUCCESS) {
                                USB_DPRINTF_L2(UGEN_PRINT_XFER,
                                    ugenp->ug_log_hdl,
                                    "implicit set new alternate "
                                    "(%d) failed (%d)", new_alt, rval);
                                mutex_enter(&epp->ep_mutex);

                                return (rval);
                        }
                }
        }

        mutex_enter(&epp->ep_mutex);
        epp->ep_alt = (uchar_t)new_alt;
        ugen_update_ep_descr(ugenp, epp);

        return (rval);
}


/*
 * update endpoint descriptor in ugen_ep structure after
 * switching configuration or alternate
 */
static void
ugen_update_ep_descr(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        usb_cfg_data_t  *dev_cfg = ugenp->ug_dev_data->dev_cfg;
        usb_if_data_t   *if_data;
        usb_alt_if_data_t *alt_if_data;
        usb_ep_data_t   *ep_data;
        int             ep;

        dev_cfg = &ugenp->ug_dev_data->dev_cfg[epp->ep_cfgidx];
        if_data = &dev_cfg->cfg_if[epp->ep_if];
        alt_if_data = &if_data->if_alt[epp->ep_alt];
        for (ep = 0; ep < alt_if_data->altif_n_ep; ep++) {
                ep_data = &alt_if_data->altif_ep[ep];
                if (usb_get_ep_index(ep_data->ep_descr.
                    bEndpointAddress) ==
                    usb_get_ep_index(epp->ep_descr.
                    bEndpointAddress)) {
                        epp->ep_descr = ep_data->ep_descr;
                        (void) usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
                            ugenp->ug_dip, ep_data, &epp->ep_xdescr);

                        break;
                }
        }
}


/*
 * Xfer endpoint management
 *
 * open an endpoint for xfers
 *
 * Return values: errno
 */
static int
ugen_epx_open(ugen_state_t *ugenp, dev_t dev, int flag)
{
        ugen_ep_t *epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, dev)];
        int     rval;

        mutex_enter(&epp->ep_mutex);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_open: minor=0x%x flag=0x%x ep_state=0x%x",
            getminor(dev), flag, epp->ep_state);

        ASSERT(epp->ep_state & UGEN_EP_STATE_ACTIVE);

        /* implicit switch to new cfg & alt */
        if ((epp->ep_state & UGEN_EP_STATE_XFER_OPEN) != 0) {
                mutex_exit(&epp->ep_mutex);

                return (EBUSY);
        }
        if ((rval = ugen_epxs_switch_cfg_alt(ugenp, epp, dev)) ==
            USB_SUCCESS) {
                rval = ugen_epx_open_pipe(ugenp, epp, flag);
        }

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_open: state=0x%x", epp->ep_state);

        ASSERT(epp->ep_state & UGEN_EP_STATE_ACTIVE);
        epp->ep_done = epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

        mutex_exit(&epp->ep_mutex);

        return (usb_rval2errno(rval));
}


/*
 * close an endpoint for xfers
 */
static void
ugen_epx_close(ugen_state_t *ugenp, dev_t dev, int flag)
{
        ugen_ep_t *epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, dev)];

        mutex_enter(&epp->ep_mutex);
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_close: dev=0x%lx flag=0x%x state=0x%x", dev, flag,
            epp->ep_state);
        mutex_exit(&epp->ep_mutex);

        ugen_epx_close_pipe(ugenp, epp);

        mutex_enter(&epp->ep_mutex);
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_close: state=0x%x", epp->ep_state);
        ASSERT(epp->ep_state & UGEN_EP_STATE_ACTIVE);
        ASSERT(epp->ep_bp == NULL);
        ASSERT(epp->ep_done == 0);
        ASSERT(epp->ep_data == NULL);
        mutex_exit(&epp->ep_mutex);
}


/*
 * open pipe for this endpoint
 * If the pipe is an interrupt IN pipe, start polling immediately
 */
static int
ugen_epx_open_pipe(ugen_state_t *ugenp, ugen_ep_t *epp, int flag)
{
        int rval = USB_SUCCESS;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_open_pipe: epp=0x%p flag=%d state=0x%x",
            (void *)epp, flag, epp->ep_state);

        epp->ep_state |= UGEN_EP_STATE_XFER_OPEN;
        epp->ep_xfer_oflag = flag;

        /* if default pipe, just copy the handle */
        if ((epp->ep_descr.bEndpointAddress & USB_EP_NUM_MASK) == 0) {
                epp->ep_ph = ugenp->ug_dev_data->dev_default_ph;
        } else {
                mutex_exit(&epp->ep_mutex);

                rval = usb_pipe_xopen(ugenp->ug_dip,
                    &epp->ep_xdescr, &epp->ep_pipe_policy,
                    USB_FLAGS_SLEEP, &epp->ep_ph);

                mutex_enter(&epp->ep_mutex);

                if (rval == USB_SUCCESS) {
                        (void) usb_pipe_set_private(epp->ep_ph,
                            (usb_opaque_t)epp);

                        /*
                         * if interrupt IN pipe, and one xfer mode
                         * has not been set, start polling immediately
                         */
                        if ((UGEN_XFER_TYPE(epp) == USB_EP_ATTR_INTR) &&
                            (!(epp->ep_one_xfer)) &&
                            (UGEN_XFER_DIR(epp) == USB_EP_DIR_IN)) {
                                if ((rval = ugen_epx_intr_IN_start_polling(
                                    ugenp, epp)) != USB_SUCCESS) {

                                        mutex_exit(&epp->ep_mutex);
                                        usb_pipe_close(ugenp->ug_dip,
                                            epp->ep_ph, USB_FLAGS_SLEEP,
                                            NULL, NULL);
                                        mutex_enter(&epp->ep_mutex);

                                        epp->ep_ph = NULL;
                                } else {
                                        epp->ep_state |=
                                            UGEN_EP_STATE_INTR_IN_POLLING_ON;

                                        /* allow for about 1 sec of data */
                                        epp->ep_buf_limit =
                                            (1000/epp->ep_descr.bInterval) *
                                            epp->ep_descr.wMaxPacketSize;
                                }
                        }

                        /* set ep_buf_limit for isoc IN pipe */
                        if ((UGEN_XFER_TYPE(epp) == USB_EP_ATTR_ISOCH) &&
                            (UGEN_XFER_DIR(epp) == USB_EP_DIR_IN)) {
                                uint16_t max_size;
                                uint32_t framecnt;

                                max_size =
                                    UGEN_PKT_SIZE(epp->ep_descr.wMaxPacketSize);

                                /*
                                 * wMaxPacketSize bits 10..0 specifies maximum
                                 * packet size, which can hold 1024 bytes. If
                                 * bits 12..11 is non zero, max_size will be
                                 * greater than 1024 and the endpoint is a
                                 * high-bandwidth endpoint.
                                 */
                                if (max_size <= 1024) {
                                /*
                                 * allowing about 1s data of highspeed and 8s
                                 * data of full speed device
                                 */
                                        framecnt = ugen_isoc_buf_limit;
                                        epp->ep_buf_limit = framecnt *
                                            max_size * 8;
                                } else {
                                /*
                                 * allow for about 333 ms data for high-speed
                                 * high-bandwidth data
                                 */
                                        framecnt = ugen_isoc_buf_limit/3;
                                        epp->ep_buf_limit =
                                            framecnt * max_size * 8;
                                }

                                epp->ep_isoc_in_inited = 0;
                        }
                }
        }

        if (rval != USB_SUCCESS) {
                epp->ep_state &= ~(UGEN_EP_STATE_XFER_OPEN |
                    UGEN_EP_STATE_INTR_IN_POLLING_ON);
        }

        return (rval);
}


/*
 * close an endpoint pipe
 */
static void
ugen_epx_close_pipe(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_close_pipe: epp=0x%p", (void *)epp);

        mutex_enter(&epp->ep_mutex);
        if (epp->ep_state & UGEN_EP_STATE_XFER_OPEN) {

                /*  free isoc pipe private data ep_isoc_info.isoc_pkt_descr. */
                if (UGEN_XFER_TYPE(epp) == USB_EP_ATTR_ISOCH) {
                        int len;
                        int n_pkt;

                        if (UGEN_XFER_DIR(epp) == USB_EP_DIR_IN &&
                            (epp->ep_state &
                            UGEN_EP_STATE_ISOC_IN_POLLING_ON)) {
                                mutex_exit(&epp->ep_mutex);
                                usb_pipe_stop_isoc_polling(epp->ep_ph,
                                    USB_FLAGS_SLEEP);
                                mutex_enter(&epp->ep_mutex);
                        }

                        if (epp->ep_isoc_info.isoc_pkt_descr) {
                                n_pkt = epp->ep_isoc_info.
                                    isoc_pkts_count;
                                len = sizeof (ugen_isoc_pkt_descr_t) * n_pkt;

                                kmem_free(epp->ep_isoc_info.isoc_pkt_descr,
                                    len);

                                epp->ep_isoc_info.isoc_pkt_descr = NULL;
                        }
                        epp->ep_isoc_in_inited = 0;

                }


                epp->ep_state &= ~(UGEN_EP_STATE_XFER_OPEN |
                    UGEN_EP_STATE_INTR_IN_POLLING_IS_STOPPED |
                    UGEN_EP_STATE_INTR_IN_POLLING_ON |
                    UGEN_EP_STATE_ISOC_IN_POLLING_IS_STOPPED |
                    UGEN_EP_STATE_ISOC_IN_POLLING_ON);

                if (epp->ep_ph == ugenp->ug_dev_data->dev_default_ph) {
                        mutex_exit(&epp->ep_mutex);

                        (void) usb_pipe_drain_reqs(ugenp->ug_dip,
                            epp->ep_ph, 0, USB_FLAGS_SLEEP,
                            NULL, NULL);
                        mutex_enter(&epp->ep_mutex);
                } else {
                        mutex_exit(&epp->ep_mutex);
                        usb_pipe_close(ugenp->ug_dip,
                            epp->ep_ph, USB_FLAGS_SLEEP, NULL, NULL);

                        mutex_enter(&epp->ep_mutex);
                        epp->ep_ph = NULL;
                }

                freemsg(epp->ep_data);
                epp->ep_ph = NULL;
                epp->ep_data = NULL;
        }
        ASSERT(epp->ep_ph == NULL);
        ASSERT(epp->ep_data == NULL);
        mutex_exit(&epp->ep_mutex);
}


/*
 * start endpoint xfer
 *
 * We first serialize at endpoint level for only one request at the time
 *
 * Return values: errno
 */
static int
ugen_epx_req(ugen_state_t *ugenp, struct buf *bp)
{
        dev_t           dev = bp->b_edev;
        ugen_ep_t       *epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, dev)];
        boolean_t       wait = B_FALSE;
        int             rval = 0;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_req: bp=0x%p dev=0x%lx", (void *)bp, dev);

        /* single thread per endpoint, one request at the time */
        if (usb_serialize_access(epp->ep_ser_cookie, USB_WAIT_SIG, 0) <=
            0) {

                return (EINTR);
        }

        mutex_enter(&ugenp->ug_mutex);
        switch (ugenp->ug_dev_state) {
        case USB_DEV_ONLINE:

                break;
        case USB_UGEN_DEV_UNAVAILABLE_RECONNECT:
        case USB_DEV_DISCONNECTED:
                mutex_enter(&epp->ep_mutex);
                epp->ep_lcmd_status = USB_LC_STAT_DISCONNECTED;
                mutex_exit(&epp->ep_mutex);
                rval = ENODEV;

                break;
        case USB_UGEN_DEV_UNAVAILABLE_RESUME:
        case USB_DEV_SUSPENDED:
                mutex_enter(&epp->ep_mutex);
                epp->ep_lcmd_status = USB_LC_STAT_SUSPENDED;
                mutex_exit(&epp->ep_mutex);
                rval = EBADF;

                break;
        default:
                mutex_enter(&epp->ep_mutex);
                epp->ep_lcmd_status = USB_LC_STAT_HW_ERR;
                mutex_exit(&epp->ep_mutex);
                rval = EIO;

                break;
        }

#ifndef __lock_lint
        USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_req: lcmd_status=0x%x", epp->ep_lcmd_status);
#endif

        mutex_exit(&ugenp->ug_mutex);

        if (rval) {
                usb_release_access(epp->ep_ser_cookie);

                return (rval);
        }

        mutex_enter(&epp->ep_mutex);
        ASSERT(epp->ep_state & UGEN_EP_STATE_XS_OPEN);
        epp->ep_done = 0;
        epp->ep_bp = bp;

        switch (epp->ep_descr.bmAttributes & USB_EP_ATTR_MASK) {
        case USB_EP_ATTR_CONTROL:
                rval = ugen_epx_ctrl_req(ugenp, epp, bp, &wait);

                break;
        case USB_EP_ATTR_BULK:
                rval = ugen_epx_bulk_req(ugenp, epp, bp, &wait);

                break;
        case USB_EP_ATTR_INTR:
                if (bp->b_flags & B_READ) {
                        rval = ugen_epx_intr_IN_req(ugenp, epp, bp, &wait);
                } else {
                        rval = ugen_epx_intr_OUT_req(ugenp, epp, bp, &wait);
                }

                break;
        case USB_EP_ATTR_ISOCH:
                if (bp->b_flags & B_READ) {
                        rval = ugen_epx_isoc_IN_req(ugenp, epp, bp, &wait);
                } else {
                        rval = ugen_epx_isoc_OUT_req(ugenp, epp, bp, &wait);
                }

                break;
        default:
                epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;
                rval = USB_INVALID_REQUEST;
        }

        /* if the xfer could not immediately be completed, block here */
        if ((rval == USB_SUCCESS) && wait) {
                while (!epp->ep_done) {
                        if ((cv_wait_sig(&epp->ep_wait_cv,
                            &epp->ep_mutex) <= 0) && !epp->ep_done) {
                                USB_DPRINTF_L2(UGEN_PRINT_XFER,
                                    ugenp->ug_log_hdl,
                                    "ugen_epx_req: interrupted ep=0x%" PRIx64,
                                    UGEN_MINOR_EPIDX(ugenp, dev));

                                /*
                                 * blow away the request except for dflt pipe
                                 * (this is prevented in USBA)
                                 */
                                mutex_exit(&epp->ep_mutex);
                                usb_pipe_reset(ugenp->ug_dip, epp->ep_ph,
                                    USB_FLAGS_SLEEP, NULL, NULL);
                                (void) usb_pipe_drain_reqs(ugenp->ug_dip,
                                    epp->ep_ph, 0,
                                    USB_FLAGS_SLEEP, NULL, NULL);

                                mutex_enter(&epp->ep_mutex);

                                if (geterror(bp) == 0) {
                                        bioerror(bp, EINTR);
                                }
                                epp->ep_lcmd_status =
                                    USB_LC_STAT_INTERRUPTED;

                                break;
                        }
                        USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_epx_req: wakeup");
                }
        }

        /* always set lcmd_status if there was a failure */
        if ((rval != USB_SUCCESS) &&
            (epp->ep_lcmd_status == USB_LC_STAT_NOERROR)) {
                epp->ep_lcmd_status = USB_LC_STAT_UNSPECIFIED_ERR;
        }

        epp->ep_done = 0;
        epp->ep_bp = NULL;
        mutex_exit(&epp->ep_mutex);

        usb_release_access(epp->ep_ser_cookie);
        USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_req: done");

        return (usb_rval2errno(rval));
}


/*
 * handle control xfers
 */
static int
ugen_epx_ctrl_req(ugen_state_t *ugenp, ugen_ep_t *epp,
    struct buf *bp, boolean_t *wait)
{
        usb_ctrl_req_t *reqp = NULL;
        uchar_t *setup = ((uchar_t *)(bp->b_un.b_addr));
        int     rval;
        ushort_t wLength;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_ctrl_req: epp=0x%p state=0x%x bp=0x%p",
            (void *)epp, epp->ep_state, (void *)bp);

        /* is this a read following a write with setup data? */
        if (bp->b_flags & B_READ) {
                if (epp->ep_data) {
                        int ep_len = MBLKL(epp->ep_data);
                        int len = min(bp->b_bcount, ep_len);

                        bcopy(epp->ep_data->b_rptr, bp->b_un.b_addr, len);
                        epp->ep_data->b_rptr += len;
                        if (MBLKL(epp->ep_data) == 0) {
                                freemsg(epp->ep_data);
                                epp->ep_data = NULL;
                        }
                        bp->b_resid = bp->b_bcount - len;
                } else {
                        bp->b_resid = bp->b_bcount;
                }

                return (USB_SUCCESS);
        }

        /* discard old data if any */
        if (epp->ep_data) {
                freemsg(epp->ep_data);
                epp->ep_data = NULL;
        }

        /* allocate and initialize request */
        wLength = (setup[7] << 8) | setup[6];
        reqp = usb_alloc_ctrl_req(ugenp->ug_dip, wLength, USB_FLAGS_NOSLEEP);
        if (reqp == NULL) {
                epp->ep_lcmd_status = USB_LC_STAT_NO_RESOURCES;

                return (USB_NO_RESOURCES);
        }

        /* assume an LE data stream */
        reqp->ctrl_bmRequestType = setup[0];
        reqp->ctrl_bRequest     = setup[1];
        reqp->ctrl_wValue       = (setup[3] << 8) | setup[2];
        reqp->ctrl_wIndex       = (setup[5] << 8) | setup[4];
        reqp->ctrl_wLength      = wLength;
        reqp->ctrl_timeout      = ugen_ctrl_timeout;
        reqp->ctrl_attributes   = USB_ATTRS_AUTOCLEARING |
            USB_ATTRS_SHORT_XFER_OK;
        reqp->ctrl_cb           = ugen_epx_ctrl_req_cb;
        reqp->ctrl_exc_cb       = ugen_epx_ctrl_req_cb;
        reqp->ctrl_client_private = (usb_opaque_t)ugenp;

        /*
         * is this a legal request? No accesses to device are
         * allowed if we don't own the device
         */
        if (((reqp->ctrl_bmRequestType & USB_DEV_REQ_RCPT_MASK) ==
            USB_DEV_REQ_RCPT_DEV) &&
            (((reqp->ctrl_bmRequestType & USB_DEV_REQ_DIR_MASK) ==
            USB_DEV_REQ_HOST_TO_DEV) &&
            (usb_owns_device(ugenp->ug_dip) == B_FALSE))) {
                rval = USB_INVALID_PERM;
                epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;

                goto fail;
        }

        /* filter out set_cfg and set_if standard requests */
        if ((reqp->ctrl_bmRequestType & USB_DEV_REQ_TYPE_MASK) ==
            USB_DEV_REQ_TYPE_STANDARD) {
                switch (reqp->ctrl_bRequest) {
                case USB_REQ_SET_CFG:
                case USB_REQ_SET_IF:
                        rval = USB_INVALID_REQUEST;
                        epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;

                        goto fail;
                default:

                        break;
                }
        }

        /* is this from host to device? */
        if (((reqp->ctrl_bmRequestType & USB_DEV_REQ_DIR_MASK) ==
            USB_DEV_REQ_HOST_TO_DEV) && reqp->ctrl_wLength) {
                if (((bp->b_bcount - UGEN_SETUP_PKT_SIZE) - wLength) != 0) {
                        rval = USB_INVALID_REQUEST;
                        epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;

                        goto fail;
                }
                bcopy(bp->b_un.b_addr + UGEN_SETUP_PKT_SIZE,
                    reqp->ctrl_data->b_wptr, wLength);
                reqp->ctrl_data->b_wptr += wLength;
        } else  if ((reqp->ctrl_bmRequestType & USB_DEV_REQ_DIR_MASK) ==
            USB_DEV_REQ_DEV_TO_HOST) {
                if (bp->b_bcount != UGEN_SETUP_PKT_SIZE) {
                        rval = USB_INVALID_REQUEST;
                        epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;

                        goto fail;
                }
        }

        /* submit the request */
        mutex_exit(&epp->ep_mutex);
        rval = usb_pipe_ctrl_xfer(epp->ep_ph, reqp, USB_FLAGS_NOSLEEP);
        mutex_enter(&epp->ep_mutex);
        if (rval != USB_SUCCESS) {
                epp->ep_lcmd_status =
                    ugen_cr2lcstat(reqp->ctrl_completion_reason);

                goto fail;
        }

        *wait = B_TRUE;

        return (USB_SUCCESS);
fail:
        *wait = B_FALSE;

        usb_free_ctrl_req(reqp);

        return (rval);
}


/*
 * callback for control requests, normal and exception completion
 */
static void
ugen_epx_ctrl_req_cb(usb_pipe_handle_t ph, usb_ctrl_req_t *reqp)
{
        ugen_state_t *ugenp = (ugen_state_t *)reqp->ctrl_client_private;
        ugen_ep_t *epp = (ugen_ep_t *)usb_pipe_get_private(ph);

        if (epp == NULL) {
                epp = &ugenp->ug_ep[0];
        }

        mutex_enter(&epp->ep_mutex);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_ctrl_req_cb:\n\t"
            "epp=0x%p state=0x%x ph=0x%p reqp=0x%p cr=%d cb=0x%x",
            (void *)epp, epp->ep_state, (void *)ph, (void *)reqp,
            reqp->ctrl_completion_reason, reqp->ctrl_cb_flags);

        ASSERT((reqp->ctrl_cb_flags & USB_CB_INTR_CONTEXT) == 0);

        /* save any data for the next read */
        switch (reqp->ctrl_completion_reason) {
        case USB_CR_OK:
                epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

                break;
        case USB_CR_PIPE_RESET:

                break;
        default:
                epp->ep_lcmd_status =
                    ugen_cr2lcstat(reqp->ctrl_completion_reason);
                if (epp->ep_bp) {
                        bioerror(epp->ep_bp, EIO);
                }

                break;
        }

        if (reqp->ctrl_data) {
                ASSERT(epp->ep_data == NULL);
                epp->ep_data = reqp->ctrl_data;
                reqp->ctrl_data = NULL;
        }
        epp->ep_done++;
        cv_signal(&epp->ep_wait_cv);
        mutex_exit(&epp->ep_mutex);

        usb_free_ctrl_req(reqp);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_ctrl_req_cb: done");
}


/*
 * handle bulk xfers
 */
static int
ugen_epx_bulk_req(ugen_state_t *ugenp, ugen_ep_t *epp,
    struct buf *bp, boolean_t *wait)
{
        int             rval;
        usb_bulk_req_t  *reqp = usb_alloc_bulk_req(ugenp->ug_dip,
            bp->b_bcount, USB_FLAGS_NOSLEEP);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_bulk_req: epp=0x%p state=0x%x bp=0x%p",
            (void *)epp, epp->ep_state, (void *)bp);

        if (reqp == NULL) {
                epp->ep_lcmd_status = USB_LC_STAT_NO_RESOURCES;

                return (USB_NO_RESOURCES);
        }

        ASSERT(epp->ep_state & UGEN_EP_STATE_XS_OPEN);

        /*
         * the transfer count is limited in minphys with what the HCD can
         * do
         */
        reqp->bulk_len          = bp->b_bcount;
        reqp->bulk_timeout      = ugen_bulk_timeout;
        reqp->bulk_client_private = (usb_opaque_t)ugenp;
        reqp->bulk_attributes   = USB_ATTRS_AUTOCLEARING;
        reqp->bulk_cb           = ugen_epx_bulk_req_cb;
        reqp->bulk_exc_cb       = ugen_epx_bulk_req_cb;

        /* copy data into bp for OUT pipes */
        if ((UGEN_XFER_DIR(epp) & USB_EP_DIR_IN) == 0) {
                bcopy(epp->ep_bp->b_un.b_addr, reqp->bulk_data->b_rptr,
                    bp->b_bcount);
                reqp->bulk_data->b_wptr += bp->b_bcount;
        } else {
                reqp->bulk_attributes |= USB_ATTRS_SHORT_XFER_OK;
        }

        mutex_exit(&epp->ep_mutex);
        if ((rval = usb_pipe_bulk_xfer(epp->ep_ph, reqp,
            USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
                mutex_enter(&epp->ep_mutex);
                epp->ep_lcmd_status =
                    ugen_cr2lcstat(reqp->bulk_completion_reason);
                usb_free_bulk_req(reqp);
                bioerror(bp, EIO);
        } else {
                mutex_enter(&epp->ep_mutex);
        }
        *wait = (rval == USB_SUCCESS) ? B_TRUE : B_FALSE;

        return (rval);
}


/*
 * normal and exception bulk request callback
 */
static void
ugen_epx_bulk_req_cb(usb_pipe_handle_t ph, usb_bulk_req_t *reqp)
{
        ugen_state_t *ugenp = (ugen_state_t *)reqp->bulk_client_private;
        ugen_ep_t *epp = (ugen_ep_t *)usb_pipe_get_private(ph);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_bulk_req_cb: ph=0x%p reqp=0x%p cr=%d cb=0x%x",
            (void *)ph, (void *)reqp, reqp->bulk_completion_reason,
            reqp->bulk_cb_flags);

        ASSERT((reqp->bulk_cb_flags & USB_CB_INTR_CONTEXT) == 0);

        /* epp might be NULL if we are closing the pipe */
        if (epp) {
                mutex_enter(&epp->ep_mutex);
                if (epp->ep_bp && reqp->bulk_data) {
                        int len = min(MBLKL(reqp->bulk_data),
                            epp->ep_bp->b_bcount);
                        if (UGEN_XFER_DIR(epp) & USB_EP_DIR_IN) {
                                if (len) {
                                        bcopy(reqp->bulk_data->b_rptr,
                                            epp->ep_bp->b_un.b_addr, len);
                                        epp->ep_bp->b_resid =
                                            epp->ep_bp->b_bcount - len;
                                }
                        } else {
                                epp->ep_bp->b_resid =
                                    epp->ep_bp->b_bcount - len;
                        }
                }
                switch (reqp->bulk_completion_reason) {
                case USB_CR_OK:
                        epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

                        break;
                case USB_CR_PIPE_RESET:

                        break;
                default:
                        epp->ep_lcmd_status =
                            ugen_cr2lcstat(reqp->bulk_completion_reason);
                        if (epp->ep_bp) {
                                bioerror(epp->ep_bp, EIO);
                        }
                }
                epp->ep_done++;
                cv_signal(&epp->ep_wait_cv);
                mutex_exit(&epp->ep_mutex);
        }

        usb_free_bulk_req(reqp);
}


/*
 * handle intr IN xfers
 */
static int
ugen_epx_intr_IN_req(ugen_state_t *ugenp, ugen_ep_t *epp,
    struct buf *bp, boolean_t *wait)
{
        int     len = 0;
        int     rval = USB_SUCCESS;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_intr_IN_req: epp=0x%p state=0x%x bp=0x%p",
            (void *)epp, epp->ep_state, (void *)bp);

        *wait = B_FALSE;

        /* can we satisfy this read? */
        if (epp->ep_data) {
                len = min(MBLKL(epp->ep_data),
                    bp->b_bcount);
        }

        /*
         * if polling not active, restart, and return failure
         * immediately unless one xfer mode has been requested
         * if there is some data, return a short read
         */
        if ((epp->ep_state & UGEN_EP_STATE_INTR_IN_POLLING_ON) == 0) {
                if (len == 0) {
                        if (!epp->ep_one_xfer) {
                                rval = USB_FAILURE;
                                if (epp->ep_lcmd_status ==
                                    USB_LC_STAT_NOERROR) {
                                        epp->ep_lcmd_status =
                                            USB_LC_STAT_INTR_BUF_FULL;
                                }
                        }
                        if (ugen_epx_intr_IN_start_polling(ugenp,
                            epp) != USB_SUCCESS) {
                                epp->ep_lcmd_status =
                                    USB_LC_STAT_INTR_POLLING_FAILED;
                        }
                        if (epp->ep_one_xfer) {
                                *wait = B_TRUE;
                        }
                        goto done;
                } else if (epp->ep_data && (len < bp->b_bcount)) {
                        bcopy(epp->ep_data->b_rptr, bp->b_un.b_addr, len);
                        bp->b_resid = bp->b_bcount - len;
                        epp->ep_data->b_rptr += len;

                        goto done;
                }
        }

        /*
         * if there is data or FNDELAY, return available data
         */
        if ((len >= bp->b_bcount) ||
            (epp->ep_xfer_oflag & (FNDELAY | FNONBLOCK))) {
                if (epp->ep_data) {
                        bcopy(epp->ep_data->b_rptr, bp->b_un.b_addr, len);
                        epp->ep_data->b_rptr += len;
                        bp->b_resid = bp->b_bcount - len;
                } else {
                        bp->b_resid = bp->b_bcount;
                }
        } else {
                /* otherwise just wait for data */
                *wait = B_TRUE;
        }

done:
        if (epp->ep_data && (epp->ep_data->b_rptr == epp->ep_data->b_wptr)) {
                freemsg(epp->ep_data);
                epp->ep_data = NULL;
        }

        if (*wait) {
                ASSERT(epp->ep_state & UGEN_EP_STATE_INTR_IN_POLLING_ON);
        }

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_intr_IN_req end: rval=%d bcount=%lu len=%d data=0x%p",
            rval, bp->b_bcount, len, (void *)epp->ep_data);

        return (rval);
}


/*
 * Start polling on interrupt endpoint, synchronously
 */
static int
ugen_epx_intr_IN_start_polling(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        int rval = USB_FAILURE;
        usb_intr_req_t  *reqp;
        usb_flags_t uflag;

        /*
         * if polling is being stopped, we restart polling in the
         * interrrupt callback again
         */
        if (epp->ep_state & UGEN_EP_STATE_INTR_IN_POLLING_IS_STOPPED) {

                return (rval);
        }
        if ((epp->ep_state & UGEN_EP_STATE_INTR_IN_POLLING_ON) == 0) {
                USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_intr_IN_start_polling: epp=0x%p state=0x%x",
                    (void *)epp, epp->ep_state);

                epp->ep_state |= UGEN_EP_STATE_INTR_IN_POLLING_ON;
                mutex_exit(&epp->ep_mutex);

                reqp = usb_alloc_intr_req(ugenp->ug_dip, 0,
                    USB_FLAGS_SLEEP);
                reqp->intr_client_private = (usb_opaque_t)ugenp;

                reqp->intr_attributes   = USB_ATTRS_AUTOCLEARING |
                    USB_ATTRS_SHORT_XFER_OK;
                mutex_enter(&epp->ep_mutex);
                if (epp->ep_one_xfer) {
                        reqp->intr_attributes |= USB_ATTRS_ONE_XFER;
                        uflag = USB_FLAGS_NOSLEEP;
                } else {
                        uflag = USB_FLAGS_SLEEP;
                }
                mutex_exit(&epp->ep_mutex);

                reqp->intr_len          = epp->ep_descr.wMaxPacketSize;
                reqp->intr_cb           = ugen_epx_intr_IN_req_cb;
                reqp->intr_exc_cb       = ugen_epx_intr_IN_req_cb;


                if ((rval = usb_pipe_intr_xfer(epp->ep_ph, reqp,
                    uflag)) != USB_SUCCESS) {
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_epx_intr_IN_start_polling: failed %d", rval);
                        usb_free_intr_req(reqp);
                }
                mutex_enter(&epp->ep_mutex);
                if (rval != USB_SUCCESS) {
                        epp->ep_state &= ~UGEN_EP_STATE_INTR_IN_POLLING_ON;
                }
        } else {
                rval = USB_SUCCESS;
        }

        return (rval);
}


/*
 * stop polling on an interrupt endpoint, asynchronously
 */
static void
ugen_epx_intr_IN_stop_polling(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        if ((epp->ep_state & UGEN_EP_STATE_INTR_IN_POLLING_ON) &&
            ((epp->ep_state & UGEN_EP_STATE_INTR_IN_POLLING_IS_STOPPED) == 0)) {

                USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_intr_IN_stop_polling: epp=0x%p state=0x%x",
                    (void *)epp, epp->ep_state);

                epp->ep_state |= UGEN_EP_STATE_INTR_IN_POLLING_IS_STOPPED;
                mutex_exit(&epp->ep_mutex);
                usb_pipe_stop_intr_polling(epp->ep_ph, USB_FLAGS_NOSLEEP);
                mutex_enter(&epp->ep_mutex);
        }
}


/*
 * poll management
 */
static void
ugen_epx_intr_IN_poll_wakeup(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        if (epp->ep_state & UGEN_EP_STATE_INTR_IN_POLL_PENDING) {
                struct pollhead *phpp = &epp->ep_pollhead;

                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_intr_IN_poll_wakeup: state=0x%x", epp->ep_state);

                epp->ep_state &= ~UGEN_EP_STATE_INTR_IN_POLL_PENDING;
                mutex_exit(&epp->ep_mutex);
                pollwakeup(phpp, POLLIN);
                mutex_enter(&epp->ep_mutex);
        }
}


/*
 * callback functions for interrupt IN pipe
 */
static void
ugen_epx_intr_IN_req_cb(usb_pipe_handle_t ph, usb_intr_req_t *reqp)
{
        ugen_state_t *ugenp = (ugen_state_t *)reqp->intr_client_private;
        ugen_ep_t *epp = (ugen_ep_t *)usb_pipe_get_private(ph);

        if (epp == NULL) {
                /* pipe is closing */

                goto done;
        }

        mutex_enter(&epp->ep_mutex);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_intr_IN_req_cb:\n\t"
            "epp=0x%p state=0x%x ph=0x%p reqp=0x%p cr=%d cb=0x%x len=%ld",
            (void *)epp, epp->ep_state, (void *)ph, (void *)reqp,
            reqp->intr_completion_reason, reqp->intr_cb_flags,
            (reqp->intr_data == NULL) ? 0 :
            MBLKL(reqp->intr_data));

        ASSERT((reqp->intr_cb_flags & USB_CB_INTR_CONTEXT) == 0);

        if (epp->ep_data && reqp->intr_data) {
                mblk_t *mp;

                USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "intr ep%x coalesce data", epp->ep_descr.bEndpointAddress);

                /* coalesce the data into one mblk */
                epp->ep_data->b_cont = reqp->intr_data;
                if ((mp = msgpullup(epp->ep_data, -1)) != NULL) {
                        reqp->intr_data = NULL;
                        freemsg(epp->ep_data);
                        epp->ep_data = mp;
                } else {
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "msgpullup failed, discard data");
                        epp->ep_data->b_cont = NULL;
                }
        } else if (reqp->intr_data) {
                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "setting ep_data");

                epp->ep_data = reqp->intr_data;
                reqp->intr_data = NULL;
        }

        switch (reqp->intr_completion_reason) {
        case USB_CR_OK:
                epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

                break;
        case USB_CR_PIPE_RESET:
        case USB_CR_STOPPED_POLLING:

                break;
        default:
                epp->ep_lcmd_status =
                    ugen_cr2lcstat(reqp->intr_completion_reason);
                USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_exp_intr_cb_req: lcmd_status=0x%x",
                    epp->ep_lcmd_status);

                break;
        }

        /* any non-zero completion reason stops polling */
        if ((reqp->intr_completion_reason) ||
            (epp->ep_one_xfer)) {
                epp->ep_state &= ~(UGEN_EP_STATE_INTR_IN_POLLING_ON |
                    UGEN_EP_STATE_INTR_IN_POLLING_IS_STOPPED);
        }

        /* is there a poll pending? should we stop polling? */
        if (epp->ep_data) {
                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_intr_IN_req_cb: data len=0x%lx",
                    MBLKL(epp->ep_data));

                ugen_epx_intr_IN_poll_wakeup(ugenp, epp);

                /* if there is no space left, stop polling */
                if (epp->ep_data &&
                    (MBLKL(epp->ep_data) >=
                    epp->ep_buf_limit)) {
                        ugen_epx_intr_IN_stop_polling(ugenp, epp);
                }
        }

        if (reqp->intr_completion_reason && epp->ep_bp) {
                bioerror(epp->ep_bp, EIO);
                epp->ep_done++;
                cv_signal(&epp->ep_wait_cv);

        /* can we satisfy the read now */
        } else if (epp->ep_data && epp->ep_bp &&
            (!epp->ep_done || epp->ep_one_xfer)) {
                boolean_t wait;

                if ((ugen_epx_intr_IN_req(ugenp, epp, epp->ep_bp, &wait) ==
                    USB_SUCCESS) && (wait == B_FALSE)) {
                        epp->ep_done++;
                        cv_signal(&epp->ep_wait_cv);
                }
        }
        mutex_exit(&epp->ep_mutex);

done:
        usb_free_intr_req(reqp);
}


/*
 * handle intr OUT xfers
 */
static int
ugen_epx_intr_OUT_req(ugen_state_t *ugenp, ugen_ep_t *epp,
    struct buf *bp, boolean_t *wait)
{
        int     rval = USB_SUCCESS;
        usb_intr_req_t  *reqp;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_intr_OUT_req: epp=0x%p state=0x%x bp=0x%p",
            (void *)epp, epp->ep_state, (void *)bp);

        reqp = usb_alloc_intr_req(ugenp->ug_dip, bp->b_bcount,
            USB_FLAGS_NOSLEEP);
        if (reqp == NULL) {
                epp->ep_lcmd_status = USB_LC_STAT_NO_RESOURCES;

                return (USB_NO_RESOURCES);
        }

        ASSERT(epp->ep_state & UGEN_EP_STATE_XS_OPEN);

        reqp->intr_timeout      = ugen_intr_timeout;
        reqp->intr_client_private = (usb_opaque_t)ugenp;
        reqp->intr_len          = bp->b_bcount;
        reqp->intr_attributes   = USB_ATTRS_AUTOCLEARING;
        reqp->intr_cb           = ugen_epx_intr_OUT_req_cb;
        reqp->intr_exc_cb       = ugen_epx_intr_OUT_req_cb;

        /* copy data from bp */
        bcopy(epp->ep_bp->b_un.b_addr, reqp->intr_data->b_rptr,
            bp->b_bcount);
        reqp->intr_data->b_wptr += bp->b_bcount;

        mutex_exit(&epp->ep_mutex);
        if ((rval = usb_pipe_intr_xfer(epp->ep_ph, reqp,
            USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
                mutex_enter(&epp->ep_mutex);
                epp->ep_lcmd_status =
                    ugen_cr2lcstat(reqp->intr_completion_reason);
                usb_free_intr_req(reqp);
                bioerror(bp, EIO);
        } else {
                mutex_enter(&epp->ep_mutex);
        }
        *wait = (rval == USB_SUCCESS) ? B_TRUE : B_FALSE;

        return (rval);
}


/*
 * callback functions for interrupt OUT pipe
 */
static void
ugen_epx_intr_OUT_req_cb(usb_pipe_handle_t ph, usb_intr_req_t *reqp)
{
        ugen_state_t *ugenp = (ugen_state_t *)reqp->intr_client_private;
        ugen_ep_t *epp = (ugen_ep_t *)usb_pipe_get_private(ph);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_intr_OUT_req_cb: ph=0x%p reqp=0x%p cr=%d cb=0x%x",
            (void *)ph, (void *)reqp, reqp->intr_completion_reason,
            reqp->intr_cb_flags);

        ASSERT((reqp->intr_cb_flags & USB_CB_INTR_CONTEXT) == 0);

        /* epp might be NULL if we are closing the pipe */
        if (epp) {
                int len;

                mutex_enter(&epp->ep_mutex);
                if (epp->ep_bp) {
                        len = min(MBLKL(reqp->intr_data), epp->ep_bp->b_bcount);

                        epp->ep_bp->b_resid = epp->ep_bp->b_bcount - len;

                        switch (reqp->intr_completion_reason) {
                        case USB_CR_OK:
                                epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

                                break;
                        case USB_CR_PIPE_RESET:

                                break;
                        default:
                                epp->ep_lcmd_status =
                                    ugen_cr2lcstat(
                                    reqp->intr_completion_reason);
                                bioerror(epp->ep_bp, EIO);
                        }
                }
                epp->ep_done++;
                cv_signal(&epp->ep_wait_cv);
                mutex_exit(&epp->ep_mutex);
        }

        usb_free_intr_req(reqp);
}


/*
 * handle isoc IN xfers
 */
static int
ugen_epx_isoc_IN_req(ugen_state_t *ugenp, ugen_ep_t *epp,
    struct buf *bp, boolean_t *wait)
{
        int rval = USB_SUCCESS;
        ugen_isoc_pkt_descr_t *pkt_descr;
        ushort_t n_pkt;
        uint_t pkts_len, len = 0;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_isoc_IN_req: epp=0x%p state=0x%x bp=0x%p",
            (void *)epp, epp->ep_state, (void *)bp);

        *wait = B_FALSE;

        /* check if the isoc in pkt info has been initialized */
        pkt_descr = epp->ep_isoc_info.isoc_pkt_descr;
        n_pkt = epp->ep_isoc_info.isoc_pkts_count;
        if ((n_pkt == 0) || (pkt_descr == NULL)) {
                rval = USB_FAILURE;
                epp->ep_lcmd_status = USB_LC_STAT_ISOC_UNINITIALIZED;

                goto done;
        }


        /* For OUT endpoint, return pkts transfer status of last request */
        if (UGEN_XFER_DIR(epp) != USB_EP_DIR_IN) {
                if (bp->b_bcount < sizeof (ugen_isoc_pkt_descr_t) * n_pkt) {
                        rval = USB_INVALID_REQUEST;
                        epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;

                        return (rval);
                }
                bcopy(epp->ep_isoc_info.isoc_pkt_descr, bp->b_un.b_addr,
                    n_pkt * sizeof (ugen_isoc_pkt_descr_t));
                epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

                return (USB_SUCCESS);
        }

        /* read length should be the sum of pkt descrs and data length */
        pkts_len = epp->ep_isoc_info.isoc_pkts_length;
        if (bp->b_bcount != pkts_len + sizeof (ugen_isoc_pkt_descr_t) * n_pkt) {
                rval = USB_INVALID_REQUEST;
                epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;

                goto done;
        }

        /* can we satisfy this read? */
        if (epp->ep_data) {
                len = min(MBLKL(epp->ep_data),
                    bp->b_bcount);
                /*
                 * every msg block in ep_data must be the size of
                 * pkts_len(payload length) + pkt descrs len
                 */
                ASSERT((len == 0) || (len == bp->b_bcount));
        }

        /*
         * if polling not active, restart
         * if there is some data, return the data
         */
        if ((epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLLING_ON) == 0) {
                if (len == 0) {
                        rval = USB_FAILURE;
                        if ((rval = ugen_epx_isoc_IN_start_polling(ugenp,
                            epp)) != USB_SUCCESS) {
                                epp->ep_lcmd_status =
                                    USB_LC_STAT_ISOC_POLLING_FAILED;
                        }

                        goto done;

                } else if (epp->ep_data && (len >= bp->b_bcount)) {
                        bcopy(epp->ep_data->b_rptr, bp->b_un.b_addr,
                            bp->b_bcount);
                        bp->b_resid = 0;
                        epp->ep_data->b_rptr += bp->b_bcount;

                        goto done;
                }
        }

        /*
         * if there is data or FNDELAY, return available data
         */
        if (epp->ep_data && (len >= bp->b_bcount)) {
                /* can fulfill this read request */
                bcopy(epp->ep_data->b_rptr, bp->b_un.b_addr, bp->b_bcount);
                epp->ep_data->b_rptr += bp->b_bcount;
                bp->b_resid = 0;
        } else if (epp->ep_xfer_oflag & (FNDELAY | FNONBLOCK)) {
                bp->b_resid = bp->b_bcount;
        } else {
                /* otherwise just wait for data */
                *wait = B_TRUE;
        }

done:
        /* data have been read */
        if (epp->ep_data && (epp->ep_data->b_rptr == epp->ep_data->b_wptr)) {
                mblk_t *mp = NULL;

                /* remove the just read msg block */
                mp = unlinkb(epp->ep_data);
                freemsg(epp->ep_data);

                if (mp) {
                        epp->ep_data = mp;
                } else {
                        epp->ep_data = NULL;
                }
        }

        if (*wait) {
                ASSERT(epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLLING_ON);
        }

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_isoc_IN_req end: rval=%d bcount=%lu len=%d data=0x%p",
            rval, bp->b_bcount, len, (void *)epp->ep_data);

        return (rval);
}


/*
 * Start polling on isoc endpoint, asynchronously
 */
static int
ugen_epx_isoc_IN_start_polling(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        int rval = USB_FAILURE;
        usb_isoc_req_t  *reqp;
        ugen_isoc_pkt_descr_t *pkt_descr;
        ushort_t n_pkt, pkt;
        uint_t pkts_len;

        /*
         * if polling is being stopped, we restart polling in the
         * isoc callback again
         */
        if (epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLLING_IS_STOPPED) {

                return (rval);
        }

        if ((epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLLING_ON) == 0) {
                USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_isoc_IN_start_polling: epp=0x%p state=0x%x",
                    (void *)epp, epp->ep_state);

                pkts_len = epp->ep_isoc_info.isoc_pkts_length;
                n_pkt = epp->ep_isoc_info.isoc_pkts_count;
                pkt_descr = epp->ep_isoc_info.isoc_pkt_descr;

                epp->ep_state |= UGEN_EP_STATE_ISOC_IN_POLLING_ON;
                mutex_exit(&epp->ep_mutex);

                if ((reqp = usb_alloc_isoc_req(ugenp->ug_dip, n_pkt, pkts_len,
                    USB_FLAGS_NOSLEEP)) == NULL) {
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_epx_isoc_IN_start_polling: alloc isoc "
                            "req failed");
                        mutex_enter(&epp->ep_mutex);
                        epp->ep_state &= ~UGEN_EP_STATE_ISOC_IN_POLLING_ON;

                        return (USB_NO_RESOURCES);
                }
                reqp->isoc_client_private = (usb_opaque_t)ugenp;

                reqp->isoc_attributes   = USB_ATTRS_AUTOCLEARING |
                    USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_ISOC_XFER_ASAP;

                /*
                 * isoc_pkts_length was defined to be ushort_t. This
                 * has been obsoleted by usb high speed isoc support.
                 * It is set here just for compatibility reason
                 */
                reqp->isoc_pkts_length = 0;

                for (pkt = 0; pkt < n_pkt; pkt++) {
                        reqp->isoc_pkt_descr[pkt].isoc_pkt_length =
                            pkt_descr[pkt].dsc_isoc_pkt_len;
                }
                reqp->isoc_pkts_count   = n_pkt;
                reqp->isoc_cb           = ugen_epx_isoc_IN_req_cb;
                reqp->isoc_exc_cb       = ugen_epx_isoc_IN_req_cb;

                if ((rval = usb_pipe_isoc_xfer(epp->ep_ph, reqp,
                    USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_epx_isoc_IN_start_polling: failed %d", rval);
                        usb_free_isoc_req(reqp);
                }

                mutex_enter(&epp->ep_mutex);
                if (rval != USB_SUCCESS) {
                        epp->ep_state &= ~UGEN_EP_STATE_ISOC_IN_POLLING_ON;
                }
        } else {
                rval = USB_SUCCESS;
        }

        return (rval);
}


/*
 * stop polling on an isoc endpoint, asynchronously
 */
static void
ugen_epx_isoc_IN_stop_polling(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        if ((epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLLING_ON) &&
            ((epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLLING_IS_STOPPED) == 0)) {
                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_isoc_IN_stop_polling: epp=0x%p state=0x%x",
                    (void *)epp, epp->ep_state);

                epp->ep_state |= UGEN_EP_STATE_ISOC_IN_POLLING_IS_STOPPED;
                mutex_exit(&epp->ep_mutex);
                usb_pipe_stop_isoc_polling(epp->ep_ph, USB_FLAGS_NOSLEEP);
                mutex_enter(&epp->ep_mutex);
        }
}


/*
 * poll management
 */
static void
ugen_epx_isoc_IN_poll_wakeup(ugen_state_t *ugenp, ugen_ep_t *epp)
{
        if (epp->ep_state & UGEN_EP_STATE_ISOC_IN_POLL_PENDING) {
                struct pollhead *phpp = &epp->ep_pollhead;

                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_isoc_IN_poll_wakeup: state=0x%x", epp->ep_state);

                epp->ep_state &= ~UGEN_EP_STATE_ISOC_IN_POLL_PENDING;
                mutex_exit(&epp->ep_mutex);
                pollwakeup(phpp, POLLIN);
                mutex_enter(&epp->ep_mutex);
        }
}


/*
 * callback functions for isoc IN pipe
 */
static void
ugen_epx_isoc_IN_req_cb(usb_pipe_handle_t ph, usb_isoc_req_t *reqp)
{
        ugen_state_t *ugenp = (ugen_state_t *)reqp->isoc_client_private;
        ugen_ep_t *epp = (ugen_ep_t *)usb_pipe_get_private(ph);

        if (epp == NULL) {
                /* pipe is closing */

                goto done;
        }

        ASSERT(!mutex_owned(&epp->ep_mutex)); /* not owned */

        mutex_enter(&epp->ep_mutex);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_isoc_IN_req_cb: "
            "epp=0x%p state=0x%x ph=0x%p reqp=0x%p cr=%d cb=0x%x len=%ld "
            "isoc error count=%d, pkt cnt=%d", (void *)epp, epp->ep_state,
            (void *)ph, (void *)reqp, reqp->isoc_completion_reason,
            reqp->isoc_cb_flags, (reqp->isoc_data == NULL) ? 0 :
            MBLKL(reqp->isoc_data),
            reqp->isoc_error_count, reqp->isoc_pkts_count);

        /* Too many packet errors during isoc transfer of this request */
        if (reqp->isoc_error_count == reqp->isoc_pkts_count) {
                USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "too many errors(%d) in this req, stop polling",
                    reqp->isoc_error_count);
                epp->ep_lcmd_status = USB_LC_STAT_ISOC_PKT_ERROR;
                ugen_epx_isoc_IN_stop_polling(ugenp, epp);
        }

        /* Data OK */
        if (reqp->isoc_data && !reqp->isoc_completion_reason) {
                mblk_t *mp1 = NULL, *mp2 = NULL;
                usb_isoc_pkt_descr_t *pkt_descr =
                    reqp->isoc_pkt_descr;
                ushort_t i, n_pkt = reqp->isoc_pkts_count;

                for (i = 0; i < n_pkt; i++) {
                        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "pkt %d: len=%d status=%d actual_len=%d", i,
                            pkt_descr[i].isoc_pkt_length,
                            pkt_descr[i].isoc_pkt_status,
                            pkt_descr[i].isoc_pkt_actual_length);

                        /* translate cr to ugen lcstat */
                        pkt_descr[i].isoc_pkt_status =
                            ugen_cr2lcstat(pkt_descr[i].isoc_pkt_status);
                }

                /* construct data buffer: pkt descriptors + payload */
                mp2 = allocb(sizeof (ugen_isoc_pkt_descr_t) * n_pkt, BPRI_HI);
                if (mp2 == NULL) {
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "alloc msgblk failed, discard data");
                } else {
                        /* pkt descrs first */
                        bcopy(pkt_descr, mp2->b_wptr,
                            sizeof (ugen_isoc_pkt_descr_t) * n_pkt);

                        mp2->b_wptr += sizeof (ugen_isoc_pkt_descr_t) * n_pkt;

                        /* payload follows */
                        linkb(mp2, reqp->isoc_data);

                        /* concatenate data bytes in mp2 */
                        if ((mp1 = msgpullup(mp2, -1)) != NULL) {
                                /*
                                 * now we get the required data:
                                 *      pkt descrs + payload
                                 */
                                reqp->isoc_data = NULL;
                        } else {
                                USB_DPRINTF_L2(UGEN_PRINT_XFER,
                                    ugenp->ug_log_hdl,
                                    "msgpullup status blk failed, "
                                    "discard data");
                                mp2->b_cont = NULL;
                        }

                        freemsg(mp2);
                        mp2 = NULL;
                }

                if (epp->ep_data && (mp1 != NULL)) {
                        USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ISOC ep%x coalesce ep_data",
                            epp->ep_descr.bEndpointAddress);

                        /* add mp1 to the tail of ep_data */
                        linkb(epp->ep_data, mp1);

                } else if (mp1 != NULL) {
                        USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "setting ep_data");
                        epp->ep_data = mp1;
                }
        }

        switch (reqp->isoc_completion_reason) {
        case USB_CR_OK:
                epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

                break;
        case USB_CR_PIPE_RESET:
        case USB_CR_STOPPED_POLLING:
        case USB_CR_PIPE_CLOSING:

                break;
        default:
                epp->ep_lcmd_status =
                    ugen_cr2lcstat(reqp->isoc_completion_reason);
                USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_exp_isoc_cb_req: error lcmd_status=0x%x ",
                    epp->ep_lcmd_status);

                break;
        }

        /* any non-zero completion reason signifies polling has stopped */
        if (reqp->isoc_completion_reason) {
                epp->ep_state &= ~(UGEN_EP_STATE_ISOC_IN_POLLING_ON |
                    UGEN_EP_STATE_ISOC_IN_POLLING_IS_STOPPED);
        }


        /* is there a poll pending? should we stop polling? */
        if (epp->ep_data) {
                USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_epx_isoc_IN_req_cb: data len=0x%lx, limit=0x%lx",
                    msgdsize(epp->ep_data),
                    epp->ep_buf_limit);

                ugen_epx_isoc_IN_poll_wakeup(ugenp, epp);


                /*
                 * Since isoc is unreliable xfer, if buffered data size exceeds
                 * the limit, we just discard and free data in the oldest mblk
                 */
                if (epp->ep_data &&
                    (msgdsize(epp->ep_data) >= epp->ep_buf_limit)) {
                        mblk_t *mp = NULL;

                        /* exceed buf lenth limit, remove the oldest one */
                        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_epx_isoc_IN_req_cb: overflow!");
                        mp = unlinkb(epp->ep_data);
                        if (epp->ep_data) {
                                freeb(epp->ep_data);
                        }
                        epp->ep_data = mp;
                }

        }

        if (reqp->isoc_completion_reason && epp->ep_bp) {
                bioerror(epp->ep_bp, EIO);
                epp->ep_done++;
                cv_signal(&epp->ep_wait_cv);

        } else if (epp->ep_data && epp->ep_bp && !epp->ep_done) {
                boolean_t wait;

                /* can we satisfy the read now */
                if ((ugen_epx_isoc_IN_req(ugenp, epp, epp->ep_bp, &wait) ==
                    USB_SUCCESS) && (wait == B_FALSE)) {
                        epp->ep_done++;
                        cv_signal(&epp->ep_wait_cv);
                }
        }
        mutex_exit(&epp->ep_mutex);

done:

        usb_free_isoc_req(reqp);
}

/*
 * handle isoc OUT xfers or init isoc IN polling
 */
static int
ugen_epx_isoc_OUT_req(ugen_state_t *ugenp, ugen_ep_t *epp,
    struct buf *bp, boolean_t *wait)
{
        int rval = USB_SUCCESS;
        usb_isoc_req_t *reqp;
        ugen_isoc_pkt_descr_t *pkt_descr;
        ushort_t pkt, n_pkt = 0;
        uint_t pkts_len = 0;
        uint_t head_len;
        char *p;
        ugen_isoc_req_head_t *pkth;

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_isoc_OUT_req: epp=0x%p state=0x%x bp=0x%p",
            (void *)epp, epp->ep_state, (void *)bp);

        *wait = B_FALSE;

        if (bp->b_bcount < sizeof (int)) {
                epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;
                rval = USB_INVALID_REQUEST;

                goto done;
        }

        /* LINTED E_BAD_PTR_CAST_ALIGN */
        pkth = (ugen_isoc_req_head_t *)bp->b_un.b_addr;
        n_pkt = pkth->req_isoc_pkts_count;
        head_len = sizeof (ugen_isoc_pkt_descr_t) * n_pkt +
            sizeof (int);

        if ((n_pkt == 0) ||
            (n_pkt > usb_get_max_pkts_per_isoc_request(ugenp->ug_dip)) ||
            (bp->b_bcount < head_len)) {
                USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "Invalid params: bcount=%lu, head_len=%d, pktcnt=%d",
                    bp->b_bcount, head_len, n_pkt);

                epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;
                rval = USB_INVALID_REQUEST;

                goto done;
        }

        p = bp->b_un.b_addr;
        p += sizeof (int); /* points to pkt_descrs */

        pkt_descr = kmem_zalloc(sizeof (ugen_isoc_pkt_descr_t) * n_pkt,
            KM_NOSLEEP);
        if (pkt_descr == NULL) {
                epp->ep_lcmd_status = USB_LC_STAT_NO_RESOURCES;
                rval = USB_NO_RESOURCES;

                goto done;
        }
        bcopy(p, pkt_descr, sizeof (ugen_isoc_pkt_descr_t) * n_pkt);
        p += sizeof (ugen_isoc_pkt_descr_t) * n_pkt;

        /* total packet payload length */
        for (pkt = 0; pkt < n_pkt; pkt++) {
                pkts_len += pkt_descr[pkt].dsc_isoc_pkt_len;
        }

        /*
         * write length may either be header length for isoc IN endpoint or
         * the sum of header and data pkts length for isoc OUT endpoint
         */
        if (((bp->b_bcount != head_len) &&
            (bp->b_bcount != head_len + pkts_len))) {
                USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "invalid length: bcount=%lu, head_len=%d, pkts_len = %d,"
                    "pktcnt=%d", bp->b_bcount, head_len, pkts_len, n_pkt);

                epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;
                kmem_free(pkt_descr, sizeof (ugen_isoc_pkt_descr_t) * n_pkt);
                rval = USB_INVALID_REQUEST;

                goto done;
        }


        ASSERT(epp->ep_state & UGEN_EP_STATE_XS_OPEN);

        /* Set parameters for READ */
        if (bp->b_bcount == head_len) {
                /* must be isoc IN endpoint */
                if ((UGEN_XFER_DIR(epp) & USB_EP_DIR_IN) == 0) {
                        epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;
                        kmem_free(pkt_descr, sizeof (ugen_isoc_pkt_descr_t) *
                            n_pkt);
                        rval = USB_INVALID_REQUEST;
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "write length invalid for OUT ep%x",
                            epp->ep_descr.bEndpointAddress);

                        goto done;
                }

                if (epp->ep_isoc_in_inited) {
                        epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;
                        kmem_free(pkt_descr, sizeof (ugen_isoc_pkt_descr_t) *
                            n_pkt);
                        rval = USB_INVALID_REQUEST;
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "isoc IN polling fail: already inited, need to"
                            "close the ep before initing again");

                        goto done;
                }

                /* save pkts info for the READ */
                epp->ep_isoc_info.isoc_pkts_count = n_pkt;
                epp->ep_isoc_info.isoc_pkts_length = pkts_len;
                epp->ep_isoc_info.isoc_pkt_descr = pkt_descr;

                if ((rval = ugen_epx_isoc_IN_start_polling(ugenp,
                    epp)) != USB_SUCCESS) {
                        epp->ep_lcmd_status =
                            USB_LC_STAT_ISOC_POLLING_FAILED;
                        kmem_free(pkt_descr, sizeof (ugen_isoc_pkt_descr_t) *
                            n_pkt);
                        epp->ep_isoc_info.isoc_pkts_count = 0;
                        epp->ep_isoc_info.isoc_pkts_length = 0;
                        epp->ep_isoc_info.isoc_pkt_descr = NULL;

                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "isoc IN start polling failed");

                        goto done;
                }

                epp->ep_bp->b_resid = epp->ep_bp->b_bcount - head_len;

                epp->ep_isoc_in_inited++;
                USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "isoc IN ep inited");

                goto done;
        }

        /* must be isoc OUT endpoint */
        if (UGEN_XFER_DIR(epp) & USB_EP_DIR_IN) {
                epp->ep_lcmd_status = USB_LC_STAT_INVALID_REQ;
                kmem_free(pkt_descr, sizeof (ugen_isoc_pkt_descr_t) * n_pkt);
                rval = USB_INVALID_REQUEST;
                USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "write length invalid for an IN ep%x",
                    epp->ep_descr.bEndpointAddress);

                goto done;
        }

        /* OUT endpoint, free previous info if there's any */
        if (epp->ep_isoc_info.isoc_pkt_descr) {
                kmem_free(epp->ep_isoc_info.isoc_pkt_descr,
                    sizeof (ugen_isoc_pkt_descr_t) *
                    epp->ep_isoc_info.isoc_pkts_count);
        }

        /* save pkts info for the WRITE */
        epp->ep_isoc_info.isoc_pkts_count = n_pkt;
        epp->ep_isoc_info.isoc_pkts_length = pkts_len;
        epp->ep_isoc_info.isoc_pkt_descr = pkt_descr;

        reqp = usb_alloc_isoc_req(ugenp->ug_dip, n_pkt, pkts_len,
            USB_FLAGS_NOSLEEP);
        if (reqp == NULL) {
                epp->ep_lcmd_status = USB_LC_STAT_NO_RESOURCES;
                kmem_free(pkt_descr, sizeof (ugen_isoc_pkt_descr_t) * n_pkt);
                rval = USB_NO_RESOURCES;
                epp->ep_isoc_info.isoc_pkts_count = 0;
                epp->ep_isoc_info.isoc_pkts_length = 0;
                epp->ep_isoc_info.isoc_pkt_descr = NULL;

                USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "alloc isoc out req failed");
                goto done;
        }

        for (pkt = 0; pkt < n_pkt; pkt++) {
                reqp->isoc_pkt_descr[pkt].isoc_pkt_length =
                    pkt_descr[pkt].dsc_isoc_pkt_len;
        }
        reqp->isoc_pkts_count = n_pkt;
        reqp->isoc_client_private = (usb_opaque_t)ugenp;
        reqp->isoc_attributes   = USB_ATTRS_AUTOCLEARING |
            USB_ATTRS_ISOC_XFER_ASAP;

        reqp->isoc_cb           = ugen_epx_isoc_OUT_req_cb;
        reqp->isoc_exc_cb       = ugen_epx_isoc_OUT_req_cb;

        /* copy data from bp */
        bcopy(p, reqp->isoc_data->b_wptr, pkts_len);
        reqp->isoc_data->b_wptr += pkts_len;

        mutex_exit(&epp->ep_mutex);
        if ((rval = usb_pipe_isoc_xfer(epp->ep_ph, reqp,
            USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
                mutex_enter(&epp->ep_mutex);
                epp->ep_lcmd_status =
                    ugen_cr2lcstat(reqp->isoc_completion_reason);
                usb_free_isoc_req(reqp);
                kmem_free(pkt_descr, sizeof (ugen_isoc_pkt_descr_t) * n_pkt);

                epp->ep_isoc_info.isoc_pkt_descr = NULL;
                epp->ep_isoc_info.isoc_pkts_count = 0;
                epp->ep_isoc_info.isoc_pkts_length = 0;

                USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "isoc out xfer failed");

                bioerror(bp, EIO);
        } else {
                mutex_enter(&epp->ep_mutex);
        }
        *wait = (rval == USB_SUCCESS) ? B_TRUE : B_FALSE;

done:
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_isoc_OUT_req end: rval=%d bcount=%lu xfer_len=%d",
            rval, bp->b_bcount, pkts_len);

        return (rval);
}


/*
 * callback functions for isoc OUT pipe
 */
static void
ugen_epx_isoc_OUT_req_cb(usb_pipe_handle_t ph, usb_isoc_req_t *reqp)
{
        ugen_state_t *ugenp = (ugen_state_t *)reqp->isoc_client_private;
        ugen_ep_t *epp = (ugen_ep_t *)usb_pipe_get_private(ph);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_epx_isoc_OUT_req_cb: ph=0x%p reqp=0x%p cr=%d cb=0x%x",
            (void *)ph, (void *)reqp, reqp->isoc_completion_reason,
            reqp->isoc_cb_flags);

        /* epp might be NULL if we are closing the pipe */
        if (epp) {
                ugen_isoc_pkt_info_t info;

                mutex_enter(&epp->ep_mutex);

                info = epp->ep_isoc_info;
                if (epp->ep_bp) {
                        int len, i;
                        int headlen;
                        usb_isoc_pkt_descr_t *pktdesc;

                        pktdesc = reqp->isoc_pkt_descr;
                        headlen = info.isoc_pkts_count *
                            sizeof (ugen_isoc_pkt_descr_t);

                        len = min(headlen + MBLKL(reqp->isoc_data),
                            epp->ep_bp->b_bcount);

                        epp->ep_bp->b_resid = epp->ep_bp->b_bcount - len;


                        switch (reqp->isoc_completion_reason) {
                        case USB_CR_OK:

                                epp->ep_lcmd_status = USB_LC_STAT_NOERROR;

                                for (i = 0; i < reqp->isoc_pkts_count; i++) {
                                        pktdesc[i].isoc_pkt_status =
                                            ugen_cr2lcstat(pktdesc[i].
                                            isoc_pkt_status);
                                }

                                /* save the status info */
                                bcopy(reqp->isoc_pkt_descr,
                                    info.isoc_pkt_descr,
                                    (sizeof (ugen_isoc_pkt_descr_t) *
                                    info.isoc_pkts_count));

                                break;
                        case USB_CR_PIPE_RESET:

                                break;
                        default:
                                epp->ep_lcmd_status =
                                    ugen_cr2lcstat(
                                    reqp->isoc_completion_reason);
                                bioerror(epp->ep_bp, EIO);
                        }
                }
                epp->ep_done++;
                cv_signal(&epp->ep_wait_cv);
                mutex_exit(&epp->ep_mutex);
        }

        usb_free_isoc_req(reqp);
}


/*
 * Endpoint status node management
 *
 * open/close an endpoint status node.
 *
 * Return values: errno
 */
static int
ugen_eps_open(ugen_state_t *ugenp, dev_t dev, int flag)
{
        ugen_ep_t *epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, dev)];
        int rval = EBUSY;

        mutex_enter(&epp->ep_mutex);
        USB_DPRINTF_L4(UGEN_PRINT_STAT, ugenp->ug_log_hdl,
            "ugen_eps_open: dev=0x%lx flag=0x%x state=0x%x",
            dev, flag, epp->ep_state);

        ASSERT(epp->ep_state & UGEN_EP_STATE_ACTIVE);

        /* only one open at the time */
        if ((epp->ep_state & UGEN_EP_STATE_STAT_OPEN) == 0) {
                epp->ep_state |= UGEN_EP_STATE_STAT_OPEN;
                epp->ep_stat_oflag = flag;
                rval = 0;
        }
        mutex_exit(&epp->ep_mutex);

        return (rval);
}


/*
 * close endpoint status
 */
static void
ugen_eps_close(ugen_state_t *ugenp, dev_t dev, int flag)
{
        ugen_ep_t *epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, dev)];

        mutex_enter(&epp->ep_mutex);
        USB_DPRINTF_L4(UGEN_PRINT_STAT, ugenp->ug_log_hdl,
            "ugen_eps_close: dev=0x%lx flag=0x%x state=0x%x",
            dev, flag, epp->ep_state);

        epp->ep_state &= ~(UGEN_EP_STATE_STAT_OPEN |
            UGEN_EP_STATE_INTR_IN_POLL_PENDING |
            UGEN_EP_STATE_ISOC_IN_POLL_PENDING);
        epp->ep_one_xfer = B_FALSE;

        USB_DPRINTF_L4(UGEN_PRINT_STAT, ugenp->ug_log_hdl,
            "ugen_eps_close: state=0x%x", epp->ep_state);

        ASSERT(epp->ep_state & UGEN_EP_STATE_ACTIVE);
        mutex_exit(&epp->ep_mutex);
}


/*
 * return status info
 *
 * Return values: errno
 */
static int
ugen_eps_req(ugen_state_t *ugenp, struct buf *bp)
{
        ugen_ep_t *epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, bp->b_edev)];

        mutex_enter(&epp->ep_mutex);
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_eps_req: bp=0x%p lcmd_status=0x%x bcount=%lu",
            (void *)bp, epp->ep_lcmd_status, bp->b_bcount);

        if (bp->b_flags & B_READ) {
                int len = min(sizeof (epp->ep_lcmd_status), bp->b_bcount);
                if (len) {
                        bcopy(&epp->ep_lcmd_status, bp->b_un.b_addr, len);
                }
                bp->b_resid = bp->b_bcount - len;
        } else {
                USB_DPRINTF_L3(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                    "ugen_eps_req: control=0x%x",
                    *((char *)(bp->b_un.b_addr)));

                if (epp->ep_state & UGEN_EP_STATE_XFER_OPEN) {
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_eps_req: cannot change one xfer mode if "
                            "endpoint is open");

                        mutex_exit(&epp->ep_mutex);

                        return (EINVAL);
                }

                if ((epp->ep_descr.bmAttributes & USB_EP_ATTR_INTR) &&
                    (epp->ep_descr.bEndpointAddress & USB_EP_DIR_IN)) {
                        epp->ep_one_xfer = (*((char *)(bp->b_un.b_addr)) &
                            USB_EP_INTR_ONE_XFER) ? B_TRUE : B_FALSE;
                } else {
                        USB_DPRINTF_L2(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
                            "ugen_eps_req: not an interrupt endpoint");

                        mutex_exit(&epp->ep_mutex);

                        return (EINVAL);
                }

                bp->b_resid = bp->b_bcount - 1;
        }
        mutex_exit(&epp->ep_mutex);

        return (0);
}


/*
 * device status node management
 */
static int
ugen_ds_init(ugen_state_t *ugenp)
{
        cv_init(&ugenp->ug_ds.dev_wait_cv, NULL, CV_DRIVER, NULL);

        /* Create devstat minor node for this instance */
        if (ugen_ds_minor_nodes_create(ugenp) != USB_SUCCESS) {
                USB_DPRINTF_L2(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "ugen_create_dev_stat_minor_nodes failed");

                return (USB_FAILURE);
        }


        return (USB_SUCCESS);
}


static void
ugen_ds_destroy(ugen_state_t *ugenp)
{
        cv_destroy(&ugenp->ug_ds.dev_wait_cv);
}


/*
 * open devstat minor node
 *
 * Return values: errno
 */
static int
ugen_ds_open(ugen_state_t *ugenp, dev_t dev, int flag)
{
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_ds_open: dev=0x%lx flag=0x%x", dev, flag);

        mutex_enter(&ugenp->ug_mutex);
        if ((ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_ACTIVE) == 0) {
                /*
                 * first read on device node should return status
                 */
                ugenp->ug_ds.dev_stat |= UGEN_DEV_STATUS_CHANGED |
                    UGEN_DEV_STATUS_ACTIVE;
                ugenp->ug_ds.dev_oflag = flag;
                mutex_exit(&ugenp->ug_mutex);

                return (0);
        } else {
                mutex_exit(&ugenp->ug_mutex);

                return (EBUSY);
        }
}


static void
ugen_ds_close(ugen_state_t *ugenp, dev_t dev, int flag)
{
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_ds_close: dev=0x%lx flag=0x%x", dev, flag);

        mutex_enter(&ugenp->ug_mutex);
        ugenp->ug_ds.dev_stat = UGEN_DEV_STATUS_INACTIVE;
        mutex_exit(&ugenp->ug_mutex);
}


/*
 * request for devstat
 *
 * Return values: errno
 */
static int
ugen_ds_req(ugen_state_t *ugenp, struct buf *bp)
{
        int len = min(sizeof (ugenp->ug_ds.dev_state), bp->b_bcount);

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_ds_req: bp=0x%p", (void *)bp);

        mutex_enter(&ugenp->ug_mutex);
        if ((ugenp->ug_ds.dev_oflag & (FNDELAY | FNONBLOCK)) == 0) {
                while ((ugenp->ug_ds.dev_stat &
                    UGEN_DEV_STATUS_CHANGED) == 0) {
                        if (cv_wait_sig(&ugenp->ug_ds.dev_wait_cv,
                            &ugenp->ug_mutex) <= 0) {
                                mutex_exit(&ugenp->ug_mutex);

                                return (EINTR);
                        }
                }
        } else if ((ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED) ==
            0) {
                bp->b_resid = bp->b_bcount;
                mutex_exit(&ugenp->ug_mutex);

                return (0);
        }

        ugenp->ug_ds.dev_stat &= ~UGEN_DEV_STATUS_CHANGED;
        switch (ugenp->ug_dev_state) {
        case USB_DEV_ONLINE:
                ugenp->ug_ds.dev_state = USB_DEV_STAT_ONLINE;

                break;
        case USB_DEV_DISCONNECTED:
                ugenp->ug_ds.dev_state = USB_DEV_STAT_DISCONNECTED;

                break;
        case USB_DEV_SUSPENDED:
        case USB_UGEN_DEV_UNAVAILABLE_RESUME:
                ugenp->ug_ds.dev_state = USB_DEV_STAT_RESUMED;

                break;
        case USB_UGEN_DEV_UNAVAILABLE_RECONNECT:
        default:
                ugenp->ug_ds.dev_state = USB_DEV_STAT_UNAVAILABLE;

                break;
        }

        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_ds_req: dev_state=0x%x dev_stat=0x%x",
            ugenp->ug_dev_state, ugenp->ug_ds.dev_stat);

        bcopy(&ugenp->ug_ds.dev_state, bp->b_un.b_addr, len);
        bp->b_resid = bp->b_bcount - len;

        mutex_exit(&ugenp->ug_mutex);

        return (0);
}


static void
ugen_ds_change(ugen_state_t *ugenp)
{
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_ds_change:");

        ugenp->ug_ds.dev_stat |= UGEN_DEV_STATUS_CHANGED;
        cv_signal(&ugenp->ug_ds.dev_wait_cv);
}


/*
 * poll management
 */
static void
ugen_ds_poll_wakeup(ugen_state_t *ugenp)
{
        USB_DPRINTF_L4(UGEN_PRINT_XFER, ugenp->ug_log_hdl,
            "ugen_ds_poll_wakeup:");

        if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_POLL_PENDING) {
                struct pollhead *phpp = &ugenp->ug_ds.dev_pollhead;
                ugenp->ug_ds.dev_stat &= ~UGEN_DEV_STATUS_POLL_PENDING;
                mutex_exit(&ugenp->ug_mutex);
                pollwakeup(phpp, POLLIN);
                mutex_enter(&ugenp->ug_mutex);
        }
}


/*
 * minor node management:
 */
static int
ugen_ds_minor_nodes_create(ugen_state_t *ugenp)
{
        char    node_name[32];
        int     vid = ugenp->ug_dev_data->dev_descr->idVendor;
        int     pid = ugenp->ug_dev_data->dev_descr->idProduct;
        minor_t minor;
        int     minor_index;
        int     owns_device = (usb_owns_device(ugenp->ug_dip) ?
            UGEN_OWNS_DEVICE : 0);

        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "ugen_ds_minor_nodes_create: idx shift=%d inst shift=%d",
            UGEN_MINOR_IDX_SHIFT(ugenp),
            UGEN_MINOR_INSTANCE_SHIFT(ugenp));

        if (ugenp->ug_instance >= UGEN_MINOR_INSTANCE_LIMIT(ugenp)) {
                USB_DPRINTF_L0(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "instance number too high (%d)", ugenp->ug_instance);

                return (USB_FAILURE);
        }

        /* create devstat minor node */
        if (owns_device) {
                (void) sprintf(node_name, "%x.%x.devstat", vid, pid);
        } else {
                (void) sprintf(node_name, "%x.%x.if%ddevstat", vid, pid,
                    ugenp->ug_dev_data->dev_curr_if);
        }

        minor_index = ugen_minor_index_create(ugenp,
            (UGEN_MINOR_DEV_STAT_NODE | owns_device) <<
            UGEN_MINOR_IDX_SHIFT(ugenp));

        if (minor_index < 0) {
                USB_DPRINTF_L0(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
                    "too many minor nodes");

                return (USB_FAILURE);
        }
        minor = (minor_index << UGEN_MINOR_IDX_SHIFT(ugenp)) |
            ugenp->ug_instance << UGEN_MINOR_INSTANCE_SHIFT(ugenp);

        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "minor=0x%x minor_index=%d name=%s",
            minor, minor_index, node_name);

        ASSERT(minor < L_MAXMIN);

        if ((ddi_create_minor_node(ugenp->ug_dip, node_name,
            S_IFCHR, minor, DDI_NT_UGEN, 0)) != DDI_SUCCESS) {

                return (USB_FAILURE);
        }

        ugen_store_devt(ugenp, minor);

        return (USB_SUCCESS);
}


/*
 * utility functions:
 *
 * conversion from completion reason to  USB_LC_STAT_*
 */
static struct ugen_cr2lcstat_entry {
        int     cr;
        int     lcstat;
} ugen_cr2lcstat_table[] = {
        { USB_CR_OK,                    USB_LC_STAT_NOERROR     },
        { USB_CR_CRC,                   USB_LC_STAT_CRC         },
        { USB_CR_BITSTUFFING,           USB_LC_STAT_BITSTUFFING },
        { USB_CR_DATA_TOGGLE_MM,        USB_LC_STAT_DATA_TOGGLE_MM },
        { USB_CR_STALL,                 USB_LC_STAT_STALL       },
        { USB_CR_DEV_NOT_RESP,          USB_LC_STAT_DEV_NOT_RESP },
        { USB_CR_PID_CHECKFAILURE,      USB_LC_STAT_PID_CHECKFAILURE },
        { USB_CR_UNEXP_PID,             USB_LC_STAT_UNEXP_PID   },
        { USB_CR_DATA_OVERRUN,          USB_LC_STAT_DATA_OVERRUN },
        { USB_CR_DATA_UNDERRUN,         USB_LC_STAT_DATA_UNDERRUN },
        { USB_CR_BUFFER_OVERRUN,        USB_LC_STAT_BUFFER_OVERRUN },
        { USB_CR_BUFFER_UNDERRUN,       USB_LC_STAT_BUFFER_UNDERRUN },
        { USB_CR_TIMEOUT,               USB_LC_STAT_TIMEOUT     },
        { USB_CR_NOT_ACCESSED,          USB_LC_STAT_NOT_ACCESSED },
        { USB_CR_NO_RESOURCES,          USB_LC_STAT_NO_BANDWIDTH },
        { USB_CR_UNSPECIFIED_ERR,       USB_LC_STAT_UNSPECIFIED_ERR },
        { USB_CR_STOPPED_POLLING,       USB_LC_STAT_HW_ERR      },
        { USB_CR_PIPE_CLOSING,          USB_LC_STAT_UNSPECIFIED_ERR     },
        { USB_CR_PIPE_RESET,            USB_LC_STAT_UNSPECIFIED_ERR     },
        { USB_CR_NOT_SUPPORTED,         USB_LC_STAT_UNSPECIFIED_ERR },
        { USB_CR_FLUSHED,               USB_LC_STAT_UNSPECIFIED_ERR }
};

#define UGEN_CR2LCSTAT_TABLE_SIZE (sizeof (ugen_cr2lcstat_table) / \
                        sizeof (struct ugen_cr2lcstat_entry))
static int
ugen_cr2lcstat(int cr)
{
        int i;

        for (i = 0; i < UGEN_CR2LCSTAT_TABLE_SIZE; i++) {
                if (ugen_cr2lcstat_table[i].cr == cr) {

                        return (ugen_cr2lcstat_table[i].lcstat);
                }
        }

        return (USB_LC_STAT_UNSPECIFIED_ERR);
}


/*
 * create and lookup minor index
 */
static int
ugen_minor_index_create(ugen_state_t *ugenp, ugen_minor_t minor)
{
        int i;

        /* check if already in the table */
        for (i = 1; i < ugenp->ug_minor_node_table_index; i++) {
                if (ugenp->ug_minor_node_table[i] == minor) {

                        return (-1);
                }
        }
        if (ugenp->ug_minor_node_table_index <
            (ugenp->ug_minor_node_table_size/sizeof (ugen_minor_t))) {
                ugenp->ug_minor_node_table[ugenp->
                    ug_minor_node_table_index] = minor;

                USB_DPRINTF_L4(UGEN_PRINT_ATTA, ugenp->ug_log_hdl,
                    "ugen_minor_index_create: %d: 0x%lx",
                    ugenp->ug_minor_node_table_index,
                    (unsigned long)minor);

                return (ugenp->ug_minor_node_table_index++);
        } else {

                return (-1);
        }
}


static ugen_minor_t
ugen_devt2minor(ugen_state_t *ugenp, dev_t dev)
{
        USB_DPRINTF_L4(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "ugen_devt2minor: minorindex=%lu, minor=0x%" PRIx64,
            UGEN_MINOR_GET_IDX(ugenp, dev),
            ugenp->ug_minor_node_table[UGEN_MINOR_GET_IDX(ugenp, dev)]);

        ASSERT(UGEN_MINOR_GET_IDX(ugenp, dev) <
            ugenp->ug_minor_node_table_index);

        return (ugenp->ug_minor_node_table[UGEN_MINOR_GET_IDX(ugenp, dev)]);
}


static int
ugen_is_valid_minor_node(ugen_state_t *ugenp, dev_t dev)
{
        int idx = UGEN_MINOR_GET_IDX(ugenp, dev);

        if ((idx < ugenp->ug_minor_node_table_index) &&
            (idx > 0)) {

                return (USB_SUCCESS);
        }
        USB_DPRINTF_L2(UGEN_PRINT_CBOPS, ugenp->ug_log_hdl,
            "ugen_is_valid_minor_node: invalid minorindex=%d", idx);

        return (USB_FAILURE);
}


static void
ugen_minor_node_table_create(ugen_state_t *ugenp)
{
        size_t  size = sizeof (ugen_minor_t) * UGEN_MINOR_IDX_LIMIT(ugenp);

        /* allocate the max table size needed, we reduce later */
        ugenp->ug_minor_node_table = kmem_zalloc(size, KM_SLEEP);
        ugenp->ug_minor_node_table_size = size;
        ugenp->ug_minor_node_table_index = 1;
}


static void
ugen_minor_node_table_shrink(ugen_state_t *ugenp)
{
        /* reduce the table size to save some memory */
        if (ugenp->ug_minor_node_table_index < UGEN_MINOR_IDX_LIMIT(ugenp)) {
                size_t newsize = sizeof (ugen_minor_t) *
                    ugenp->ug_minor_node_table_index;
                ugen_minor_t *buf = kmem_zalloc(newsize, KM_SLEEP);

                bcopy(ugenp->ug_minor_node_table, buf, newsize);
                kmem_free(ugenp->ug_minor_node_table,
                    ugenp->ug_minor_node_table_size);
                ugenp->ug_minor_node_table = buf;
                ugenp->ug_minor_node_table_size = newsize;
        }
}


static void
ugen_minor_node_table_destroy(ugen_state_t *ugenp)
{
        if (ugenp->ug_minor_node_table) {
                kmem_free(ugenp->ug_minor_node_table,
                    ugenp->ug_minor_node_table_size);
        }
}


static void
ugen_check_mask(uint_t mask, uint_t *shift, uint_t *limit)
{
        uint_t i, j;

        for (i = 0; i < UGEN_MINOR_NODE_SIZE; i++) {
                if ((1 << i)  & mask) {

                        break;
                }
        }

        for (j = i; j < UGEN_MINOR_NODE_SIZE; j++) {
                if (((1 << j) & mask) == 0) {

                        break;
                }
        }

        *limit = (i == j) ? 0 : 1 << (j - i);
        *shift = i;
}



/*
 * power management:
 *
 * ugen_pm_init:
 *      Initialize power management and remote wakeup functionality.
 *      No mutex is necessary in this function as it's called only by attach.
 */
static void
ugen_pm_init(ugen_state_t *ugenp)
{
        dev_info_t      *dip = ugenp->ug_dip;
        ugen_power_t    *ugenpm;

        USB_DPRINTF_L4(UGEN_PRINT_PM, ugenp->ug_log_hdl,
            "ugen_pm_init:");

        /* Allocate the state structure */
        ugenpm = kmem_zalloc(sizeof (ugen_power_t), KM_SLEEP);

        mutex_enter(&ugenp->ug_mutex);
        ugenp->ug_pm = ugenpm;
        ugenpm->pwr_wakeup_enabled = B_FALSE;
        ugenpm->pwr_current = USB_DEV_OS_FULL_PWR;
        mutex_exit(&ugenp->ug_mutex);

        /*
         * If remote wakeup is not available you may not want to do
         * power management.
         */
        if (ugen_enable_pm || usb_handle_remote_wakeup(dip,
            USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) {
                if (usb_create_pm_components(dip,
                    &ugenpm->pwr_states) == USB_SUCCESS) {
                        USB_DPRINTF_L4(UGEN_PRINT_PM,
                            ugenp->ug_log_hdl,
                            "ugen_pm_init: "
                            "created PM components");

                        mutex_enter(&ugenp->ug_mutex);
                        ugenpm->pwr_wakeup_enabled = B_TRUE;
                        mutex_exit(&ugenp->ug_mutex);

                        if (pm_raise_power(dip, 0,
                            USB_DEV_OS_FULL_PWR) != DDI_SUCCESS) {
                                USB_DPRINTF_L2(UGEN_PRINT_PM,
                                    ugenp->ug_log_hdl,
                                    "ugen_pm_init: "
                                    "raising power failed");
                        }
                } else {
                        USB_DPRINTF_L2(UGEN_PRINT_PM,
                            ugenp->ug_log_hdl,
                            "ugen_pm_init: "
                            "create_pm_comps failed");
                }
        } else {
                USB_DPRINTF_L2(UGEN_PRINT_PM,
                    ugenp->ug_log_hdl, "ugen_pm_init: "
                    "failure enabling remote wakeup");
        }

        USB_DPRINTF_L4(UGEN_PRINT_PM, ugenp->ug_log_hdl,
            "ugen_pm_init: end");
}


/*
 * ugen_pm_destroy:
 *      Shut down and destroy power management and remote wakeup functionality.
 */
static void
ugen_pm_destroy(ugen_state_t *ugenp)
{
        dev_info_t *dip = ugenp->ug_dip;

        USB_DPRINTF_L4(UGEN_PRINT_PM, ugenp->ug_log_hdl,
            "ugen_pm_destroy:");

        if (ugenp->ug_pm) {
                mutex_exit(&ugenp->ug_mutex);
                ugen_pm_busy_component(ugenp);
                mutex_enter(&ugenp->ug_mutex);

                if ((ugenp->ug_pm->pwr_wakeup_enabled) &&
                    (ugenp->ug_dev_state != USB_DEV_DISCONNECTED)) {
                        int rval;

                        mutex_exit(&ugenp->ug_mutex);
                        (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);

                        if ((rval = usb_handle_remote_wakeup(dip,
                            USB_REMOTE_WAKEUP_DISABLE)) != USB_SUCCESS) {
                                USB_DPRINTF_L4(UGEN_PRINT_PM,
                                    ugenp->ug_log_hdl, "ugen_pm_destroy: "
                                    "disabling rmt wakeup: rval=%d", rval);
                        }
                        /*
                         * Since remote wakeup is disabled now,
                         * no one can raise power
                         * and get to device once power is lowered here.
                         */
                } else {
                        mutex_exit(&ugenp->ug_mutex);
                }
                (void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF);
                ugen_pm_idle_component(ugenp);

                mutex_enter(&ugenp->ug_mutex);
                kmem_free(ugenp->ug_pm, sizeof (ugen_power_t));
                ugenp->ug_pm = NULL;
        }
}


/*
 * ugen_power :
 *      Power entry point, the workhorse behind pm_raise_power, pm_lower_power,
 *      usb_req_raise_power and usb_req_lower_power.
 */
/*ARGSUSED*/
int
usb_ugen_power(usb_ugen_hdl_t usb_ugen_hdl, int comp, int level)
{
        ugen_power_t            *pm;
        int                     rval = USB_FAILURE;
        usb_ugen_hdl_impl_t     *usb_ugen_hdl_impl =
            (usb_ugen_hdl_impl_t *)usb_ugen_hdl;
        ugen_state_t            *ugenp;
        dev_info_t              *dip;

        if (usb_ugen_hdl == NULL) {

                return (USB_FAILURE);
        }

        ugenp = usb_ugen_hdl_impl->hdl_ugenp;
        dip = ugenp->ug_dip;

        if (ugenp->ug_pm == NULL) {

                return (USB_SUCCESS);
        }

        USB_DPRINTF_L4(UGEN_PRINT_PM, ugenp->ug_log_hdl,
            "usb_ugen_power: level=%d", level);

        (void) usb_serialize_access(ugenp->ug_ser_cookie,
            USB_WAIT, 0);
        /*
         * If we are disconnected/suspended, return success. Note that if we
         * return failure, bringing down the system will hang when
         * PM tries to power up all devices
         */
        mutex_enter(&ugenp->ug_mutex);
        switch (ugenp->ug_dev_state) {
        case USB_DEV_ONLINE:

                break;
        case USB_DEV_DISCONNECTED:
        case USB_DEV_SUSPENDED:
        case USB_UGEN_DEV_UNAVAILABLE_RESUME:
        case USB_UGEN_DEV_UNAVAILABLE_RECONNECT:
        default:
                USB_DPRINTF_L2(UGEN_PRINT_PM, ugenp->ug_log_hdl,
                    "ugen_power: disconnected/suspended "
                    "dev_state=%d", ugenp->ug_dev_state);
                rval = USB_SUCCESS;

                goto done;
        }

        pm = ugenp->ug_pm;

        /* Check if we are transitioning to a legal power level */
        if (USB_DEV_PWRSTATE_OK(pm->pwr_states, level)) {
                USB_DPRINTF_L2(UGEN_PRINT_PM, ugenp->ug_log_hdl,
                    "ugen_power: illegal power level=%d "
                    "pwr_states: 0x%x", level, pm->pwr_states);

                goto done;
        }

        switch (level) {
        case USB_DEV_OS_PWR_OFF :
                switch (ugenp->ug_dev_state) {
                case USB_DEV_ONLINE:
                        /* Deny the powerdown request if the device is busy */
                        if (ugenp->ug_pm->pwr_busy != 0) {

                                break;
                        }
                        ASSERT(ugenp->ug_open_count == 0);
                        ASSERT(ugenp->ug_pending_cmds == 0);
                        ugenp->ug_pm->pwr_current = USB_DEV_OS_PWR_OFF;
                        mutex_exit(&ugenp->ug_mutex);

                        /* Issue USB D3 command to the device here */
                        rval = usb_set_device_pwrlvl3(dip);
                        mutex_enter(&ugenp->ug_mutex);

                        break;
                default:
                        rval = USB_SUCCESS;

                        break;
                }
                break;
        case USB_DEV_OS_FULL_PWR :
                /*
                 * PM framework tries to put us in full power during system
                 * shutdown.
                 */
                switch (ugenp->ug_dev_state) {
                case USB_UGEN_DEV_UNAVAILABLE_RESUME:
                case USB_UGEN_DEV_UNAVAILABLE_RECONNECT:

                        break;
                default:
                        ugenp->ug_dev_state = USB_DEV_ONLINE;

                        /* wakeup devstat reads and polls */
                        ugen_ds_change(ugenp);
                        ugen_ds_poll_wakeup(ugenp);

                        break;
                }
                ugenp->ug_pm->pwr_current = USB_DEV_OS_FULL_PWR;
                mutex_exit(&ugenp->ug_mutex);
                rval = usb_set_device_pwrlvl0(dip);
                mutex_enter(&ugenp->ug_mutex);

                break;
        default:
                /* Levels 1 and 2 are not supported to keep it simple. */
                USB_DPRINTF_L2(UGEN_PRINT_PM, ugenp->ug_log_hdl,
                    "ugen_power: power level %d not supported", level);

                break;
        }
done:
        mutex_exit(&ugenp->ug_mutex);
        usb_release_access(ugenp->ug_ser_cookie);

        return (rval);
}


static void
ugen_pm_busy_component(ugen_state_t *ugen_statep)
{
        ASSERT(!mutex_owned(&ugen_statep->ug_mutex));

        if (ugen_statep->ug_pm != NULL) {
                mutex_enter(&ugen_statep->ug_mutex);
                ugen_statep->ug_pm->pwr_busy++;

                USB_DPRINTF_L4(UGEN_PRINT_PM, ugen_statep->ug_log_hdl,
                    "ugen_pm_busy_component: %d", ugen_statep->ug_pm->pwr_busy);

                mutex_exit(&ugen_statep->ug_mutex);
                if (pm_busy_component(ugen_statep->ug_dip, 0) != DDI_SUCCESS) {
                        mutex_enter(&ugen_statep->ug_mutex);
                        ugen_statep->ug_pm->pwr_busy--;

                        USB_DPRINTF_L2(UGEN_PRINT_PM, ugen_statep->ug_log_hdl,
                            "ugen_pm_busy_component failed: %d",
                            ugen_statep->ug_pm->pwr_busy);

                        mutex_exit(&ugen_statep->ug_mutex);
                }
        }
}


static void
ugen_pm_idle_component(ugen_state_t *ugen_statep)
{
        ASSERT(!mutex_owned(&ugen_statep->ug_mutex));

        if (ugen_statep->ug_pm != NULL) {
                if (pm_idle_component(ugen_statep->ug_dip, 0) == DDI_SUCCESS) {
                        mutex_enter(&ugen_statep->ug_mutex);
                        ASSERT(ugen_statep->ug_pm->pwr_busy > 0);
                        ugen_statep->ug_pm->pwr_busy--;

                        USB_DPRINTF_L4(UGEN_PRINT_PM, ugen_statep->ug_log_hdl,
                            "ugen_pm_idle_component: %d",
                            ugen_statep->ug_pm->pwr_busy);

                        mutex_exit(&ugen_statep->ug_mutex);
                }
        }
}


/*
 * devt lookup support
 *      In ugen_strategy and ugen_minphys, we only have the devt and need
 *      the ugen_state pointer. Since we don't know instance mask, we can't
 *      easily derive a softstate pointer. Therefore, we use a list
 */
static void
ugen_store_devt(ugen_state_t *ugenp, minor_t minor)
{
        ugen_devt_list_entry_t *e = kmem_zalloc(
            sizeof (ugen_devt_list_entry_t), KM_SLEEP);
        ugen_devt_list_entry_t *t;

        mutex_enter(&ugen_devt_list_mutex);
        e->list_dev = makedevice(ddi_driver_major(ugenp->ug_dip), minor);
        e->list_state = ugenp;

        t = ugen_devt_list.list_next;

        /* check if the entry is already in the list */
        while (t) {
                ASSERT(t->list_dev != e->list_dev);
                t = t->list_next;
        }

        /* add to the head of the list */
        e->list_next = ugen_devt_list.list_next;
        if (ugen_devt_list.list_next) {
                ugen_devt_list.list_next->list_prev = e;
        }
        ugen_devt_list.list_next = e;
        mutex_exit(&ugen_devt_list_mutex);
}


static ugen_state_t *
ugen_devt2state(dev_t dev)
{
        ugen_devt_list_entry_t *t;
        ugen_state_t    *ugenp = NULL;
        int             index, count;

        mutex_enter(&ugen_devt_list_mutex);

        for (index = ugen_devt_cache_index, count = 0;
            count < UGEN_DEVT_CACHE_SIZE; count++) {
                if (ugen_devt_cache[index].cache_dev == dev) {
                        ugen_devt_cache[index].cache_hit++;
                        ugenp = ugen_devt_cache[index].cache_state;

                        mutex_exit(&ugen_devt_list_mutex);

                        return (ugenp);
                }
                index++;
                index %= UGEN_DEVT_CACHE_SIZE;
        }

        t = ugen_devt_list.list_next;

        while (t) {
                if (t->list_dev == dev) {
                        ugenp = t->list_state;
                        ugen_devt_cache_index++;
                        ugen_devt_cache_index %= UGEN_DEVT_CACHE_SIZE;
                        ugen_devt_cache[ugen_devt_cache_index].cache_dev = dev;
                        ugen_devt_cache[ugen_devt_cache_index].cache_state =
                            ugenp;
                        mutex_exit(&ugen_devt_list_mutex);

                        return (ugenp);
                }
                t = t->list_next;
        }
        mutex_exit(&ugen_devt_list_mutex);

        return (ugenp);
}


static void
ugen_free_devt(ugen_state_t *ugenp)
{
        ugen_devt_list_entry_t *e, *next, *prev;
        major_t         major = ddi_driver_major(ugenp->ug_dip);
        int             instance = ddi_get_instance(ugenp->ug_dip);

        mutex_enter(&ugen_devt_list_mutex);
        prev = &ugen_devt_list;
        for (e = prev->list_next; e != 0; e = next) {
                int i = (getminor(e->list_dev) &
                    ugenp->ug_hdl->hdl_minor_node_instance_mask) >>
                    ugenp->ug_hdl->hdl_minor_node_instance_shift;
                int m = getmajor(e->list_dev);

                next = e->list_next;

                if ((i == instance) && (m == major)) {
                        prev->list_next = e->list_next;
                        if (e->list_next) {
                                e->list_next->list_prev = prev;
                        }
                        kmem_free(e, sizeof (ugen_devt_list_entry_t));
                } else {
                        prev = e;
                }
        }

        bzero(ugen_devt_cache, sizeof (ugen_devt_cache));
        ugen_devt_cache_index = 0;
        mutex_exit(&ugen_devt_list_mutex);
}