view usr/src/uts/common/io/usb/usba/usba.c @ 18357:11a2d819935f

Merge branch 'master' of git://github.com/illumos/illumos-gate
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Fri, 10 Mar 2017 18:57:44 -0500
parents 3cadecf74e1a bd4d8900145e
children
line wrap: on
line source

/*
 * 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>
 */


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

	ASSERT(usba_device->usb_client_dev_data_list.cddl_next == NULL);

	if (usba_device->usb_client_flags) {
		int i;

		for (i = 0; i < usba_device->usb_n_ifs; i++) {
			ASSERT(usba_device->usb_client_flags[i] == 0);
		}
		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);

done:
	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;
	int	circular;

	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, &circular);
	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, circular);

	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_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");
		}
	}

	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,
			    (void (*)())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);
	}
}