root/usr/src/uts/common/io/usb/usba/usba.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
 * Copyright 2016 James S. Blachly, MD <james.blachly@gmail.com>
 * Copyright 2019 Joyent, Inc.
 * Copyright 2023 Oxide Computer Company
 */


/*
 * USBA: Solaris USB Architecture support
 */
#define USBA_FRAMEWORK
#include <sys/usb/usba/usba_impl.h>
#include <sys/usb/usba/hcdi_impl.h>
#include <sys/usb/hubd/hub.h>
#include <sys/fs/dv_node.h>

/*
 * USBA private variables and tunables
 */
static kmutex_t usba_mutex;

/* mutex to protect usba_root_hubs */
static kmutex_t usba_hub_mutex;

typedef struct usba_root_hub_ent {
        dev_info_t *dip;
        struct usba_root_hub_ent *next;
}usba_root_hub_ent_t;

static usba_root_hub_ent_t *usba_root_hubs = NULL;

/*
 * ddivs forced binding:
 *
 *    usbc usbc_xhubs usbc_xaddress  node name
 *
 *      0       x       x       class name or "device"
 *
 *      1       0       0       ddivs_usbc
 *      1       0       >1      ddivs_usbc except device
 *                              at usbc_xaddress
 *      1       1       0       ddivs_usbc except hubs
 *      1       1       >1      ddivs_usbc except hubs and
 *                              device at usbc_xaddress
 */
uint_t usba_ddivs_usbc;
uint_t usba_ddivs_usbc_xhubs;
uint_t usba_ddivs_usbc_xaddress;

uint_t usba_ugen_force_binding;

/*
 * compatible name handling
 */
/*
 * allowing for 15 compat names, plus one force bind name and
 * one possible specified client driver name
 */
#define USBA_MAX_COMPAT_NAMES           17
#define USBA_MAX_COMPAT_NAME_LEN        64

/* double linked list for usba_devices */
usba_list_entry_t       usba_device_list;

_NOTE(MUTEX_PROTECTS_DATA(usba_mutex, usba_device_list))

/*
 * modload support
 */

static struct modlmisc modlmisc = {
        &mod_miscops,   /* Type of module */
        "USBA: USB Architecture 2.0 1.66"
};

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


static usb_log_handle_t usba_log_handle;
uint_t          usba_errlevel = USB_LOG_L4;
uint_t          usba_errmask = (uint_t)-1;

extern usb_log_handle_t hubdi_log_handle;

int
_init(void)
{
        int rval;

        /*
         * usbai providing log support needs to be init'ed first
         * and destroyed last
         */
        usba_usbai_initialization();
        usba_usba_initialization();
        usba_usbai_register_initialization();
        usba_hcdi_initialization();
        usba_hubdi_initialization();
        usba_devdb_initialization();

        if ((rval = mod_install(&modlinkage)) != 0) {
                usba_devdb_destroy();
                usba_hubdi_destroy();
                usba_hcdi_destroy();
                usba_usbai_register_destroy();
                usba_usba_destroy();
                usba_usbai_destroy();
        }

        return (rval);
}

int
_fini()
{
        int rval;

        if ((rval = mod_remove(&modlinkage)) == 0) {
                usba_devdb_destroy();
                usba_hubdi_destroy();
                usba_hcdi_destroy();
                usba_usbai_register_destroy();
                usba_usba_destroy();
                usba_usbai_destroy();
        }

        return (rval);
}

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

boolean_t
usba_owns_ia(dev_info_t *dip)
{
        int if_count = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "interface-count", 0);

        return ((if_count) ? B_TRUE : B_FALSE);
}

/*
 * common bus ctl for hcd, usb_mid, and hubd
 */
int
usba_bus_ctl(
    dev_info_t          *dip,
    dev_info_t          *rdip,
    ddi_ctl_enum_t      op,
    void                *arg,
    void                *result)
{
        dev_info_t              *child_dip = (dev_info_t *)arg;
        usba_device_t           *usba_device;
        usba_hcdi_t             *usba_hcdi;
        usba_hcdi_ops_t         *usba_hcdi_ops;

        USB_DPRINTF_L4(DPRINT_MASK_USBA, hubdi_log_handle,
            "usba_bus_ctl: %s%d %s%d op=%d", ddi_node_name(rdip),
            ddi_get_instance(rdip), ddi_node_name(dip),
            ddi_get_instance(dip), op);

        switch (op) {

        case DDI_CTLOPS_REPORTDEV:
        {
                char *name, compat_name[64], *speed;
                usba_device_t   *hub_usba_device;
                dev_info_t      *hubdip;

                usba_device = usba_get_usba_device(rdip);

                /* find the parent hub */
                hubdip = ddi_get_parent(rdip);
                while ((strcmp(ddi_driver_name(hubdip), "hubd") != 0) &&
                    !(usba_is_root_hub(hubdip))) {
                        hubdip = ddi_get_parent(hubdip);
                }

                hub_usba_device = usba_get_usba_device(hubdip);

                if (usba_device) {
                        if (usb_owns_device(rdip)) {
                                (void) snprintf(compat_name,
                                    sizeof (compat_name),
                                    "usb%x,%x",
                                    usba_device->usb_dev_descr->idVendor,
                                    usba_device->usb_dev_descr->idProduct);
                        } else if (usba_owns_ia(rdip)) {
                                (void) snprintf(compat_name,
                                    sizeof (compat_name),
                                    "usbia%x,%x.config%x.%x",
                                    usba_device->usb_dev_descr->idVendor,
                                    usba_device->usb_dev_descr->idProduct,
                                    usba_device->usb_cfg_value,
                                    usb_get_if_number(rdip));
                        } else {
                                (void) snprintf(compat_name,
                                    sizeof (compat_name),
                                    "usbif%x,%x.config%x.%x",
                                    usba_device->usb_dev_descr->idVendor,
                                    usba_device->usb_dev_descr->idProduct,
                                    usba_device->usb_cfg_value,
                                    usb_get_if_number(rdip));
                        }
                        switch (usba_device->usb_port_status) {
                        case USBA_SUPER_SPEED_DEV:
                                speed = "super speed (USB 3.x)";
                                break;
                        case USBA_HIGH_SPEED_DEV:
                                speed = "hi speed (USB 2.x)";
                                break;
                        case USBA_LOW_SPEED_DEV:
                                speed = "low speed (USB 1.x)";
                                break;
                        case USBA_FULL_SPEED_DEV:
                        default:
                                speed = "full speed (USB 1.x)";
                                break;
                        }

                        cmn_err(CE_CONT,
                            "?USB %x.%x %s (%s) operating at %s on "
                            "USB %x.%x %s hub: "
                            "%s@%s, %s%d at bus address %d\n",
                            (usba_device->usb_dev_descr->bcdUSB & 0xff00) >> 8,
                            usba_device->usb_dev_descr->bcdUSB & 0xff,
                            (usb_owns_device(rdip) ? "device" :
                            ((usba_owns_ia(rdip) ? "interface-association" :
                            "interface"))),
                            compat_name, speed,
                            (hub_usba_device->usb_dev_descr->bcdUSB &
                            0xff00) >> 8,
                            hub_usba_device->usb_dev_descr->bcdUSB & 0xff,
                            usba_is_root_hub(hubdip) ? "root" : "external",
                            ddi_node_name(rdip), ddi_get_name_addr(rdip),
                            ddi_driver_name(rdip),
                            ddi_get_instance(rdip), usba_device->usb_addr);

                        name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
                        (void) usba_get_mfg_prod_sn_str(rdip, name, MAXNAMELEN);
                        if (name[0] != '\0') {
                                cmn_err(CE_CONT, "?%s\n", name);
                        }
                        kmem_free(name, MAXNAMELEN);

                } else { /* harden USBA against this case; if it happens */

                        cmn_err(CE_CONT,
                            "?USB-device: %s@%s, %s%d\n",
                            ddi_node_name(rdip), ddi_get_name_addr(rdip),
                            ddi_driver_name(rdip), ddi_get_instance(rdip));
                }

                return (DDI_SUCCESS);
        }

        case DDI_CTLOPS_INITCHILD:
        {
                int                     usb_addr;
                uint_t                  n;
                char                    name[32];
                int                     *data;
                int                     rval;
                int                     len = sizeof (usb_addr);

                usba_hcdi       = usba_hcdi_get_hcdi(dip);
                usba_hcdi_ops   = usba_hcdi->hcdi_ops;
                ASSERT(usba_hcdi_ops != NULL);

                /*
                 * as long as the dip exists, it should have
                 * usba_device structure associated with it
                 */
                usba_device = usba_get_usba_device(child_dip);
                if (usba_device == NULL) {

                        USB_DPRINTF_L2(DPRINT_MASK_USBA, hubdi_log_handle,
                            "usba_bus_ctl: DDI_NOT_WELL_FORMED (%s (0x%p))",
                            ddi_node_name(child_dip), (void *)child_dip);

                        return (DDI_NOT_WELL_FORMED);
                }

                /* the dip should have an address and reg property */
                if (ddi_prop_op(DDI_DEV_T_NONE, child_dip, PROP_LEN_AND_VAL_BUF,
                    DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "assigned-address",
                    (caddr_t)&usb_addr, &len) != DDI_SUCCESS) {

                        USB_DPRINTF_L2(DPRINT_MASK_USBA, hubdi_log_handle,
                            "usba_bus_ctl:\n\t"
                            "%s%d %s%d op=%d rdip = 0x%p dip = 0x%p",
                            ddi_node_name(rdip), ddi_get_instance(rdip),
                            ddi_node_name(dip), ddi_get_instance(dip), op,
                            (void *)rdip, (void *)dip);

                        USB_DPRINTF_L2(DPRINT_MASK_USBA, hubdi_log_handle,
                            "usba_bus_ctl: DDI_NOT_WELL_FORMED (%s (0x%p))",
                            ddi_node_name(child_dip), (void *)child_dip);

                        return (DDI_NOT_WELL_FORMED);
                }

                if ((rval = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, child_dip,
                    DDI_PROP_DONTPASS, "reg",
                    &data, &n)) != DDI_SUCCESS) {

                        USB_DPRINTF_L2(DPRINT_MASK_USBA, hubdi_log_handle,
                            "usba_bus_ctl: %d, DDI_NOT_WELL_FORMED", rval);

                        return (DDI_NOT_WELL_FORMED);
                }


                /*
                 * if the configuration is 1, the unit address is
                 * just the interface number
                 */
                if ((n == 1) || ((n > 1) && (data[1] == 1))) {
                        (void) sprintf(name, "%x", data[0]);
                } else {
                        (void) sprintf(name, "%x,%x", data[0], data[1]);
                }

                USB_DPRINTF_L3(DPRINT_MASK_USBA,
                    hubdi_log_handle, "usba_bus_ctl: name = %s", name);

                ddi_prop_free(data);
                ddi_set_name_addr(child_dip, name);

                /*
                 * increment the reference count for each child using this
                 * usba_device structure
                 */
                mutex_enter(&usba_device->usb_mutex);
                usba_device->usb_ref_count++;

                USB_DPRINTF_L3(DPRINT_MASK_USBA, hubdi_log_handle,
                    "usba_bus_ctl: init usba_device = 0x%p ref_count = %d",
                    (void *)usba_device, usba_device->usb_ref_count);

                mutex_exit(&usba_device->usb_mutex);

                return (DDI_SUCCESS);
        }

        case DDI_CTLOPS_UNINITCHILD:
        {
                usba_device = usba_get_usba_device(child_dip);

                if (usba_device != NULL) {
                        /*
                         * decrement the reference count for each child
                         * using this  usba_device structure
                         */
                        mutex_enter(&usba_device->usb_mutex);
                        usba_device->usb_ref_count--;

                        USB_DPRINTF_L3(DPRINT_MASK_USBA, hubdi_log_handle,
                            "usba_hcdi_bus_ctl: uninit usba_device=0x%p "
                            "ref_count=%d",
                            (void *)usba_device, usba_device->usb_ref_count);

                        mutex_exit(&usba_device->usb_mutex);
                }
                ddi_set_name_addr(child_dip, NULL);

                return (DDI_SUCCESS);
        }

        case DDI_CTLOPS_IOMIN:
                /* Do nothing */
                return (DDI_SUCCESS);

        /*
         * These ops correspond to functions that "shouldn't" be called
         * by a USB client driver.  So  we whine when we're called.
         */
        case DDI_CTLOPS_DMAPMAPC:
        case DDI_CTLOPS_REPORTINT:
        case DDI_CTLOPS_REGSIZE:
        case DDI_CTLOPS_NREGS:
        case DDI_CTLOPS_SIDDEV:
        case DDI_CTLOPS_SLAVEONLY:
        case DDI_CTLOPS_AFFINITY:
        case DDI_CTLOPS_POKE:
        case DDI_CTLOPS_PEEK:
                cmn_err(CE_CONT, "%s%d: invalid op (%d) from %s%d",
                    ddi_node_name(dip), ddi_get_instance(dip),
                    op, ddi_node_name(rdip), ddi_get_instance(rdip));
                return (DDI_FAILURE);

        /*
         * Everything else (e.g. PTOB/BTOP/BTOPR requests) we pass up
         */
        default:
                return (ddi_ctlops(dip, rdip, op, arg, result));
        }
}


/*
 * initialize and destroy USBA module
 */
void
usba_usba_initialization()
{
        usba_log_handle = usb_alloc_log_hdl(NULL, "usba", &usba_errlevel,
            &usba_errmask, NULL, 0);

        USB_DPRINTF_L4(DPRINT_MASK_USBA,
            usba_log_handle, "usba_usba_initialization");

        mutex_init(&usba_mutex, NULL, MUTEX_DRIVER, NULL);
        mutex_init(&usba_hub_mutex, NULL, MUTEX_DRIVER, NULL);
        usba_init_list(&usba_device_list, NULL, NULL);
}


void
usba_usba_destroy()
{
        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle, "usba_usba_destroy");

        mutex_destroy(&usba_hub_mutex);
        mutex_destroy(&usba_mutex);
        usba_destroy_list(&usba_device_list);

        usb_free_log_hdl(usba_log_handle);
}


/*
 * usba_set_usb_address:
 *      set usb address in usba_device structure
 */
int
usba_set_usb_address(usba_device_t *usba_device)
{
        usb_addr_t address;
        uchar_t s = 8;
        usba_hcdi_t *hcdi;
        char *usb_address_in_use;

        mutex_enter(&usba_device->usb_mutex);

        hcdi = usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip);

        mutex_enter(&hcdi->hcdi_mutex);
        usb_address_in_use = hcdi->hcdi_usb_address_in_use;

        for (address = ROOT_HUB_ADDR + 1;
            address <= USBA_MAX_ADDRESS; address++) {
                if (usb_address_in_use[address/s] & (1 << (address % s))) {
                        continue;
                }
                usb_address_in_use[address/s] |= (1 << (address % s));
                hcdi->hcdi_device_count++;
                HCDI_HOTPLUG_STATS_DATA(hcdi)->hcdi_device_count.value.ui64++;
                mutex_exit(&hcdi->hcdi_mutex);

                USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_set_usb_address: %d", address);

                usba_device->usb_addr = address;

                mutex_exit(&usba_device->usb_mutex);

                return (USB_SUCCESS);
        }

        usba_device->usb_addr = 0;

        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
            "no usb address available");

        mutex_exit(&hcdi->hcdi_mutex);
        mutex_exit(&usba_device->usb_mutex);

        return (USB_FAILURE);
}


/*
 * usba_unset_usb_address:
 *      unset usb_address in usba_device structure
 */
void
usba_unset_usb_address(usba_device_t *usba_device)
{
        usb_addr_t address;
        usba_hcdi_t *hcdi;
        uchar_t s = 8;
        char *usb_address_in_use;

        mutex_enter(&usba_device->usb_mutex);
        address = usba_device->usb_addr;
        hcdi = usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip);

        if (address > ROOT_HUB_ADDR) {
                USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_unset_usb_address: address=%d", address);

                mutex_enter(&hcdi->hcdi_mutex);
                usb_address_in_use = hcdi->hcdi_usb_address_in_use;

                ASSERT(usb_address_in_use[address/s] & (1 << (address % s)));

                usb_address_in_use[address/s] &= ~(1 << (address % s));

                hcdi->hcdi_device_count--;
                HCDI_HOTPLUG_STATS_DATA(hcdi)->hcdi_device_count.value.ui64--;

                mutex_exit(&hcdi->hcdi_mutex);

                usba_device->usb_addr = 0;
        }
        mutex_exit(&usba_device->usb_mutex);
}


struct usba_evdata *
usba_get_evdata(dev_info_t *dip)
{
        usba_evdata_t *evdata;
        usba_device_t *usba_device = usba_get_usba_device(dip);

        /* called when dip attaches */
        ASSERT(usba_device != NULL);

        mutex_enter(&usba_device->usb_mutex);
        evdata = usba_device->usb_evdata;
        while (evdata) {
                if (evdata->ev_dip == dip) {
                        mutex_exit(&usba_device->usb_mutex);

                        return (evdata);
                }
                evdata = evdata->ev_next;
        }

        evdata = kmem_zalloc(sizeof (usba_evdata_t), KM_SLEEP);
        evdata->ev_dip = dip;
        evdata->ev_next = usba_device->usb_evdata;
        usba_device->usb_evdata = evdata;
        mutex_exit(&usba_device->usb_mutex);

        return (evdata);
}


/*
 * allocate a usb device structure and link it in the list
 */
usba_device_t *
usba_alloc_usba_device(dev_info_t *root_hub_dip)
{
        usba_device_t   *usba_device;
        int             ep_idx;
        ddi_iblock_cookie_t iblock_cookie =
            usba_hcdi_get_hcdi(root_hub_dip)->hcdi_iblock_cookie;

        /*
         * create a new usba_device structure
         */
        usba_device = kmem_zalloc(sizeof (usba_device_t), KM_SLEEP);

        /*
         * initialize usba_device
         */
        mutex_init(&usba_device->usb_mutex, NULL, MUTEX_DRIVER,
            iblock_cookie);

        usba_init_list(&usba_device->usb_device_list, (usb_opaque_t)usba_device,
            iblock_cookie);
        usba_init_list(&usba_device->usb_allocated, (usb_opaque_t)usba_device,
            iblock_cookie);
        mutex_enter(&usba_device->usb_mutex);
        usba_device->usb_root_hub_dip = root_hub_dip;

        /*
         * add to list of usba_devices
         */
        usba_add_to_list(&usba_device_list, &usba_device->usb_device_list);

        /* init mutex in each usba_ph_impl structure */
        for (ep_idx = 0; ep_idx < USBA_N_ENDPOINTS; ep_idx++) {
                mutex_init(&usba_device->usb_ph_list[ep_idx].usba_ph_mutex,
                    NULL, MUTEX_DRIVER, iblock_cookie);
        }

        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
            "allocated usba_device 0x%p", (void *)usba_device);

        mutex_exit(&usba_device->usb_mutex);

        return (usba_device);
}


/* free NDI event data associated with usba_device */
void
usba_free_evdata(usba_evdata_t *evdata)
{
        usba_evdata_t *next;

        while (evdata) {
                next = evdata->ev_next;
                kmem_free(evdata, sizeof (usba_evdata_t));
                evdata = next;
        }
}


/*
 * free usb device structure
 */
void
usba_free_usba_device(usba_device_t *usba_device)
{
        int                     i, ep_idx;
        usb_pipe_handle_t       def_ph;

        if (usba_device == NULL) {

                return;
        }

        mutex_enter(&usba_device->usb_mutex);
        if (usba_device->usb_ref_count) {
                mutex_exit(&usba_device->usb_mutex);

                return;
        }

        USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
            "usba_free_usba_device 0x%p, address=0x%x, ref cnt=%d",
            (void *)usba_device, usba_device->usb_addr,
            usba_device->usb_ref_count);

        usba_free_evdata(usba_device->usb_evdata);
        mutex_exit(&usba_device->usb_mutex);

        def_ph = usba_usbdev_to_dflt_pipe_handle(usba_device);
        if (def_ph != NULL) {
                usba_pipe_handle_data_t *ph_data = usba_get_ph_data(def_ph);

                if (ph_data) {
                        usb_pipe_close(ph_data->p_dip, def_ph,
                            USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED,
                            NULL, NULL);
                }
        }

        /*
         * Give the HCD a chance to clean up this child device before we finish
         * tearing things down.
         */
        if (usba_device->usb_hcdi_ops->usba_hcdi_device_fini != NULL) {
                usba_device->usb_hcdi_ops->usba_hcdi_device_fini(
                    usba_device, usba_device->usb_hcd_private);
                usba_device->usb_hcd_private = NULL;
        }

        mutex_enter(&usba_mutex);

        /* destroy mutex in each usba_ph_impl structure */
        for (ep_idx = 0; ep_idx < USBA_N_ENDPOINTS; ep_idx++) {
                mutex_destroy(&usba_device->usb_ph_list[ep_idx].usba_ph_mutex);
        }

        (void) usba_rm_from_list(&usba_device_list,
            &usba_device->usb_device_list);

        mutex_exit(&usba_mutex);

        usba_destroy_list(&usba_device->usb_device_list);
        usba_destroy_list(&usba_device->usb_allocated);

        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
            "deallocating usba_device = 0x%p, address = 0x%x",
            (void *)usba_device, usba_device->usb_addr);

        /*
         * ohci allocates descriptors for root hub so we can't
         * deallocate these here
         */

        if (usba_device->usb_addr != ROOT_HUB_ADDR) {
                if (usba_device->usb_cfg_array) {
                        USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                            "deallocating usb_config_array: 0x%p",
                            (void *)usba_device->usb_cfg_array);
                        mutex_enter(&usba_device->usb_mutex);
                        for (i = 0;
                            i < usba_device->usb_dev_descr->bNumConfigurations;
                            i++) {
                                if (usba_device->usb_cfg_array[i]) {
                                        kmem_free(
                                            usba_device->usb_cfg_array[i],
                                            usba_device->usb_cfg_array_len[i]);
                                }
                        }

                        /* free the array pointers */
                        kmem_free(usba_device->usb_cfg_array,
                            usba_device->usb_cfg_array_length);
                        kmem_free(usba_device->usb_cfg_array_len,
                            usba_device->usb_cfg_array_len_length);

                        mutex_exit(&usba_device->usb_mutex);
                }

                if (usba_device->usb_cfg_str_descr) {
                        USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                            "deallocating usb_cfg_str_descr: 0x%p",
                            (void *)usba_device->usb_cfg_str_descr);
                        for (i = 0;
                            i < usba_device->usb_dev_descr->bNumConfigurations;
                            i++) {
                                if (usba_device->usb_cfg_str_descr[i]) {
                                        kmem_free(
                                            usba_device->usb_cfg_str_descr[i],
                                            strlen(usba_device->
                                            usb_cfg_str_descr[i]) + 1);
                                }
                        }
                        /* free the array pointers */
                        kmem_free(usba_device->usb_cfg_str_descr,
                            sizeof (uchar_t *) * usba_device->usb_n_cfgs);
                }

                if (usba_device->usb_dev_descr) {
                        kmem_free(usba_device->usb_dev_descr,
                            sizeof (usb_dev_descr_t));
                }

                if (usba_device->usb_mfg_str) {
                        kmem_free(usba_device->usb_mfg_str,
                            strlen(usba_device->usb_mfg_str) + 1);
                }

                if (usba_device->usb_product_str) {
                        kmem_free(usba_device->usb_product_str,
                            strlen(usba_device->usb_product_str) + 1);
                }

                if (usba_device->usb_serialno_str) {
                        kmem_free(usba_device->usb_serialno_str,
                            strlen(usba_device->usb_serialno_str) + 1);
                }

                usba_free_binary_object_store(usba_device);

                usba_unset_usb_address(usba_device);
        }

#ifndef __lock_lint
        ASSERT(usba_device->usb_client_dev_data_list.cddl_next == NULL);
#endif

        if (usba_device->usb_client_flags) {
#ifndef __lock_lint
                int i;

                for (i = 0; i < usba_device->usb_n_ifs; i++) {
                        ASSERT(usba_device->usb_client_flags[i] == 0);
                }
#endif
                kmem_free(usba_device->usb_client_flags,
                    usba_device->usb_n_ifs * USBA_CLIENT_FLAG_SIZE);
        }


        if (usba_device->usb_client_attach_list) {
                kmem_free(usba_device->usb_client_attach_list,
                    usba_device->usb_n_ifs *
                    sizeof (*usba_device->usb_client_attach_list));
        }
        if (usba_device->usb_client_ev_cb_list) {
                kmem_free(usba_device->usb_client_ev_cb_list,
                    usba_device->usb_n_ifs *
                    sizeof (*usba_device->usb_client_ev_cb_list));
        }

        /*
         * finally ready to destroy the structure
         */
        mutex_destroy(&usba_device->usb_mutex);

        kmem_free((caddr_t)usba_device, sizeof (usba_device_t));
}


/* clear the data toggle for all endpoints on this device */
void
usba_clear_data_toggle(usba_device_t *usba_device)
{
        int     i;

        if (usba_device != NULL) {
                mutex_enter(&usba_device->usb_mutex);
                for (i = 0; i < USBA_N_ENDPOINTS; i++) {
                        usba_device->usb_ph_list[i].usba_ph_flags &=
                            ~USBA_PH_DATA_TOGGLE;
                }
                mutex_exit(&usba_device->usb_mutex);
        }
}


/*
 * usba_create_child_devi():
 *      create a child devinfo node, usba_device, attach properties.
 *      the usba_device structure is shared between all interfaces
 */
int
usba_create_child_devi(dev_info_t       *dip,
    char                                *node_name,
    usba_hcdi_ops_t                     *usba_hcdi_ops,
    dev_info_t                          *usb_root_hub_dip,
    usb_port_status_t                   port_status,
    usba_device_t                       *usba_device,
    dev_info_t                          **child_dip)
{
        int rval = USB_FAILURE;
        int usba_device_allocated = 0;
        usb_addr_t      address;

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_create_child_devi: %s usba_device=0x%p "
            "port status=0x%x", node_name,
            (void *)usba_device, port_status);

        ndi_devi_alloc_sleep(dip, node_name, (pnode_t)DEVI_SID_NODEID,
            child_dip);

        USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
            "child dip=0x%p", (void *)*child_dip);

        if (usba_device == NULL) {

                usba_device = usba_alloc_usba_device(usb_root_hub_dip);

                /* grab the mutex to keep warlock happy */
                mutex_enter(&usba_device->usb_mutex);
                usba_device->usb_hcdi_ops       = usba_hcdi_ops;
                usba_device->usb_port_status    = port_status;
                mutex_exit(&usba_device->usb_mutex);

                usba_device_allocated++;
        } else {
                mutex_enter(&usba_device->usb_mutex);
                if (usba_hcdi_ops) {
                        ASSERT(usba_device->usb_hcdi_ops == usba_hcdi_ops);
                }
                if (usb_root_hub_dip) {
                        ASSERT(usba_device->usb_root_hub_dip ==
                            usb_root_hub_dip);
                }

                usba_device->usb_port_status    = port_status;

                mutex_exit(&usba_device->usb_mutex);
        }

        if (usba_device->usb_addr == 0) {
                if (usba_set_usb_address(usba_device) == USB_FAILURE) {
                        address = 0;

                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "cannot set usb address for dip=0x%p",
                            (void *)*child_dip);

                        goto fail;
                }
        }
        address = usba_device->usb_addr;

        /* attach properties */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, *child_dip,
            "assigned-address", address);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "cannot set usb address property for dip=0x%p",
                    (void *)*child_dip);
                rval = USB_FAILURE;

                goto fail;
        }

        /*
         * store the usba_device point in the dip
         */
        usba_set_usba_device(*child_dip, usba_device);

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_create_child_devi: devi=0x%p (%s) ud=0x%p",
            (void *)*child_dip, ddi_driver_name(*child_dip),
            (void *)usba_device);

        return (USB_SUCCESS);

fail:
        if (*child_dip) {
                int rval = usba_destroy_child_devi(*child_dip, NDI_DEVI_REMOVE);
                ASSERT(rval == USB_SUCCESS);
                *child_dip = NULL;
        }

        if (usba_device_allocated) {
                usba_free_usba_device(usba_device);
        } else if (address && usba_device) {
                usba_unset_usb_address(usba_device);
        }

        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
            "usba_create_child_devi failed: rval=%d", rval);

        return (rval);
}


int
usba_destroy_child_devi(dev_info_t *dip, uint_t flag)
{
        usba_device_t   *usba_device;
        int             rval = NDI_SUCCESS;

        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
            "usba_destroy_child_devi: %s%d (0x%p)",
            ddi_driver_name(dip), ddi_get_instance(dip), (void *)dip);

        usba_device = usba_get_usba_device(dip);

        /*
         * if the child hasn't been bound yet, we can just
         * free the dip
         */
        if (i_ddi_node_state(dip) < DS_INITIALIZED) {
                /*
                 * do not call ndi_devi_free() since it might
                 * deadlock
                 */
                rval = ddi_remove_child(dip, 0);

        } else {
                char *devnm = kmem_alloc(MAXNAMELEN + 1, KM_SLEEP);
                dev_info_t *pdip = ddi_get_parent(dip);

                (void) ddi_deviname(dip, devnm);

                USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_destroy_child_devi:\n\t"
                    "offlining dip 0x%p usba_device=0x%p (%s)", (void *)dip,
                    (void *)usba_device, devnm);

                (void) devfs_clean(pdip, NULL, DV_CLEAN_FORCE);
                rval =  ndi_devi_unconfig_one(pdip, devnm + 1, NULL,
                    flag | NDI_UNCONFIG | NDI_DEVI_OFFLINE);
                if (rval != NDI_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            " ndi_devi_unconfig_one %s%d failed (%d)",
                            ddi_driver_name(dip), ddi_get_instance(dip),
                            rval);
                }
                kmem_free(devnm, MAXNAMELEN + 1);
        }

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_destroy_child_devi: rval=%d", rval);

        return (rval == NDI_SUCCESS ? USB_SUCCESS : USB_FAILURE);
}


/*
 * list management
 */
void
usba_init_list(usba_list_entry_t *element, usb_opaque_t private,
    ddi_iblock_cookie_t iblock_cookie)
{
        mutex_init(&element->list_mutex, NULL, MUTEX_DRIVER,
            iblock_cookie);
        mutex_enter(&element->list_mutex);
        element->private = private;
        mutex_exit(&element->list_mutex);
}


void
usba_destroy_list(usba_list_entry_t *head)
{
        mutex_enter(&head->list_mutex);
        ASSERT(head->next == NULL);
        ASSERT(head->prev == NULL);
        mutex_exit(&head->list_mutex);

        mutex_destroy(&head->list_mutex);
}


void
usba_add_to_list(usba_list_entry_t *head, usba_list_entry_t *element)
{
        usba_list_entry_t *next;
        int             remaining;

        mutex_enter(&head->list_mutex);
        mutex_enter(&element->list_mutex);

        remaining = head->count;

        /* check if it is not in another list */
        ASSERT(element->next == NULL);
        ASSERT(element->prev == NULL);

#ifdef DEBUG
        /*
         * only verify the list when not in interrupt context, we
         * have to trust the HCD
         */
        if (!servicing_interrupt()) {

                /* check if not already in this list */
                for (next = head->next; (next != NULL);
                    next = next->next) {
                        if (next == element) {
                                USB_DPRINTF_L0(DPRINT_MASK_USBA,
                                    usba_log_handle,
                                    "Attempt to corrupt USB list at 0x%p",
                                    (void *)head);
                                ASSERT(next == element);

                                goto done;
                        }
                        remaining--;

                        /*
                         * Detect incorrect circ links or found
                         * unexpected elements.
                         */
                        if ((next->next && (remaining == 0)) ||
                            ((next->next == NULL) && remaining)) {
                                panic("Corrupted USB list at 0x%p",
                                    (void *)head);
                                /*NOTREACHED*/
                        }
                }
        }
#endif

        if (head->next == NULL) {
                head->prev = head->next = element;
        } else {
                /* add to tail */
                head->prev->next = element;
                element->prev = head->prev;
                head->prev = element;
        }

        head->count++;

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_add_to_list: head=0x%p element=0x%p count=%d",
            (void *)head, (void *)element, head->count);

#ifdef DEBUG
done:
#endif
        mutex_exit(&head->list_mutex);
        mutex_exit(&element->list_mutex);
}


int
usba_rm_from_list(usba_list_entry_t *head, usba_list_entry_t *element)
{
        usba_list_entry_t *e;
        int             found = 0;
        int             remaining;

        /* find the element in the list first */
        mutex_enter(&head->list_mutex);

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_rm_from_list: head=0x%p element=0x%p count=%d",
            (void *)head, (void *)element, head->count);

        remaining = head->count;
        e = head->next;

        while (e) {
                if (e == element) {
                        found++;
                        break;
                }
                e = e->next;

                remaining--;

                /* Detect incorrect circ links or found unexpected elements. */
                if ((e && (remaining == 0)) ||
                    ((e == NULL) && (remaining))) {
                        panic("Corrupted USB list at 0x%p", (void *)head);
                        /*NOTREACHED*/
                }
        }

        if (!found) {
                mutex_exit(&head->list_mutex);

                return (USB_FAILURE);
        }

        /* now remove the element */
        mutex_enter(&element->list_mutex);

        if (element->next) {
                element->next->prev = element->prev;
        }
        if (element->prev) {
                element->prev->next = element->next;
        }
        if (head->next == element) {
                head->next = element->next;
        }
        if (head->prev == element) {
                head->prev = element->prev;
        }

        element->prev = element->next = NULL;
        if (head->next == NULL) {
                ASSERT(head->prev == NULL);
        } else {
                ASSERT(head->next->prev == NULL);
        }
        if (head->prev == NULL) {
                ASSERT(head->next == NULL);
        } else {
                ASSERT(head->prev->next == NULL);
        }

        head->count--;

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_rm_from_list success: head=0x%p element=0x%p cnt=%d",
            (void *)head, (void *)element, head->count);

        mutex_exit(&element->list_mutex);
        mutex_exit(&head->list_mutex);

        return (USB_SUCCESS);
}


usba_list_entry_t *
usba_rm_first_from_list(usba_list_entry_t *head)
{
        usba_list_entry_t *element = NULL;

        if (head) {
                mutex_enter(&head->list_mutex);
                element = head->next;
                if (element) {
                        /* now remove the element */
                        mutex_enter(&element->list_mutex);
                        head->next = element->next;
                        if (head->next) {
                                head->next->prev = NULL;
                        }
                        if (head->prev == element) {
                                head->prev = element->next;
                        }
                        element->prev = element->next = NULL;
                        mutex_exit(&element->list_mutex);
                        head->count--;
                }
                if (head->next == NULL) {
                        ASSERT(head->prev == NULL);
                } else {
                        ASSERT(head->next->prev == NULL);
                }
                if (head->prev == NULL) {
                        ASSERT(head->next == NULL);
                } else {
                        ASSERT(head->prev->next == NULL);
                }
                USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_rm_first_from_list: head=0x%p el=0x%p cnt=%d",
                    (void *)head, (void *)element, head->count);

                mutex_exit(&head->list_mutex);
        }

        return (element);
}


usb_opaque_t
usba_rm_first_pvt_from_list(usba_list_entry_t *head)
{
        usba_list_entry_t *element = usba_rm_first_from_list(head);
        usb_opaque_t private = NULL;

        if (element) {
                mutex_enter(&element->list_mutex);
                private = element->private;
                mutex_exit(&element->list_mutex);
        }

        return (private);
}


/*
 * move list to new list and zero original list
 */
void
usba_move_list(usba_list_entry_t *head, usba_list_entry_t *new,
    ddi_iblock_cookie_t iblock_cookie)
{
        usba_init_list(new, NULL, iblock_cookie);
        mutex_enter(&head->list_mutex);
        mutex_enter(&new->list_mutex);

        new->next = head->next;
        new->prev = head->prev;
        new->count = head->count;
        new->private = head->private;

        head->next = NULL;
        head->prev = NULL;
        head->count = 0;
        head->private = NULL;
        mutex_exit(&head->list_mutex);
        mutex_exit(&new->list_mutex);
}


int
usba_check_in_list(usba_list_entry_t *head, usba_list_entry_t *element)
{
        int             rval = USB_FAILURE;
        int             remaining;
        usba_list_entry_t *next;

        mutex_enter(&head->list_mutex);
        remaining = head->count;

        mutex_enter(&element->list_mutex);
        for (next = head->next; next != NULL; next = next->next) {
                if (next == element) {
                        rval = USB_SUCCESS;
                        break;
                }
                remaining--;

                /* Detect incorrect circ links or found unexpected elements. */
                if ((next->next && (remaining == 0)) ||
                    ((next->next == NULL) && remaining)) {
                        panic("Corrupted USB list at 0x%p", (void *)head);
                        /*NOTREACHED*/
                }
        }
        mutex_exit(&element->list_mutex);
        mutex_exit(&head->list_mutex);

        return (rval);
}


int
usba_list_entry_leaks(usba_list_entry_t *head, char *what)
{
        int             count = 0;
        int             remaining;
        usba_list_entry_t *next;

        mutex_enter(&head->list_mutex);
        remaining = head->count;
        for (next = head->next; next != NULL; next = next->next) {
                USB_DPRINTF_L2(DPRINT_MASK_HCDI, usba_log_handle,
                    "leaking %s 0x%p", what, (void *)next->private);
                count++;

                remaining--;

                /* Detect incorrect circ links or found unexpected elements. */
                if ((next->next && (remaining == 0)) ||
                    ((next->next == NULL) && remaining)) {
                        panic("Corrupted USB list at 0x%p", (void *)head);
                        /*NOTREACHED*/
                }
        }
        ASSERT(count == head->count);
        mutex_exit(&head->list_mutex);

        if (count) {
                USB_DPRINTF_L2(DPRINT_MASK_HCDI, usba_log_handle,
                    "usba_list_entry_count: leaking %d", count);
        }

        return (count);
}


int
usba_list_entry_count(usba_list_entry_t *head)
{
        int count;

        mutex_enter(&head->list_mutex);
        count = head->count;
        mutex_exit(&head->list_mutex);

        return (count);
}

/* add a new root hub to the usba_root_hubs list */

void
usba_add_root_hub(dev_info_t *dip)
{
        usba_root_hub_ent_t *hub;

        hub = (usba_root_hub_ent_t *)
            kmem_zalloc(sizeof (usba_root_hub_ent_t), KM_SLEEP);

        mutex_enter(&usba_hub_mutex);
        hub->dip = dip;
        hub->next = usba_root_hubs;
        usba_root_hubs = hub;
        mutex_exit(&usba_hub_mutex);
}

/* remove a root hub from the usba_root_hubs list */

void
usba_rem_root_hub(dev_info_t *dip)
{
        usba_root_hub_ent_t **hubp, *hub;

        mutex_enter(&usba_hub_mutex);
        hubp = &usba_root_hubs;
        while (*hubp) {
                if ((*hubp)->dip == dip) {
                        hub = *hubp;
                        *hubp = hub->next;
                        kmem_free(hub, sizeof (struct usba_root_hub_ent));
                        mutex_exit(&usba_hub_mutex);

                        return;
                }
                hubp = &(*hubp)->next;
        }
        mutex_exit(&usba_hub_mutex);
}

/*
 * check whether this dip is the root hub. Any root hub known by
 * usba is recorded in the linked list pointed to by usba_root_hubs
 */
int
usba_is_root_hub(dev_info_t *dip)
{
        usba_root_hub_ent_t *hub;

        mutex_enter(&usba_hub_mutex);
        hub = usba_root_hubs;
        while (hub) {
                if (hub->dip == dip) {
                        mutex_exit(&usba_hub_mutex);

                        return (1);
                }
                hub = hub->next;
        }
        mutex_exit(&usba_hub_mutex);

        return (0);
}

/*
 * get and store usba_device pointer in the devi
 */
usba_device_t *
usba_get_usba_device(dev_info_t *dip)
{
        /*
         * we cannot use parent_data in the usb node because its
         * bus parent (eg. PCI nexus driver) uses this data
         *
         * we cannot use driver data in the other usb nodes since
         * usb drivers may need to use this
         */
        if (usba_is_root_hub(dip)) {
                usba_hcdi_t *hcdi = usba_hcdi_get_hcdi(dip);

                return (hcdi->hcdi_usba_device);
        } else {

                return (ddi_get_parent_data(dip));
        }
}


/*
 * Retrieve the usba_device pointer from the dev without checking for
 * the root hub first.  This function is only used in polled mode.
 */
usba_device_t *
usba_polled_get_usba_device(dev_info_t *dip)
{
        /*
         * Don't call usba_is_root_hub() to find out if this is
         * the root hub  usba_is_root_hub() calls into the DDI
         * where there are locking issues. The dip sent in during
         * polled mode will never be the root hub, so just get
         * the usba_device pointer from the dip.
         */
        return (ddi_get_parent_data(dip));
}


void
usba_set_usba_device(dev_info_t *dip, usba_device_t *usba_device)
{
        if (usba_is_root_hub(dip)) {
                usba_hcdi_t *hcdi = usba_hcdi_get_hcdi(dip);
                /* no locking is needed here */
                _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(hcdi->hcdi_usba_device))
                hcdi->hcdi_usba_device = usba_device;
                _NOTE(NOW_VISIBLE_TO_OTHER_THREADS(hcdi->hcdi_usba_device))
        } else {
                ddi_set_parent_data(dip, usba_device);
        }
}


/*
 * usba_set_node_name() according to class, subclass, and protocol
 * following the 1275 USB binding tables.
 */

/* device node table, refer to section 3.2.2.1 of 1275 binding */
static node_name_entry_t device_node_name_table[] = {
{ USB_CLASS_COMM,       DONTCARE,       DONTCARE,       "communications" },
{ USB_CLASS_HUB,        DONTCARE,       DONTCARE,       "hub" },
{ USB_CLASS_DIAG,       DONTCARE,       DONTCARE,       "diagnostics" },
{ USB_CLASS_MISC,       DONTCARE,       DONTCARE,       "miscellaneous" },
{ DONTCARE,             DONTCARE,       DONTCARE,       "device" }
};

/* interface-association node table */
static node_name_entry_t ia_node_name_table[] = {
{ USB_CLASS_AUDIO,      DONTCARE,       DONTCARE, "audio" },
{ USB_CLASS_VIDEO,      DONTCARE,       DONTCARE, "video" },
{ USB_CLASS_WIRELESS,   USB_SUBCLS_WUSB_2, USB_PROTO_WUSB_DWA,
                                                "device-wire-adaptor" },
{ USB_CLASS_WIRELESS,   DONTCARE,       DONTCARE, "wireless-controller" },
{ DONTCARE,             DONTCARE,       DONTCARE, "interface-association" }
};

/* interface node table, refer to section 3.3.2.1 */
static node_name_entry_t if_node_name_table[] = {
{ USB_CLASS_AUDIO, USB_SUBCLS_AUD_CONTROL, DONTCARE,    "sound-control" },
{ USB_CLASS_AUDIO, USB_SUBCLS_AUD_STREAMING, DONTCARE, "sound" },
{ USB_CLASS_AUDIO, USB_SUBCLS_AUD_MIDI_STREAMING, DONTCARE, "midi" },
{ USB_CLASS_AUDIO, DONTCARE,            DONTCARE,       "sound" },

{ USB_CLASS_COMM, USB_SUBCLS_CDCC_DIRECT_LINE,  DONTCARE, "line" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ABSTRCT_CTRL, DONTCARE, "modem" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_PHONE_CTRL, DONTCARE, "telephone" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_MULTCNL_ISDN, DONTCARE, "isdn" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ISDN,         DONTCARE, "isdn" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ETHERNET,     DONTCARE, "ethernet" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ATM_NETWORK, DONTCARE, "atm-network" },
{ USB_CLASS_COMM, DONTCARE,             DONTCARE,       "communications" },

{ USB_CLASS_HID, USB_SUBCLS_HID_1, USB_PROTO_HID_KEYBOARD,      "keyboard" },
{ USB_CLASS_HID, USB_SUBCLS_HID_1, USB_PROTO_HID_MOUSE, "mouse" },
{ USB_CLASS_HID,        DONTCARE,       DONTCARE,       "input" },

{ USB_CLASS_HUB,        DONTCARE,       DONTCARE,       "hub" },

{ USB_CLASS_PHYSICAL,   DONTCARE,       DONTCARE,       "physical" },

{ USB_CLASS_IMAGE,      DONTCARE,       DONTCARE,       "image" },

{ USB_CLASS_PRINTER,    DONTCARE,       DONTCARE,       "printer" },

{ USB_CLASS_MASS_STORAGE, DONTCARE,     DONTCARE,       "storage" },

{ USB_CLASS_CDC_DATA,   DONTCARE,       DONTCARE,       "data" },

{ USB_CLASS_SECURITY,   DONTCARE,       DONTCARE,       "security" },

{ USB_CLASS_VIDEO, USB_SUBCLS_VIDEO_CONTROL, DONTCARE,  "video-control" },
{ USB_CLASS_VIDEO, USB_SUBCLS_VIDEO_STREAM,  DONTCARE,  "video-stream" },
{ USB_CLASS_VIDEO,      DONTCARE,       DONTCARE,       "video" },

{ USB_CLASS_APP,        USB_SUBCLS_APP_FIRMWARE, DONTCARE, "firmware" },
{ USB_CLASS_APP,        USB_SUBCLS_APP_IRDA,    DONTCARE, "IrDa" },
{ USB_CLASS_APP,        USB_SUBCLS_APP_TEST,    DONTCARE, "test" },

{ USB_CLASS_MISC,       USB_SUBCLS_CBAF, USB_PROTO_CBAF,  "wusb_ca"},
{ USB_CLASS_WIRELESS, USB_SUBCLS_WUSB_1, USB_PROTO_WUSB_RC, "hwa-radio" },
{ USB_CLASS_WIRELESS, USB_SUBCLS_WUSB_2, USB_PROTO_WUSB_HWA, "hwa-host" },
{ USB_CLASS_WIRELESS, USB_SUBCLS_WUSB_2, USB_PROTO_WUSB_DWA, "dwa-control" },
{ USB_CLASS_WIRELESS, USB_SUBCLS_WUSB_2, USB_PROTO_WUSB_DWA_ISO, "dwa-isoc" },
{ USB_CLASS_WIRELESS, DONTCARE, DONTCARE, "wireless" },

{ DONTCARE,             DONTCARE,       DONTCARE,       "interface" },

};

/* combined node table, refer to section 3.4.2.1 */
static node_name_entry_t combined_node_name_table[] = {
{ USB_CLASS_AUDIO, USB_SUBCLS_AUD_CONTROL, DONTCARE,    "sound-control" },
{ USB_CLASS_AUDIO, USB_SUBCLS_AUD_STREAMING, DONTCARE, "sound" },
{ USB_CLASS_AUDIO, USB_SUBCLS_AUD_MIDI_STREAMING, DONTCARE, "midi" },
{ USB_CLASS_AUDIO, DONTCARE,            DONTCARE,       "sound" },

{ USB_CLASS_COMM, USB_SUBCLS_CDCC_DIRECT_LINE,  DONTCARE, "line" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ABSTRCT_CTRL, DONTCARE, "modem" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_PHONE_CTRL, DONTCARE, "telephone" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_MULTCNL_ISDN, DONTCARE, "isdn" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ISDN,         DONTCARE, "isdn" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ETHERNET,     DONTCARE, "ethernet" },
{ USB_CLASS_COMM, USB_SUBCLS_CDCC_ATM_NETWORK, DONTCARE, "atm-network" },
{ USB_CLASS_COMM, DONTCARE,             DONTCARE,       "communications" },

{ USB_CLASS_HID, USB_SUBCLS_HID_1, USB_PROTO_HID_KEYBOARD, "keyboard" },
{ USB_CLASS_HID, USB_SUBCLS_HID_1, USB_PROTO_HID_MOUSE, "mouse" },
{ USB_CLASS_HID,        DONTCARE,       DONTCARE,       "input" },

{ USB_CLASS_PHYSICAL,   DONTCARE,       DONTCARE,       "physical" },

{ USB_CLASS_IMAGE,      DONTCARE,       DONTCARE,       "image" },

{ USB_CLASS_PRINTER,    DONTCARE,       DONTCARE,       "printer" },

{ USB_CLASS_MASS_STORAGE, USB_SUBCLS_MS_RBC_T10,        DONTCARE, "storage" },
{ USB_CLASS_MASS_STORAGE, USB_SUBCLS_MS_SFF8020I,       DONTCARE, "cdrom" },
{ USB_CLASS_MASS_STORAGE, USB_SUBCLS_MS_QIC_157,        DONTCARE, "tape" },
{ USB_CLASS_MASS_STORAGE, USB_SUBCLS_MS_UFI,            DONTCARE, "floppy" },
{ USB_CLASS_MASS_STORAGE, USB_SUBCLS_MS_SFF8070I,       DONTCARE, "storage" },
{ USB_CLASS_MASS_STORAGE, USB_SUBCLS_MS_SCSI,           DONTCARE, "storage" },
{ USB_CLASS_MASS_STORAGE, DONTCARE,     DONTCARE,       "storage" },

{ USB_CLASS_CDC_DATA,   DONTCARE,       DONTCARE,       "data" },

{ USB_CLASS_SECURITY,   DONTCARE,       DONTCARE,       "security" },

{ USB_CLASS_VIDEO, USB_SUBCLS_VIDEO_CONTROL, DONTCARE,  "video-control" },
{ USB_CLASS_VIDEO, USB_SUBCLS_VIDEO_STREAM,  DONTCARE,  "video-stream" },
{ USB_CLASS_VIDEO,      DONTCARE,       DONTCARE,       "video" },

{ USB_CLASS_APP,        USB_SUBCLS_APP_FIRMWARE, DONTCARE, "firmware" },
{ USB_CLASS_APP,        USB_SUBCLS_APP_IRDA,    DONTCARE, "IrDa" },
{ USB_CLASS_APP,        USB_SUBCLS_APP_TEST,    DONTCARE, "test" },

{ USB_CLASS_COMM,       DONTCARE,       DONTCARE,       "communications" },
{ USB_CLASS_HUB,        DONTCARE,       DONTCARE,       "hub" },
{ USB_CLASS_DIAG,       DONTCARE,       DONTCARE,       "diagnostics" },
{ USB_CLASS_MISC,       DONTCARE,       DONTCARE,       "miscellaneous" },
{ DONTCARE,             DONTCARE,       DONTCARE,       "device" }
};

static size_t device_node_name_table_size =
        sizeof (device_node_name_table)/sizeof (struct node_name_entry);
static size_t ia_node_name_table_size =
        sizeof (ia_node_name_table)/sizeof (struct node_name_entry);
static size_t if_node_name_table_size =
        sizeof (if_node_name_table)/sizeof (struct node_name_entry);
static size_t combined_node_name_table_size =
        sizeof (combined_node_name_table)/sizeof (struct node_name_entry);


static void
usba_set_node_name(dev_info_t *dip, uint8_t class, uint8_t subclass,
    uint8_t protocol, uint_t flag)
{
        int i;
        size_t size;
        node_name_entry_t *node_name_table;

        switch (flag) {
        /* interface share node names with interface-association */
        case FLAG_INTERFACE_ASSOCIATION_NODE:
                node_name_table = ia_node_name_table;
                size = ia_node_name_table_size;
                break;
        case FLAG_INTERFACE_NODE:
                node_name_table = if_node_name_table;
                size = if_node_name_table_size;
                break;
        case FLAG_DEVICE_NODE:
                node_name_table = device_node_name_table;
                size = device_node_name_table_size;
                break;
        case FLAG_COMBINED_NODE:
                node_name_table = combined_node_name_table;
                size = combined_node_name_table_size;
                break;
        default:

                return;
        }

        for (i = 0; i < size; i++) {
                int16_t c = node_name_table[i].class;
                int16_t s = node_name_table[i].subclass;
                int16_t p = node_name_table[i].protocol;

                if (((c == DONTCARE) || (c == class)) &&
                    ((s == DONTCARE) || (s == subclass)) &&
                    ((p == DONTCARE) || (p == protocol))) {
                        char *name = node_name_table[i].name;

                        (void) ndi_devi_set_nodename(dip, name, 0);
                        break;
                }
        }
}


#ifdef DEBUG
/*
 * walk the children of the parent of this devi and compare the
 * name and  reg property of each child. If there is a match
 * return this node
 */
static dev_info_t *
usba_find_existing_node(dev_info_t *odip)
{
        dev_info_t *ndip, *child, *pdip;
        int     *odata, *ndata;
        uint_t  n_odata, n_ndata;

        pdip = ddi_get_parent(odip);
        if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY,
            odip, DDI_PROP_DONTPASS, "reg",
            &odata, &n_odata) != DDI_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_HCDI, usba_log_handle,
                    "usba_find_existing_node: "
                    "%s: DDI_NOT_WELL_FORMED", ddi_driver_name(odip));

                return (NULL);
        }

        ndi_devi_enter(pdip);
        ndip = (dev_info_t *)(DEVI(pdip)->devi_child);
        while ((child = ndip) != NULL) {

                ndip = (dev_info_t *)(DEVI(child)->devi_sibling);

                if (child == odip) {
                        continue;
                }

                if (strcmp(DEVI(child)->devi_node_name,
                    DEVI(odip)->devi_node_name)) {
                        continue;
                }

                if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY,
                    child, DDI_PROP_DONTPASS, "reg",
                    &ndata, &n_ndata) != DDI_SUCCESS) {

                        USB_DPRINTF_L2(DPRINT_MASK_HCDI, usba_log_handle,
                            "usba_find_existing_node: "
                            "%s DDI_NOT_WELL_FORMED", ddi_driver_name(child));

                } else if (n_ndata && n_odata && (bcmp(odata, ndata,
                    max(n_odata, n_ndata) * sizeof (int)) == 0)) {

                        USB_DPRINTF_L3(DPRINT_MASK_HCDI, usba_log_handle,
                            "usba_find_existing_node: found %s%d (%p)",
                            ddi_driver_name(child),
                            ddi_get_instance(child), (void *)child);

                        USB_DPRINTF_L3(DPRINT_MASK_HCDI, usba_log_handle,
                            "usba_find_existing_node: "
                            "reg: %x %x %x - %x %x %x",
                            n_odata, odata[0], odata[1],
                            n_ndata, ndata[0], ndata[1]);

                        ddi_prop_free(ndata);
                        break;

                } else {
                        ddi_prop_free(ndata);
                }
        }

        ndi_devi_exit(pdip);

        ddi_prop_free(odata);

        return (child);
}
#endif

/* change all unprintable characters to spaces */
static void
usba_filter_string(char *instr, char *outstr)
{
        while (*instr) {
                if ((*instr >= ' ') && (*instr <= '~')) {
                        *outstr = *instr;
                } else {
                        *outstr = ' ';
                }
                outstr++;
                instr++;
        }
        *outstr = '\0';
}


/*
 * lookup ugen binding specified in property in
 * hcd.conf files
 */
int
usba_get_ugen_binding(dev_info_t *dip)
{
        usba_device_t   *usba_device = usba_get_usba_device(dip);
        usba_hcdi_t     *hcdi =
            usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip);

        return (hcdi->hcdi_ugen_default_binding);
}


/*
 * driver binding support at device level
 */
dev_info_t *
usba_ready_device_node(dev_info_t *child_dip)
{
        int             rval, i;
        int             n = 0;
        usba_device_t   *usba_device = usba_get_usba_device(child_dip);
        usb_dev_descr_t *usb_dev_descr;
        uint_t          n_cfgs; /* number of configs */
        uint_t          n_ifs;  /* number of interfaces */
        uint_t          port, bus_num;
        size_t          usb_config_length;
        uchar_t         *usb_config;
        int             reg[1];
        usb_addr_t      address = usb_get_addr(child_dip);
        usb_if_descr_t  if_descr;
        size_t          size;
        int             combined_node = 0;
        int             is_hub;
        char            *devprop_str;
        char            *force_bind = NULL;
        char            *usba_name_buf = NULL;
        char            *usba_name[USBA_MAX_COMPAT_NAMES];

        usb_config = usb_get_raw_cfg_data(child_dip, &usb_config_length);

        mutex_enter(&usba_device->usb_mutex);
        mutex_enter(&usba_mutex);

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_ready_device_node: child=0x%p", (void *)child_dip);

        port = usba_device->usb_port;
        usb_dev_descr = usba_device->usb_dev_descr;
        n_cfgs = usba_device->usb_n_cfgs;
        n_ifs = usba_device->usb_n_ifs;
        bus_num = usba_device->usb_addr;

        if (address != ROOT_HUB_ADDR) {
                size = usb_parse_if_descr(
                    usb_config,
                    usb_config_length,
                    0,          /* interface index */
                    0,          /* alt interface index */
                    &if_descr,
                    USB_IF_DESCR_SIZE);

                if (size != USB_IF_DESCR_SIZE) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "parsing interface: "
                            "size (%lu) != USB_IF_DESCR_SIZE (%d)",
                            size, USB_IF_DESCR_SIZE);

                        mutex_exit(&usba_mutex);
                        mutex_exit(&usba_device->usb_mutex);

                        return (child_dip);
                }
        } else {
                /* fake an interface descriptor for the root hub */
                bzero(&if_descr, sizeof (if_descr));

                if_descr.bInterfaceClass = USB_CLASS_HUB;
        }

        reg[0] = port;

        mutex_exit(&usba_mutex);
        mutex_exit(&usba_device->usb_mutex);

        rval = ndi_prop_update_int_array(
            DDI_DEV_T_NONE, child_dip, "reg", reg, 1);

        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: property update failed");

                return (child_dip);
        }

        combined_node = ((n_cfgs == 1) && (n_ifs == 1) &&
            ((usb_dev_descr->bDeviceClass == USB_CLASS_HUB) ||
            (usb_dev_descr->bDeviceClass == 0)));

        is_hub = (if_descr.bInterfaceClass == USB_CLASS_HUB) ||
            (usb_dev_descr->bDeviceClass == USB_CLASS_HUB);

        /* set node name */
        if (combined_node) {
                usba_set_node_name(child_dip,
                    if_descr.bInterfaceClass,
                    if_descr.bInterfaceSubClass,
                    if_descr.bInterfaceProtocol,
                    FLAG_COMBINED_NODE);
        } else {
                usba_set_node_name(child_dip,
                    usb_dev_descr->bDeviceClass,
                    usb_dev_descr->bDeviceSubClass,
                    usb_dev_descr->bDeviceProtocol,
                    FLAG_DEVICE_NODE);
        }

        /*
         * check force binding rules
         */
        if ((address != ROOT_HUB_ADDR) && usba_ddivs_usbc &&
            (address != usba_ddivs_usbc_xaddress) &&
            (!(usba_ddivs_usbc_xhubs && is_hub))) {
                force_bind = "ddivs_usbc";
                (void) ndi_devi_set_nodename(child_dip, "ddivs_usbc", 0);

        } else if (usba_device->usb_preferred_driver) {
                force_bind = usba_device->usb_preferred_driver;

        } else if ((address != ROOT_HUB_ADDR) &&
            ((usba_ugen_force_binding == USBA_UGEN_DEVICE_BINDING) ||
            ((usba_ugen_force_binding == USBA_UGEN_INTERFACE_BINDING) &&
            combined_node)) && (!is_hub)) {
                force_bind = "ugen";
        }

#ifdef DEBUG
        /*
         * check whether there is another dip with this name and address
         * If the dip contains usba_device, it is held by the previous
         * round of configuration.
         */
        ASSERT(usba_find_existing_node(child_dip) == NULL);
#endif

        usba_name_buf = kmem_zalloc(USBA_MAX_COMPAT_NAMES *
            USBA_MAX_COMPAT_NAME_LEN, KM_SLEEP);

        for (i = 0; i < USBA_MAX_COMPAT_NAMES; i++) {
                usba_name[i] = usba_name_buf + (i * USBA_MAX_COMPAT_NAME_LEN);
        }

        if (force_bind) {
                (void) ndi_devi_set_nodename(child_dip, force_bind, 0);
                (void) strncpy(usba_name[n++], force_bind,
                    USBA_MAX_COMPAT_NAME_LEN);
        }

        /*
         * If the callback function of specified driver is registered,
         * it will be called here to check whether to take over the device.
         */
        if (usb_cap.usba_dev_driver_cb != NULL) {
                char            *dev_drv = NULL;
                usb_dev_str_t   dev_str;
                char            *pathname = kmem_alloc(MAXPATHLEN, KM_SLEEP);

                dev_str.usb_mfg = usba_device->usb_mfg_str;
                dev_str.usb_product = usba_device->usb_product_str;
                dev_str.usb_serialno = usba_device->usb_serialno_str;

                (void) ddi_pathname(child_dip, pathname);

                if ((usb_cap.usba_dev_driver_cb(usb_dev_descr, &dev_str,
                    pathname, bus_num, port, &dev_drv, NULL) == USB_SUCCESS) &&
                    (dev_drv != NULL)) {
                        USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: dev_driver=%s, port =%d,"
                            "bus =%d, path=%s\n\t",
                            dev_drv, port, bus_num, pathname);

                        (void) strncpy(usba_name[n++], dev_drv,
                            USBA_MAX_COMPAT_NAME_LEN);
                }
                kmem_free(pathname, MAXPATHLEN);
        }

        /* create compatible names */
        if (combined_node) {

                /* 1. usbVID,PID.REV */
                (void) sprintf(usba_name[n++],
                    "usb%x,%x.%x",
                    usb_dev_descr->idVendor,
                    usb_dev_descr->idProduct,
                    usb_dev_descr->bcdDevice);

                /* 2. usbVID,PID */
                (void) sprintf(usba_name[n++],
                    "usb%x,%x",
                    usb_dev_descr->idVendor,
                    usb_dev_descr->idProduct);

                if (usb_dev_descr->bDeviceClass != 0) {
                        /* 3. usbVID,classDC.DSC.DPROTO */
                        (void) sprintf(usba_name[n++],
                            "usb%x,class%x.%x.%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass,
                            usb_dev_descr->bDeviceProtocol);

                        /* 4. usbVID,classDC.DSC */
                        (void) sprintf(usba_name[n++],
                            "usb%x,class%x.%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass);

                        /* 5. usbVID,classDC */
                        (void) sprintf(usba_name[n++],
                            "usb%x,class%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->bDeviceClass);

                        /* 6. usb,classDC.DSC.DPROTO */
                        (void) sprintf(usba_name[n++],
                            "usb,class%x.%x.%x",
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass,
                            usb_dev_descr->bDeviceProtocol);

                        /* 7. usb,classDC.DSC */
                        (void) sprintf(usba_name[n++],
                            "usb,class%x.%x",
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass);

                        /* 8. usb,classDC */
                        (void) sprintf(usba_name[n++],
                            "usb,class%x",
                            usb_dev_descr->bDeviceClass);
                }

                if (if_descr.bInterfaceClass != 0) {
                        /* 9. usbifVID,classIC.ISC.IPROTO */
                        (void) sprintf(usba_name[n++],
                            "usbif%x,class%x.%x.%x",
                            usb_dev_descr->idVendor,
                            if_descr.bInterfaceClass,
                            if_descr.bInterfaceSubClass,
                            if_descr.bInterfaceProtocol);

                        /* 10. usbifVID,classIC.ISC */
                        (void) sprintf(usba_name[n++],
                            "usbif%x,class%x.%x",
                            usb_dev_descr->idVendor,
                            if_descr.bInterfaceClass,
                            if_descr.bInterfaceSubClass);

                        /* 11. usbifVID,classIC */
                        (void) sprintf(usba_name[n++],
                            "usbif%x,class%x",
                            usb_dev_descr->idVendor,
                            if_descr.bInterfaceClass);

                        /* 12. usbif,classIC.ISC.IPROTO */
                        (void) sprintf(usba_name[n++],
                            "usbif,class%x.%x.%x",
                            if_descr.bInterfaceClass,
                            if_descr.bInterfaceSubClass,
                            if_descr.bInterfaceProtocol);

                        /* 13. usbif,classIC.ISC */
                        (void) sprintf(usba_name[n++],
                            "usbif,class%x.%x",
                            if_descr.bInterfaceClass,
                            if_descr.bInterfaceSubClass);

                        /* 14. usbif,classIC */
                        (void) sprintf(usba_name[n++],
                            "usbif,class%x",
                            if_descr.bInterfaceClass);
                }

                /* 15. ugen or usb_mid */
                if (usba_get_ugen_binding(child_dip) ==
                    USBA_UGEN_DEVICE_BINDING) {
                        (void) sprintf(usba_name[n++], "ugen");
                } else {
                        (void) sprintf(usba_name[n++], "usb,device");
                }

        } else {
                if (n_cfgs > 1) {
                        /* 1. usbVID,PID.REV.configCN */
                        (void) sprintf(usba_name[n++],
                            "usb%x,%x.%x.config%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->idProduct,
                            usb_dev_descr->bcdDevice,
                            usba_device->usb_cfg_value);
                }

                /* 2. usbVID,PID.REV */
                (void) sprintf(usba_name[n++],
                    "usb%x,%x.%x",
                    usb_dev_descr->idVendor,
                    usb_dev_descr->idProduct,
                    usb_dev_descr->bcdDevice);

                /* 3. usbVID,PID.configCN */
                if (n_cfgs > 1) {
                        (void) sprintf(usba_name[n++],
                            "usb%x,%x.%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->idProduct,
                            usba_device->usb_cfg_value);
                }

                /* 4. usbVID,PID */
                (void) sprintf(usba_name[n++],
                    "usb%x,%x",
                    usb_dev_descr->idVendor,
                    usb_dev_descr->idProduct);

                if (usb_dev_descr->bDeviceClass != 0) {
                        /* 5. usbVID,classDC.DSC.DPROTO */
                        (void) sprintf(usba_name[n++],
                            "usb%x,class%x.%x.%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass,
                            usb_dev_descr->bDeviceProtocol);

                        /* 6. usbVID,classDC.DSC */
                        (void) sprintf(usba_name[n++],
                            "usb%x.class%x.%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass);

                        /* 7. usbVID,classDC */
                        (void) sprintf(usba_name[n++],
                            "usb%x.class%x",
                            usb_dev_descr->idVendor,
                            usb_dev_descr->bDeviceClass);

                        /* 8. usb,classDC.DSC.DPROTO */
                        (void) sprintf(usba_name[n++],
                            "usb,class%x.%x.%x",
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass,
                            usb_dev_descr->bDeviceProtocol);

                        /* 9. usb,classDC.DSC */
                        (void) sprintf(usba_name[n++],
                            "usb,class%x.%x",
                            usb_dev_descr->bDeviceClass,
                            usb_dev_descr->bDeviceSubClass);

                        /* 10. usb,classDC */
                        (void) sprintf(usba_name[n++],
                            "usb,class%x",
                            usb_dev_descr->bDeviceClass);
                }

                if (usba_get_ugen_binding(child_dip) ==
                    USBA_UGEN_DEVICE_BINDING) {
                        /* 11. ugen */
                        (void) sprintf(usba_name[n++], "ugen");
                } else {
                        /* 11. usb,device */
                        (void) sprintf(usba_name[n++], "usb,device");
                }
        }

        for (i = 0; i < n; i += 2) {
                USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                    "compatible name:\t%s\t%s", usba_name[i],
                    (((i+1) < n)? usba_name[i+1] : ""));
        }

        rval = ndi_prop_update_string_array(DDI_DEV_T_NONE, child_dip,
            "compatible", (char **)usba_name, n);

        kmem_free(usba_name_buf, USBA_MAX_COMPAT_NAMES *
            USBA_MAX_COMPAT_NAME_LEN);

        if (rval != DDI_PROP_SUCCESS) {

                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: property update failed");

                return (child_dip);
        }

        /* update the address property */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "assigned-address", usba_device->usb_addr);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: address update failed");
        }

        /* update the usb device properties (PSARC/2000/454) */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "usb-vendor-id", usb_dev_descr->idVendor);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: usb-vendor-id update failed");
        }

        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "usb-product-id", usb_dev_descr->idProduct);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: usb-product-id update failed");
        }

        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "usb-revision-id", usb_dev_descr->bcdDevice);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: usb-revision-id update failed");
        }

        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "usb-num-configs", usb_dev_descr->bNumConfigurations);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: usb-num-configs update failed");
        }

        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "usb-release", usb_dev_descr->bcdUSB);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: usb-release update failed");
        }

        rval = ndi_prop_update_byte_array(DDI_DEV_T_NONE, child_dip,
            "usb-dev-descriptor", (uchar_t *)usb_dev_descr,
            sizeof (usb_dev_descr_t));
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: usb-descriptor update failed");
        }

        rval = ndi_prop_update_byte_array(DDI_DEV_T_NONE, child_dip,
            "usb-raw-cfg-descriptors", usb_config, usb_config_length);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_device_node: usb-raw-cfg-descriptors update "
                    "failed");
        }

        devprop_str = kmem_zalloc(USB_MAXSTRINGLEN, KM_SLEEP);

        if (usba_device->usb_serialno_str) {
                usba_filter_string(usba_device->usb_serialno_str, devprop_str);
                rval = ndi_prop_update_string(DDI_DEV_T_NONE, child_dip,
                    "usb-serialno", devprop_str);
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "usb-serialno update failed");
                }
        }

        if (usba_device->usb_mfg_str) {
                usba_filter_string(usba_device->usb_mfg_str, devprop_str);
                rval = ndi_prop_update_string(DDI_DEV_T_NONE, child_dip,
                    "usb-vendor-name", devprop_str);
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "usb-vendor-name update failed");
                }
        }

        if (usba_device->usb_product_str) {
                usba_filter_string(usba_device->usb_product_str, devprop_str);
                rval = ndi_prop_update_string(DDI_DEV_T_NONE, child_dip,
                    "usb-product-name", devprop_str);
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "usb-product-name update failed");
                }
        }

        kmem_free(devprop_str, USB_MAXSTRINGLEN);

        if (!combined_node) {
                /* update the configuration property */
                rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
                    "configuration#", usba_device->usb_cfg_value);
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "config prop update failed");
                }
        }

        if (usba_device->usb_port_status == USBA_LOW_SPEED_DEV) {
                /* create boolean property */
                rval = ndi_prop_create_boolean(DDI_DEV_T_NONE, child_dip,
                    "low-speed");
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "low speed prop update failed");
                }
        }

        if (usba_device->usb_port_status == USBA_FULL_SPEED_DEV) {
                /* create boolean property */
                rval = ndi_prop_create_boolean(DDI_DEV_T_NONE, child_dip,
                    "full-speed");
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "full speed prop update failed");
                }
        }

        if (usba_device->usb_port_status == USBA_HIGH_SPEED_DEV) {
                /* create boolean property */
                rval = ndi_prop_create_boolean(DDI_DEV_T_NONE, child_dip,
                    "high-speed");
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "high speed prop update failed");
                }
        }

        if (usba_device->usb_port_status == USBA_SUPER_SPEED_DEV) {
                rval = ndi_prop_create_boolean(DDI_DEV_T_NONE, child_dip,
                    "super-speed");
                if (rval != DDI_PROP_SUCCESS) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                            "usba_ready_device_node: "
                            "super speed prop update failed");
                }
        }

        usba_add_binary_object_store_props(child_dip, usba_device);

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "%s%d at port %d: %s, dip=0x%p",
            ddi_node_name(ddi_get_parent(child_dip)),
            ddi_get_instance(ddi_get_parent(child_dip)),
            port, ddi_node_name(child_dip), (void *)child_dip);

        usba_set_usba_device(child_dip, usba_device);

        ASSERT(!mutex_owned(&(usba_get_usba_device(child_dip)->usb_mutex)));

        return (child_dip);
}


/*
 * driver binding at interface association level. the first arg is the parent
 * dip. if_count returns amount of interfaces which are associated within
 * this interface-association that starts from first_if.
 */
/*ARGSUSED*/
dev_info_t *
usba_ready_interface_association_node(
    dev_info_t  *dip,
    uint_t      first_if,
    uint_t      *if_count)
{
        dev_info_t              *child_dip = NULL;
        usba_device_t           *child_ud = usba_get_usba_device(dip);
        usb_dev_descr_t         *usb_dev_descr;
        size_t                  usb_cfg_length;
        uchar_t                 *usb_cfg;
        usb_ia_descr_t          ia_descr;
        int                     i, n, rval;
        int                     reg[2];
        size_t                  size;
        usb_port_status_t       port_status;
        char                    *force_bind = NULL;
        char                    *usba_name_buf = NULL;
        char                    *usba_name[USBA_MAX_COMPAT_NAMES];

        usb_cfg = usb_get_raw_cfg_data(dip, &usb_cfg_length);

        mutex_enter(&child_ud->usb_mutex);

        usb_dev_descr = child_ud->usb_dev_descr;

        /*
         * for each interface association, determine all compatible names
         */
        USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
            "usba_ready_ia_node: "
            "port %d, interface = %d, port_status = %x",
            child_ud->usb_port, first_if, child_ud->usb_port_status);

        /* Parse the interface descriptor */
        size = usb_parse_ia_descr(
            usb_cfg,
            usb_cfg_length,
            first_if,   /* interface index */
            &ia_descr,
            USB_IA_DESCR_SIZE);

        *if_count = 1;
        if (size != USB_IA_DESCR_SIZE) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "parsing ia: size (%lu) != USB_IA_DESCR_SIZE (%d)",
                    size, USB_IA_DESCR_SIZE);
                mutex_exit(&child_ud->usb_mutex);

                return (NULL);
        }

        port_status = child_ud->usb_port_status;

        /* create reg property */
        reg[0] = first_if;
        reg[1] = child_ud->usb_cfg_value;

        mutex_exit(&child_ud->usb_mutex);

        /* clone this dip */
        rval =  usba_create_child_devi(dip,
            "interface-association",
            NULL,               /* usba_hcdi ops */
            NULL,               /* root hub dip */
            port_status,        /* port status */
            child_ud,   /* share this usba_device */
            &child_dip);

        if (rval != USB_SUCCESS) {

                goto fail;
        }

        rval = ndi_prop_update_int_array(
            DDI_DEV_T_NONE, child_dip, "reg", reg, 2);

        if (rval != DDI_PROP_SUCCESS) {

                goto fail;
        }

        usba_set_node_name(child_dip, ia_descr.bFunctionClass,
            ia_descr.bFunctionSubClass, ia_descr.bFunctionProtocol,
            FLAG_INTERFACE_ASSOCIATION_NODE);

        /* check force binding */
        if (usba_ugen_force_binding ==
            USBA_UGEN_INTERFACE_ASSOCIATION_BINDING) {
                force_bind = "ugen";
        }

        /*
         * check whether there is another dip with this name and address
         */
        ASSERT(usba_find_existing_node(child_dip) == NULL);

        usba_name_buf = kmem_zalloc(USBA_MAX_COMPAT_NAMES *
            USBA_MAX_COMPAT_NAME_LEN, KM_SLEEP);

        for (i = 0; i < USBA_MAX_COMPAT_NAMES; i++) {
                usba_name[i] = usba_name_buf + (i * USBA_MAX_COMPAT_NAME_LEN);
        }

        n = 0;

        if (force_bind) {
                (void) ndi_devi_set_nodename(child_dip, force_bind, 0);
                (void) strncpy(usba_name[n++], force_bind,
                    USBA_MAX_COMPAT_NAME_LEN);
        }

        /* 1) usbiaVID,PID.REV.configCN.FN */
        (void) sprintf(usba_name[n++],
            "usbia%x,%x.%x.config%x.%x",
            usb_dev_descr->idVendor,
            usb_dev_descr->idProduct,
            usb_dev_descr->bcdDevice,
            child_ud->usb_cfg_value,
            first_if);

        /* 2) usbiaVID,PID.configCN.FN */
        (void) sprintf(usba_name[n++],
            "usbia%x,%x.config%x.%x",
            usb_dev_descr->idVendor,
            usb_dev_descr->idProduct,
            child_ud->usb_cfg_value,
            first_if);


        if (ia_descr.bFunctionClass) {
                /* 3) usbiaVID,classFC.FSC.FPROTO */
                (void) sprintf(usba_name[n++],
                    "usbia%x,class%x.%x.%x",
                    usb_dev_descr->idVendor,
                    ia_descr.bFunctionClass,
                    ia_descr.bFunctionSubClass,
                    ia_descr.bFunctionProtocol);

                /* 4) usbiaVID,classFC.FSC */
                (void) sprintf(usba_name[n++],
                    "usbia%x,class%x.%x",
                    usb_dev_descr->idVendor,
                    ia_descr.bFunctionClass,
                    ia_descr.bFunctionSubClass);

                /* 5) usbiaVID,classFC */
                (void) sprintf(usba_name[n++],
                    "usbia%x,class%x",
                    usb_dev_descr->idVendor,
                    ia_descr.bFunctionClass);

                /* 6) usbia,classFC.FSC.FPROTO */
                (void) sprintf(usba_name[n++],
                    "usbia,class%x.%x.%x",
                    ia_descr.bFunctionClass,
                    ia_descr.bFunctionSubClass,
                    ia_descr.bFunctionProtocol);

                /* 7) usbia,classFC.FSC */
                (void) sprintf(usba_name[n++],
                    "usbia,class%x.%x",
                    ia_descr.bFunctionClass,
                    ia_descr.bFunctionSubClass);

                /* 8) usbia,classFC */
                (void) sprintf(usba_name[n++],
                    "usbia,class%x",
                    ia_descr.bFunctionClass);
        }

        if (usba_get_ugen_binding(child_dip) ==
            USBA_UGEN_INTERFACE_ASSOCIATION_BINDING) {
                /* 9) ugen */
                (void) sprintf(usba_name[n++], "ugen");
        } else {

                (void) sprintf(usba_name[n++], "usb,ia");
        }

        for (i = 0; i < n; i += 2) {
                USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                    "compatible name:\t%s\t%s", usba_name[i],
                    (((i+1) < n)? usba_name[i+1] : ""));
        }

        /* create compatible property */
        rval = ndi_prop_update_string_array(DDI_DEV_T_NONE, child_dip,
            "compatible", (char **)usba_name, n);

        kmem_free(usba_name_buf, USBA_MAX_COMPAT_NAMES *
            USBA_MAX_COMPAT_NAME_LEN);

        if (rval != DDI_PROP_SUCCESS) {

                goto fail;
        }

        /* update the address property */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "assigned-address", child_ud->usb_addr);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_interface_node: address update failed");
        }

        /* create property with first interface number */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "interface", ia_descr.bFirstInterface);

        if (rval != DDI_PROP_SUCCESS) {

                goto fail;
        }

        /* create property with the count of interfaces in this ia */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "interface-count", ia_descr.bInterfaceCount);

        if (rval != DDI_PROP_SUCCESS) {

                goto fail;
        }

        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
            "%s%d port %d: %s, dip = 0x%p",
            ddi_node_name(ddi_get_parent(dip)),
            ddi_get_instance(ddi_get_parent(dip)),
            child_ud->usb_port, ddi_node_name(child_dip), (void *)child_dip);

        *if_count = ia_descr.bInterfaceCount;
        usba_set_usba_device(child_dip, child_ud);
        ASSERT(!mutex_owned(&(usba_get_usba_device(child_dip)->usb_mutex)));

        return (child_dip);

fail:
        (void) usba_destroy_child_devi(child_dip, NDI_DEVI_REMOVE);

        return (NULL);
}


/*
 * driver binding at interface level, the first arg will be the
 * the parent dip
 */
/*ARGSUSED*/
dev_info_t *
usba_ready_interface_node(dev_info_t *dip, uint_t intf)
{
        dev_info_t              *child_dip = NULL;
        usba_device_t           *child_ud = usba_get_usba_device(dip);
        usb_dev_descr_t         *usb_dev_descr;
        size_t                  usb_cfg_length;
        uchar_t                 *usb_cfg;
        usb_if_descr_t          if_descr;
        int                     i, n, rval;
        int                     reg[2];
        size_t                  size;
        usb_port_status_t       port_status;
        char                    *force_bind = NULL;
        char                    *usba_name_buf = NULL;
        char                    *usba_name[USBA_MAX_COMPAT_NAMES];

        usb_cfg = usb_get_raw_cfg_data(dip, &usb_cfg_length);

        mutex_enter(&child_ud->usb_mutex);

        usb_dev_descr = child_ud->usb_dev_descr;

        /*
         * for each interface, determine all compatible names
         */
        USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
            "usba_ready_interface_node: "
            "port %d, interface = %d port status = %x",
            child_ud->usb_port, intf, child_ud->usb_port_status);

        /* Parse the interface descriptor */
        size = usb_parse_if_descr(
            usb_cfg,
            usb_cfg_length,
            intf,               /* interface index */
            0,          /* alt interface index */
            &if_descr,
            USB_IF_DESCR_SIZE);

        if (size != USB_IF_DESCR_SIZE) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "parsing interface: size (%lu) != USB_IF_DESCR_SIZE (%d)",
                    size, USB_IF_DESCR_SIZE);
                mutex_exit(&child_ud->usb_mutex);

                return (NULL);
        }

        port_status = child_ud->usb_port_status;

        /* create reg property */
        reg[0] = intf;
        reg[1] = child_ud->usb_cfg_value;

        mutex_exit(&child_ud->usb_mutex);

        /* clone this dip */
        rval =  usba_create_child_devi(dip,
            "interface",
            NULL,               /* usba_hcdi ops */
            NULL,               /* root hub dip */
            port_status,        /* port status */
            child_ud,   /* share this usba_device */
            &child_dip);

        if (rval != USB_SUCCESS) {

                goto fail;
        }

        rval = ndi_prop_update_int_array(
            DDI_DEV_T_NONE, child_dip, "reg", reg, 2);

        if (rval != DDI_PROP_SUCCESS) {

                goto fail;
        }

        usba_set_node_name(child_dip, if_descr.bInterfaceClass,
            if_descr.bInterfaceSubClass, if_descr.bInterfaceProtocol,
            FLAG_INTERFACE_NODE);

        /* check force binding */
        if (usba_ugen_force_binding == USBA_UGEN_INTERFACE_BINDING) {
                force_bind = "ugen";
        }

        /*
         * check whether there is another dip with this name and address
         */
        ASSERT(usba_find_existing_node(child_dip) == NULL);

        usba_name_buf = kmem_zalloc(USBA_MAX_COMPAT_NAMES *
            USBA_MAX_COMPAT_NAME_LEN, KM_SLEEP);

        for (i = 0; i < USBA_MAX_COMPAT_NAMES; i++) {
                usba_name[i] = usba_name_buf + (i * USBA_MAX_COMPAT_NAME_LEN);
        }

        n = 0;

        if (force_bind) {
                (void) ndi_devi_set_nodename(child_dip, force_bind, 0);
                (void) strncpy(usba_name[n++], force_bind,
                    USBA_MAX_COMPAT_NAME_LEN);
        }

        /* 1) usbifVID,PID.REV.configCN.IN */
        (void) sprintf(usba_name[n++],
            "usbif%x,%x.%x.config%x.%x",
            usb_dev_descr->idVendor,
            usb_dev_descr->idProduct,
            usb_dev_descr->bcdDevice,
            child_ud->usb_cfg_value,
            intf);

        /* 2) usbifVID,PID.configCN.IN */
        (void) sprintf(usba_name[n++],
            "usbif%x,%x.config%x.%x",
            usb_dev_descr->idVendor,
            usb_dev_descr->idProduct,
            child_ud->usb_cfg_value,
            intf);


        if (if_descr.bInterfaceClass) {
                /* 3) usbifVID,classIC.ISC.IPROTO */
                (void) sprintf(usba_name[n++],
                    "usbif%x,class%x.%x.%x",
                    usb_dev_descr->idVendor,
                    if_descr.bInterfaceClass,
                    if_descr.bInterfaceSubClass,
                    if_descr.bInterfaceProtocol);

                /* 4) usbifVID,classIC.ISC */
                (void) sprintf(usba_name[n++],
                    "usbif%x,class%x.%x",
                    usb_dev_descr->idVendor,
                    if_descr.bInterfaceClass,
                    if_descr.bInterfaceSubClass);

                /* 5) usbifVID,classIC */
                (void) sprintf(usba_name[n++],
                    "usbif%x,class%x",
                    usb_dev_descr->idVendor,
                    if_descr.bInterfaceClass);

                /* 6) usbif,classIC.ISC.IPROTO */
                (void) sprintf(usba_name[n++],
                    "usbif,class%x.%x.%x",
                    if_descr.bInterfaceClass,
                    if_descr.bInterfaceSubClass,
                    if_descr.bInterfaceProtocol);

                /* 7) usbif,classIC.ISC */
                (void) sprintf(usba_name[n++],
                    "usbif,class%x.%x",
                    if_descr.bInterfaceClass,
                    if_descr.bInterfaceSubClass);

                /* 8) usbif,classIC */
                (void) sprintf(usba_name[n++],
                    "usbif,class%x",
                    if_descr.bInterfaceClass);
        }

        if (usba_get_ugen_binding(child_dip) ==
            USBA_UGEN_INTERFACE_BINDING) {
                /* 9) ugen */
                (void) sprintf(usba_name[n++], "ugen");
        }

        for (i = 0; i < n; i += 2) {
                USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                    "compatible name:\t%s\t%s", usba_name[i],
                    (((i+1) < n)? usba_name[i+1] : ""));
        }

        /* create compatible property */
        rval = ndi_prop_update_string_array(DDI_DEV_T_NONE, child_dip,
            "compatible", (char **)usba_name, n);

        kmem_free(usba_name_buf, USBA_MAX_COMPAT_NAMES *
            USBA_MAX_COMPAT_NAME_LEN);

        if (rval != DDI_PROP_SUCCESS) {

                goto fail;
        }

        /* update the address property */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "assigned-address", child_ud->usb_addr);
        if (rval != DDI_PROP_SUCCESS) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_ready_interface_node: address update failed");
        }

        /* create property with if number */
        rval = ndi_prop_update_int(DDI_DEV_T_NONE, child_dip,
            "interface", intf);

        if (rval != DDI_PROP_SUCCESS) {

                goto fail;
        }

        USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
            "%s%d port %d: %s, dip = 0x%p",
            ddi_node_name(ddi_get_parent(dip)),
            ddi_get_instance(ddi_get_parent(dip)),
            child_ud->usb_port, ddi_node_name(child_dip), (void *)child_dip);

        usba_set_usba_device(child_dip, child_ud);
        ASSERT(!mutex_owned(&(usba_get_usba_device(child_dip)->usb_mutex)));

        return (child_dip);

fail:
        (void) usba_destroy_child_devi(child_dip, NDI_DEVI_REMOVE);

        return (NULL);
}


/*
 * retrieve string descriptors for manufacturer, vendor and serial
 * number
 */
void
usba_get_dev_string_descrs(dev_info_t *dip, usba_device_t *ud)
{
        char    *tmpbuf, *str;
        int     l;
        usb_dev_descr_t *usb_dev_descr = ud->usb_dev_descr;


        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_get_usb_string_descr: m=%d, p=%d, s=%d",
            usb_dev_descr->iManufacturer,
            usb_dev_descr->iProduct,
            usb_dev_descr->iSerialNumber);

        tmpbuf = kmem_zalloc(USB_MAXSTRINGLEN, KM_SLEEP);

        /* fetch manufacturer string */
        if ((ud->usb_mfg_str == NULL) && usb_dev_descr->iManufacturer &&
            (usb_get_string_descr(dip, USB_LANG_ID,
            usb_dev_descr->iManufacturer, tmpbuf, USB_MAXSTRINGLEN) ==
            USB_SUCCESS)) {

                l = strlen(tmpbuf);
                if (l > 0) {
                        str = kmem_zalloc(l + 1, KM_SLEEP);
                        mutex_enter(&ud->usb_mutex);
                        ud->usb_mfg_str = str;
                        (void) strcpy(ud->usb_mfg_str, tmpbuf);
                        mutex_exit(&ud->usb_mutex);
                }
        }

        /* fetch product string */
        if ((ud->usb_product_str == NULL) && usb_dev_descr->iProduct &&
            (usb_get_string_descr(dip, USB_LANG_ID, usb_dev_descr->iProduct,
            tmpbuf, USB_MAXSTRINGLEN) ==
            USB_SUCCESS)) {

                l = strlen(tmpbuf);
                if (l > 0) {
                        str = kmem_zalloc(l + 1, KM_SLEEP);
                        mutex_enter(&ud->usb_mutex);
                        ud->usb_product_str = str;
                        (void) strcpy(ud->usb_product_str, tmpbuf);
                        mutex_exit(&ud->usb_mutex);
                }
        }

        /* fetch device serial number string */
        if ((ud->usb_serialno_str == NULL) && usb_dev_descr->iSerialNumber &&
            (usb_get_string_descr(dip, USB_LANG_ID,
            usb_dev_descr->iSerialNumber, tmpbuf, USB_MAXSTRINGLEN) ==
            USB_SUCCESS)) {

                l = strlen(tmpbuf);
                if (l > 0) {
                        str = kmem_zalloc(l + 1, KM_SLEEP);
                        mutex_enter(&ud->usb_mutex);
                        ud->usb_serialno_str = str;
                        (void) strcpy(ud->usb_serialno_str, tmpbuf);
                        mutex_exit(&ud->usb_mutex);
                }
        }

        kmem_free(tmpbuf, USB_MAXSTRINGLEN);
}


/*
 * usba_get_mfg_prod_sn_str:
 *      Return a string containing mfg, product, serial number strings.
 *      Remove duplicates if some strings are the same.
 *
 * Arguments:
 *      dip     - pointer to dev info
 *      buffer  - Where string is returned
 *      buflen  - Length of buffer
 *
 * Returns:
 *      Same as second arg.
 */
char *
usba_get_mfg_prod_sn_str(
    dev_info_t  *dip,
    char        *buffer,
    int         buflen)
{
        usba_device_t *usba_device = usba_get_usba_device(dip);
        int return_len = 0;
        int len = 0;

        buffer[0] = '\0';
        buffer[buflen-1] = '\0';

        /* Manufacturer string exists. */
        if ((usba_device->usb_mfg_str) &&
            ((len = strlen(usba_device->usb_mfg_str)) != 0)) {
                (void) strncpy(buffer, usba_device->usb_mfg_str, buflen - 1);
                return_len = min(buflen - 1, len);
        }

        /* Product string exists to append. */
        if ((usba_device->usb_product_str) &&
            ((len = strlen(usba_device->usb_product_str)) != 0)) {
                if (return_len > 0) {
                        buffer[return_len++] = ' ';
                }
                (void) strncpy(&buffer[return_len],
                    usba_device->usb_product_str, buflen - return_len - 1);
                return_len = min(buflen - 1, return_len + len);
        }

        /* Serial number string exists to append. */
        if ((usba_device->usb_serialno_str) &&
            ((len = strlen(usba_device->usb_serialno_str)) != 0)) {
                if (return_len > 0) {
                        buffer[return_len++] = ' ';
                }
                (void) strncpy(&buffer[return_len],
                    usba_device->usb_serialno_str,
                    buflen - return_len - 1);
        }

        return (buffer);
}

/*
 * USB enumeration statistic functions
 */

/*
 * Increments the hotplug statistics based on flags.
 */
void
usba_update_hotplug_stats(dev_info_t *dip, usb_flags_t flags)
{
        usba_device_t   *usba_device = usba_get_usba_device(dip);
        usba_hcdi_t     *hcdi =
            usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip);

        mutex_enter(&hcdi->hcdi_mutex);
        if (flags & USBA_TOTAL_HOTPLUG_SUCCESS) {
                hcdi->hcdi_total_hotplug_success++;
                HCDI_HOTPLUG_STATS_DATA(hcdi)->
                    hcdi_hotplug_total_success.value.ui64++;
        }
        if (flags & USBA_HOTPLUG_SUCCESS) {
                hcdi->hcdi_hotplug_success++;
                HCDI_HOTPLUG_STATS_DATA(hcdi)->
                    hcdi_hotplug_success.value.ui64++;
        }
        if (flags & USBA_TOTAL_HOTPLUG_FAILURE) {
                hcdi->hcdi_total_hotplug_failure++;
                HCDI_HOTPLUG_STATS_DATA(hcdi)->
                    hcdi_hotplug_total_failure.value.ui64++;
        }
        if (flags & USBA_HOTPLUG_FAILURE) {
                hcdi->hcdi_hotplug_failure++;
                HCDI_HOTPLUG_STATS_DATA(hcdi)->
                    hcdi_hotplug_failure.value.ui64++;
        }
        mutex_exit(&hcdi->hcdi_mutex);
}


/*
 * Retrieve the current enumeration statistics
 */
void
usba_get_hotplug_stats(dev_info_t *dip, ulong_t *total_success,
    ulong_t *success, ulong_t *total_failure, ulong_t *failure,
    uchar_t *device_count)
{
        usba_device_t   *usba_device = usba_get_usba_device(dip);
        usba_hcdi_t     *hcdi =
            usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip);

        mutex_enter(&hcdi->hcdi_mutex);
        *total_success = hcdi->hcdi_total_hotplug_success;
        *success = hcdi->hcdi_hotplug_success;
        *total_failure = hcdi->hcdi_total_hotplug_failure;
        *failure = hcdi->hcdi_hotplug_failure;
        *device_count = hcdi->hcdi_device_count;
        mutex_exit(&hcdi->hcdi_mutex);
}


/*
 * Reset the resetable hotplug stats
 */
void
usba_reset_hotplug_stats(dev_info_t *dip)
{
        usba_device_t   *usba_device = usba_get_usba_device(dip);
        usba_hcdi_t     *hcdi =
            usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip);
        hcdi_hotplug_stats_t *hsp;

        mutex_enter(&hcdi->hcdi_mutex);
        hcdi->hcdi_hotplug_success = 0;
        hcdi->hcdi_hotplug_failure = 0;

        hsp = HCDI_HOTPLUG_STATS_DATA(hcdi);
        hsp->hcdi_hotplug_success.value.ui64 = 0;
        hsp->hcdi_hotplug_failure.value.ui64 = 0;
        mutex_exit(&hcdi->hcdi_mutex);
}


/*
 * usba_bind_driver():
 *      This function calls ndi_devi_bind_driver() which tries to
 *      bind a driver to the device.  If the driver binding fails
 *      we get an rval of NDI_UNBOUD and report an error to the
 *      syslog that the driver failed binding.
 *      If rval is something other than NDI_UNBOUND we report an
 *      error to the console.
 *
 *      This function returns USB_SUCCESS if no errors were
 *      encountered while binding.
 */
int
usba_bind_driver(dev_info_t *dip)
{
        int     rval;
        char    *name;
        uint8_t if_num = usba_get_ifno(dip);

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_bind_driver: dip = 0x%p, if_num = 0x%x", (void *)dip, if_num);

        name = kmem_zalloc(MAXNAMELEN, KM_SLEEP);

        /* bind device to the driver */
        if ((rval = ndi_devi_bind_driver(dip, 0)) != NDI_SUCCESS) {
                /* if we fail to bind report an error */
                (void) usba_get_mfg_prod_sn_str(dip, name, MAXNAMELEN);
                if (name[0] != '\0') {
                        if (!usb_owns_device(dip)) {
                                USB_DPRINTF_L1(DPRINT_MASK_USBA,
                                    usba_log_handle,
                                    "no driver found for "
                                    "interface %d (nodename: '%s') of %s",
                                    if_num, ddi_node_name(dip), name);
                        } else {
                                USB_DPRINTF_L1(DPRINT_MASK_USBA,
                                    usba_log_handle,
                                    "no driver found for device %s", name);
                        }
                } else {
                        (void) ddi_pathname(dip, name);
                        USB_DPRINTF_L1(DPRINT_MASK_USBA,
                            usba_log_handle,
                            "no driver found for device %s", name);
                }

                kmem_free(name, MAXNAMELEN);

                return (USB_FAILURE);
        }
        kmem_free(name, MAXNAMELEN);

        return ((rval == NDI_SUCCESS) ? USB_SUCCESS : USB_FAILURE);
}


/*
 * usba_get_hc_dma_attr:
 *      function returning dma attributes of the HCD
 *
 * Arguments:
 *      dip     - pointer to devinfo of the client
 *
 * Return Values:
 *      hcdi_dma_attr
 */
ddi_dma_attr_t *
usba_get_hc_dma_attr(dev_info_t *dip)
{
        usba_device_t *usba_device = usba_get_usba_device(dip);
        usba_hcdi_t *hcdi = usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip);

        return (hcdi->hcdi_dma_attr);
}


/*
 * usba_check_for_leaks:
 *      check usba_device structure for leaks
 *
 * Arguments:
 *      usba_device     - usba_device structure pointer
 */
void
usba_check_for_leaks(usba_device_t *usba_device)
{
        int i, ph_open_cnt, req_wrp_leaks, iface;
        int leaks = 0;

        USB_DPRINTF_L4(DPRINT_MASK_USBA, usba_log_handle,
            "usba_check_for_leaks: %s%d usba_device=0x%p",
            ddi_driver_name(usba_device->usb_dip),
            ddi_get_instance(usba_device->usb_dip), (void *)usba_device);

        /*
         * default pipe is still open
         * all other pipes should be closed
         */
        for (ph_open_cnt = 0, i = 1; i < USBA_N_ENDPOINTS; i++) {
                usba_ph_impl_t *ph_impl =
                    &usba_device->usb_ph_list[i];
                if (ph_impl->usba_ph_data) {
                        USB_DPRINTF_L2(DPRINT_MASK_USBA,
                            usba_log_handle,
                            "%s%d: leaking pipehandle=0x%p (0x%p) ep_addr=0x%x",
                            ddi_driver_name(ph_impl->usba_ph_data->p_dip),
                            ddi_get_instance(ph_impl->usba_ph_data->p_dip),
                            (void *)ph_impl,
                            (void *)ph_impl->usba_ph_data,
                            ph_impl->usba_ph_ep.bEndpointAddress);
                        ph_open_cnt++;
                        leaks++;
#ifndef DEBUG
                        usb_pipe_close(ph_impl->usba_ph_data->p_dip,
                            (usb_pipe_handle_t)ph_impl, USB_FLAGS_SLEEP,
                            NULL, NULL);
#endif
                }
        }
        req_wrp_leaks =  usba_list_entry_leaks(&usba_device->
            usb_allocated, "request wrappers");

        ASSERT(ph_open_cnt == 0);
        ASSERT(req_wrp_leaks == 0);

        if (req_wrp_leaks) {
                usba_list_entry_t *entry;

                while ((entry = usba_rm_first_from_list(
                    &usba_device->usb_allocated)) != NULL) {
                        usba_req_wrapper_t *wrp;

                        mutex_enter(&entry->list_mutex);
                        wrp = (usba_req_wrapper_t *)entry->private;
                        mutex_exit(&entry->list_mutex);
                        leaks++;

                        USB_DPRINTF_L2(DPRINT_MASK_USBA,
                            usba_log_handle,
                            "%s%d: leaking request 0x%p",
                            ddi_driver_name(wrp->wr_dip),
                            ddi_get_instance(wrp->wr_dip),
                            (void *)wrp->wr_req);

                        /*
                         * put it back, usba_req_wrapper_free
                         * expects it on the list
                         */
                        usba_add_to_list(&usba_device->usb_allocated,
                            &wrp->wr_allocated_list);

                        usba_req_wrapper_free(wrp);
                }
        }

        mutex_enter(&usba_device->usb_mutex);
        for (iface = 0; iface < usba_device->usb_n_ifs; iface++) {
                USB_DPRINTF_L3(DPRINT_MASK_USBA, usba_log_handle,
                    "usba_check_for_leaks: if=%d client_flags=0x%x",
                    iface, usba_device->usb_client_flags[iface]);

                if (usba_device->usb_client_flags[iface] &
                    USBA_CLIENT_FLAG_DEV_DATA) {
                        usb_client_dev_data_list_t *entry =
                            usba_device->usb_client_dev_data_list.cddl_next;
                        usb_client_dev_data_list_t *next;
                        usb_client_dev_data_t *dev_data;

                        while (entry) {
                                dev_info_t *dip = entry->cddl_dip;
                                next = entry->cddl_next;
                                dev_data = entry->cddl_dev_data;


                                if (!i_ddi_devi_attached(dip)) {
                                        USB_DPRINTF_L2(DPRINT_MASK_USBA,
                                            usba_log_handle,
                                            "%s%d: leaking dev_data 0x%p",
                                            ddi_driver_name(dip),
                                            ddi_get_instance(dip),
                                            (void *)dev_data);

                                        leaks++;

                                        mutex_exit(&usba_device->usb_mutex);
                                        usb_free_dev_data(dip, dev_data);
                                        mutex_enter(&usba_device->usb_mutex);
                                }

                                entry = next;
                        }
                }
                if (usba_device->usb_client_flags[iface] &
                    USBA_CLIENT_FLAG_ATTACH) {
                        dev_info_t *dip = usba_device->
                            usb_client_attach_list[iface].dip;

                        USB_DPRINTF_L2(DPRINT_MASK_USBA,
                            usba_log_handle,
                            "%s%d: did no usb_client_detach",
                            ddi_driver_name(dip), ddi_get_instance(dip));
                        leaks++;

                        mutex_exit(&usba_device->usb_mutex);
                        usb_client_detach(dip, NULL);
                        mutex_enter(&usba_device->usb_mutex);

                        usba_device->
                            usb_client_attach_list[iface].dip = NULL;

                        usba_device->usb_client_flags[iface] &=
                            ~USBA_CLIENT_FLAG_ATTACH;

                }
                if (usba_device->usb_client_flags[iface] &
                    USBA_CLIENT_FLAG_EV_CBS) {
                        dev_info_t *dip =
                            usba_device->usb_client_ev_cb_list[iface].
                            dip;
                        usb_event_t *ev_data =
                            usba_device->usb_client_ev_cb_list[iface].
                            ev_data;

                        USB_DPRINTF_L2(DPRINT_MASK_USBA,
                            usba_log_handle,
                            "%s%d: did no usb_unregister_event_cbs",
                            ddi_driver_name(dip), ddi_get_instance(dip));
                        leaks++;

                        mutex_exit(&usba_device->usb_mutex);
                        usb_unregister_event_cbs(dip, ev_data);
                        mutex_enter(&usba_device->usb_mutex);

                        usba_device->usb_client_ev_cb_list[iface].
                            dip = NULL;
                        usba_device->usb_client_ev_cb_list[iface].
                            ev_data = NULL;
                        usba_device->usb_client_flags[iface] &=
                            ~USBA_CLIENT_FLAG_EV_CBS;
                }
        }
        mutex_exit(&usba_device->usb_mutex);

        if (leaks) {
                USB_DPRINTF_L2(DPRINT_MASK_USBA, usba_log_handle,
                    "all %d leaks fixed", leaks);
        }
}