view usr/src/uts/common/io/usb/hwa/hwahc/hwahc.c @ 10912:bb04b6e33d44

6894135 some usb modules are not lint clean
author Raymond Chen <Raymond.Chen@Sun.COM>
date Fri, 30 Oct 2009 10:52:42 +0800
parents 4b03a6b797a8
children 76b4d0b12a17
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.
 */

/*
 * The Data Transfer Interface driver for Host Wire Adapter device
 *
 * HWA device has two interfaces, one is the data transfer interface,
 * another is the radio control interface. This driver (hwahc) is only
 * for data transfer interface support, but it depends on the radio
 * control interface driver (hwarc) to work. That means the hwarc
 * driver must be loaded while the hwahc is working. This is now
 * ensured by holding hwarc open until hwahc detaches or powers down.
 *
 * The data transfer interface has three endpoints besides the default
 * control endpoint which is shared between the two interfaces. The
 * three endpoints are:
 *
 * - notification endpoint (intr in type, for asynchronous event
 * notifications and transfer status notifications)
 *
 * - data transfer OUT endpoint (bulk out type, for sending transfer
 * requests and transfer data from the host to the HWA device)
 *
 * - data transfer IN endpoint (bulk in type, for returning transfer
 * status and transfer data from the HWA device to the host)
 *
 * The HWA device is a USB 2.0 device, so it supports the standard USB
 * requests defined in chapter 9 of USB 2.0 specification as other USB
 * client devices. But its most important functionality is to work as
 * a wireless USB host. This means the hwahc driver needs to supply
 * host controller functionalities, which include children hotplug
 * support and data transfer support to children device endpoints.
 *
 * So hwahc driver is implemented as a nexus driver and it follows the
 * event mechanism in existing USBA framework to support children
 * hotplug events.
 *
 * The hwahc driver works as the root-hub on wireless USB bus. And it
 * relays data transfers to/from wireless bus to the USB bus where ehci/
 * ohci/uhci works as the root-hub. This makes a bus cascading topology.
 *
 * The data transfer to/from wireless device endpoints is implemented by
 * remote pipe (rpipe) mechanism. The rpipe descriptor on the HWA defines
 * the attributes of a wireless USB transfer, such as the transfer type,
 * the target device address, the target endpoint address and the max
 * packet size. And the transfer requests through data transfer OUT
 * endpoint will take a certain rpipe as the transfer target, thus
 * fulfills the data transfer across buses. Refer to chapter 8 of WUSB
 * 1.0 specification for details of this.
 */

#define	USBDRV_MAJOR_VER	2
#define	USBDRV_MINOR_VER	0

#include <sys/usb/hwa/hwahc/hwahc.h>
#include <sys/usb/hwa/hwahc/hwahc_util.h>
#include <sys/usb/usba/wa.h>
#include <sys/usb/usba/wusba.h>
#include <sys/usb/usba/whcdi.h>
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_impl.h>
#include <sys/usb/usba/usba_devdb.h>	/* for usba_devdb_refresh */
#include <sys/usb/hubd/hubdvar.h>
#include <sys/usb/hubd/hubd_impl.h>	/* for hubd_ioctl_data_t */
#include <sys/strsubr.h>	/* for allocb_wait */
#include <sys/strsun.h>		/* for MBLKL macro */
#include <sys/fs/dv_node.h>	/* for devfs_clean */
#include <sys/uwb/uwbai.h>	/* for uwb ioctls */
#include <sys/random.h>

void *hwahc_statep;

/* number of instances */
#define	HWAHC_INSTS	1

/* default value for set number DNTS slots request */
#define	HWAHC_DEFAULT_DNTS_INTERVAL	2 /* ms */
#define	HWAHC_DEFAULT_DNTS_SLOT_NUM	4


/* debug support */
uint_t	hwahc_errmask	= (uint_t)PRINT_MASK_ALL;
uint_t	hwahc_errlevel	= USB_LOG_L4;
uint_t	hwahc_instance_debug = (uint_t)-1;

/* bus config debug flag */
uint_t	hwahc_bus_config_debug = 0;
uint8_t	hwahc_enable_trust_timeout = 1;


/*
 * Use the default GTK for the whole life of HWA driver.
 * Not so compatible with WUSB spec.
 */
static uint8_t	dft_gtk[16];
static uint8_t	dft_gtkid[3];

extern usb_log_handle_t	whcdi_log_handle;

/*
 * Function Prototypes
 */
/* driver operations (dev_ops) entry points */
static int	hwahc_open(dev_t *, int, int, cred_t *);
static int	hwahc_close(dev_t, int, int, cred_t *);
static int	hwahc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

static int	hwahc_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int	hwahc_attach(dev_info_t *, ddi_attach_cmd_t);
static int	hwahc_detach(dev_info_t *, ddi_detach_cmd_t);
static int	hwahc_power(dev_info_t *, int, int);

/* bus_ops entry points */
static int	hwahc_bus_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t,
		void *, void *);
static int	hwahc_busop_get_eventcookie(dev_info_t *, dev_info_t *,
		char *, ddi_eventcookie_t *);
static int	hwahc_busop_add_eventcall(
		dev_info_t *, dev_info_t *, ddi_eventcookie_t,
		void (*)(dev_info_t *, ddi_eventcookie_t, void *, void *),
		void *, ddi_callback_id_t *);
static int	hwahc_busop_remove_eventcall(dev_info_t *, ddi_callback_id_t);
static int	hwahc_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t,
		void *, dev_info_t **);
static int	hwahc_bus_unconfig(dev_info_t *, uint_t, ddi_bus_config_op_t,
		void *);

/* hotplug and power management supporting functions */
static int	hwahc_disconnect_event_cb(dev_info_t *dip);
static int	hwahc_reconnect_event_cb(dev_info_t *dip);
static int	hwahc_pre_suspend_event_cb(dev_info_t *dip);
static int	hwahc_post_resume_event_cb(dev_info_t *dip);
static int	hwahc_cpr_suspend(dev_info_t *);
static int	hwahc_cpr_resume(dev_info_t *);
static void	hwahc_restore_device_state(dev_info_t *, hwahc_state_t *);
static void	hwahc_run_callbacks(hwahc_state_t *, usba_event_t);
static void	hwahc_post_event(hwahc_state_t *, usb_port_t, usba_event_t);

static int	hwahc_cleanup(dev_info_t *, hwahc_state_t *);
static void	hwahc_create_pm_components(dev_info_t *, hwahc_state_t *);
static void	hwahc_destroy_pm_components(hwahc_state_t *);
static void	hwahc_pm_busy_component(hwahc_state_t *);
static void	hwahc_pm_idle_component(hwahc_state_t *);
static int	hwahc_pwrlvl0(hwahc_state_t *);
static int	hwahc_pwrlvl1(hwahc_state_t *);
static int	hwahc_pwrlvl2(hwahc_state_t *);
static int	hwahc_pwrlvl3(hwahc_state_t *);
static int	hwahc_hc_channel_suspend(hwahc_state_t *);

/* hardware initialization and deinitialization functions */
static int	hwahc_parse_security_data(wusb_secrt_data_t *,
		usb_cfg_data_t *);
static void	hwahc_print_secrt_data(hwahc_state_t *);

static int	hwahc_hub_attach(hwahc_state_t *);
static int	hwahc_hub_detach(hwahc_state_t *);

static int	hwahc_hc_initial_start(hwahc_state_t *);
static int	hwahc_hc_final_stop(hwahc_state_t *);
static int	hwahc_wa_start(hwahc_state_t *);
static void	hwahc_wa_stop(hwahc_state_t *);
static int	hwahc_hc_channel_start(hwahc_state_t *);
static int	hwahc_hc_channel_stop(hwahc_state_t *);
static void	hwahc_hc_data_init(hwahc_state_t *);
static void	hwahc_hc_data_fini(hwahc_state_t *);

/* ioctl support */
static int	hwahc_cfgadm_ioctl(hwahc_state_t *, int, intptr_t, int,
		cred_t *, int *);
static int	hwahc_wusb_ioctl(hwahc_state_t *, int, intptr_t, int,
		cred_t *, int *);

/* callbacks registered to USBA */
static void	hwahc_disconnect_dev(dev_info_t *, usb_port_t);
static void	hwahc_reconnect_dev(dev_info_t *, usb_port_t);
static int	hwahc_create_child(dev_info_t *, usb_port_t);
static int	hwahc_destroy_child(dev_info_t *, usb_port_t);
static int	hwahc_cleanup_child(dev_info_t *);
static int	hwahc_delete_child(dev_info_t *, usb_port_t, uint_t, boolean_t);

/* data transfer and notification handling */
static void	hwahc_intr_cb(usb_pipe_handle_t, struct usb_intr_req *);
static void	hwahc_intr_exc_cb(usb_pipe_handle_t, struct usb_intr_req *);
static void	hwahc_handle_notif(hwahc_state_t *, mblk_t *);
static void	hwahc_handle_xfer_result(hwahc_state_t *, uint8_t);
static void	hwahc_stop_result_thread(hwahc_state_t *);
static void	hwahc_result_thread(void *);
static void	hwahc_handle_dn_notif(hwahc_state_t *, hwa_notif_dn_recvd_t *);
static void	hwahc_notif_thread(void *);
static void	hwahc_handle_dn(hwahc_state_t *, hwa_notif_dn_recvd_t *);
static void	hwahc_drain_notif_queue(hwahc_state_t *);
static void	hwahc_rpipe_xfer_cb(dev_info_t *, usba_pipe_handle_data_t *,
		wusb_wa_trans_wrapper_t *, usb_cr_t);

static void	hwahc_trust_timeout_handler(void *arg);
static void	hwahc_stop_trust_timer(wusb_dev_info_t *dev);

static int hwahc_pipe_submit_periodic_req(wusb_wa_data_t *wa_data,
	usba_pipe_handle_data_t *ph);

/* hwa specific requests */
static int	hwahc_set_chid(hwahc_state_t *, uint8_t *);

/* helper functions */
static usb_port_t hwahc_get_port_num(hwahc_state_t *, struct devctl_iocdata *);
static dev_info_t *hwahc_get_child_dip(hwahc_state_t *, usb_port_t);

static struct cb_ops hwahc_cb_ops = {
	hwahc_open,			/* Open */
	hwahc_close,			/* Close */
	nodev,				/* Strategy */
	nodev,				/* Print */
	nodev,				/* Dump */
	nodev,				/* Read */
	nodev,				/* Write */
	hwahc_ioctl,			/* Ioctl */
	nodev,				/* Devmap */
	nodev,				/* Mmap */
	nodev,				/* Segmap */
	nochpoll,			/* Poll */
	ddi_prop_op,			/* cb_prop_op */
	NULL,				/* Streamtab */
	D_MP				/* Driver compatibility flag */
};

static struct bus_ops hwahc_busops = {
	BUSO_REV,
	nullbusmap,			/* bus_map */
	NULL,				/* bus_get_intrspec */
	NULL,				/* bus_add_intrspec */
	NULL,				/* bus_remove_intrspec */
	NULL,				/* bus_map_fault */
	ddi_dma_map,			/* bus_dma_map */
	ddi_dma_allochdl,
	ddi_dma_freehdl,
	ddi_dma_bindhdl,
	ddi_dma_unbindhdl,
	ddi_dma_flush,
	ddi_dma_win,
	ddi_dma_mctl,			/* bus_dma_ctl */
	hwahc_bus_ctl,			/* bus_ctl */
	ddi_bus_prop_op,		/* bus_prop_op */
	hwahc_busop_get_eventcookie,	/* bus_get_eventcookie */
	hwahc_busop_add_eventcall,	/* bus_add_eventcall */
	hwahc_busop_remove_eventcall,	/* bus_remove_eventcall */
	NULL,				/* bus_post_event */
	NULL,				/* bus_intr_ctl */
	hwahc_bus_config,		/* bus_config */
	hwahc_bus_unconfig,		/* bus_unconfig */
	NULL,				/* bus_fm_init */
	NULL,				/* bus_fm_fini */
	NULL,				/* bus_fm_access_enter */
	NULL,				/* bus_fm_access_exit */
	NULL,				/* bus_power */
};

static struct dev_ops hwahc_ops = {
	DEVO_REV,			/* Devo_rev */
	0,				/* Refcnt */
	hwahc_info,			/* Info */
	nulldev,			/* Identify */
	nulldev,			/* Probe */
	hwahc_attach,			/* Attach */
	hwahc_detach,			/* Detach */
	nodev,				/* Reset */
	&hwahc_cb_ops,			/* Driver operations */
	&hwahc_busops,			/* Bus operations */
	hwahc_power,			/* Power */
	ddi_quiesce_not_needed,		/* devo_quiesce */
};

static struct modldrv hwahc_modldrv =	{
	&mod_driverops,
	"WUSB hwa-hc driver",
	&hwahc_ops
};

static struct modlinkage modlinkage = {
	MODREV_1,
	&hwahc_modldrv,
	NULL
};

/* events from parent */
static usb_event_t hwahc_events = {
	hwahc_disconnect_event_cb,
	hwahc_reconnect_event_cb,
	hwahc_pre_suspend_event_cb,
	hwahc_post_resume_event_cb
};

/*
 * events support for children
 * A map tween USBA_EVENTs and DDI_EVENTs.
 */
static ndi_event_definition_t hwahc_ndi_event_defs[] = {
	{USBA_EVENT_TAG_HOT_REMOVAL, DDI_DEVI_REMOVE_EVENT, EPL_KERNEL,
						NDI_EVENT_POST_TO_ALL},
	{USBA_EVENT_TAG_HOT_INSERTION, DDI_DEVI_INSERT_EVENT, EPL_KERNEL,
						NDI_EVENT_POST_TO_ALL},
	{USBA_EVENT_TAG_POST_RESUME, USBA_POST_RESUME_EVENT, EPL_KERNEL,
						NDI_EVENT_POST_TO_ALL},
	{USBA_EVENT_TAG_PRE_SUSPEND, USBA_PRE_SUSPEND_EVENT, EPL_KERNEL,
						NDI_EVENT_POST_TO_ALL}
};

#define	HWAHC_N_NDI_EVENTS \
	(sizeof (hwahc_ndi_event_defs) / sizeof (ndi_event_definition_t))

static	ndi_event_set_t hwahc_ndi_events = {
	NDI_EVENTS_REV1, HWAHC_N_NDI_EVENTS, hwahc_ndi_event_defs};

/* transfer callbacks */
static wusb_wa_cb_t hwahc_cbs = {
	hwahc_pipe_submit_periodic_req,
	hwahc_intr_cb,
	hwahc_intr_exc_cb,
	hwahc_rpipe_xfer_cb
};


/*
 * Module-wide initialization routine.
 */
int
_init(void)
{
	int rval;

	if ((rval = ddi_soft_state_init(&hwahc_statep, sizeof (hwahc_state_t),
	    HWAHC_INSTS)) != 0) {

		return (rval);
	}

	if ((rval = mod_install(&modlinkage)) != 0) {
		ddi_soft_state_fini(&hwahc_statep);
	}

	return (rval);
}


/*
 * Module-wide tear-down routine.
 */
int
_fini(void)
{
	int rval;

	if ((rval = mod_remove(&modlinkage)) == 0) {
		/* Release per module resources */
		ddi_soft_state_fini(&hwahc_statep);
	}

	return (rval);
}


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


/*
 * hwahc_info:
 *	Get minor number, instance number, etc.
 */
/*ARGSUSED*/
static int
hwahc_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
	void *arg, void **result)
{
	hwahc_state_t	*hwahcp;
	int error = DDI_FAILURE;
	int instance = HWAHC_MINOR_TO_INSTANCE(getminor((dev_t)arg));

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if ((hwahcp = ddi_get_soft_state(hwahc_statep,
		    instance)) != NULL) {
			*result = hwahcp->hwahc_dip;
			if (*result != NULL) {
				error = DDI_SUCCESS;
			}
		} else {
			*result = NULL;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)(uintptr_t)instance;
		error = DDI_SUCCESS;
		break;
	default:
		break;
	}

	return (error);
}


/*
 * hwahc_attach:
 *	Attach or resume.
 *
 *	For attach, initialize state and device, including:
 *		state variables, locks, device node,
 *		resource initialization, event registration,
 *		device registration with system
 *		power management, hotplugging
 *	For resume, restore device and state
 */
static int
hwahc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int				instance = ddi_get_instance(dip);
	hwahc_state_t			*hwahcp = NULL;
	usb_client_dev_data_t		*dev_data;
	struct usb_cfg_data		*cfg_data;
	usba_hcdi_register_args_t	hcdi_args;
	int				rval;
	char *pathname;

	USB_DPRINTF_L3(PRINT_MASK_ATTA, NULL, "hwahc_attach: cmd=%d", cmd);

	switch (cmd) {
	case DDI_ATTACH:
		break;
	case DDI_RESUME:
		(void) hwahc_cpr_resume(dip);

		return (DDI_SUCCESS);
	default:
		USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL,
		    "hwahc_attach: failed");

		return (DDI_FAILURE);
	}

	/*
	 * Allocate soft state information.
	 */
	rval = ddi_soft_state_zalloc(hwahc_statep, instance);
	if (rval != DDI_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL,
		    "hwahc_attach: cannot allocate soft state for instance %d",
		    instance);

		return (USB_FAILURE);
	}

	hwahcp = ddi_get_soft_state(hwahc_statep, instance);
	if (hwahcp == NULL) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, NULL,
		    "hwahc_attach: get soft state failed for instance %d",
		    instance);

		return (USB_FAILURE);
	}

	hwahcp->hwahc_log_handle = usb_alloc_log_hdl(dip, "hwahc",
	    &hwahc_errlevel, &hwahc_errmask, &hwahc_instance_debug, 0);

	/* initialize hc state */
	hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE;
	hwahcp->hwahc_dip = dip;
	hwahcp->hwahc_instance = instance;

	/* register with USBA as client driver */
	if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: client attach failed");

		goto fail;
	}

	if (usb_get_dev_data(dip, &dev_data, USB_PARSE_LVL_IF, 0) !=
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: cannot get dev_data");

		goto fail;
	}

	/* initialize mutex and cv */
	mutex_init(&hwahcp->hwahc_mutex, NULL, MUTEX_DRIVER,
	    dev_data->dev_iblock_cookie);
	cv_init(&hwahcp->hwahc_result_thread_cv, NULL, CV_DRIVER, NULL);

	hwahcp->hwahc_flags |= HWAHC_LOCK_INITED;
	hwahcp->hwahc_dev_data = dev_data;

	/* initialize data transfer function related structure */
	if (wusb_wa_data_init(dip, &hwahcp->hwahc_wa_data, &hwahc_cbs,
	    dev_data, PRINT_MASK_ATTA,
	    hwahcp->hwahc_log_handle) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: init wa data failed");

		goto fail;
	}

	hwahcp->hwahc_flags |= HWAHC_WA_INITED;
	cfg_data = dev_data->dev_curr_cfg;

	/* parse the security descrs from the configuration descr cloud */
	if (hwahc_parse_security_data(&hwahcp->hwahc_secrt_data, cfg_data) !=
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: parse security descrs failed");

		goto fail;
	}

	hwahcp->hwahc_default_pipe = dev_data->dev_default_ph;
	hwahcp->hwahc_wa_data.wa_private_data = (void *)hwahcp;
	hwahcp->hwahc_wa_data.wa_default_pipe = hwahcp->hwahc_default_pipe;

	usb_free_descr_tree(dip, dev_data);

	hwahcp->hwahc_dev_state = USB_DEV_ONLINE;

	/* now create components to power manage this device */
	hwahc_create_pm_components(dip, hwahcp);

	/*
	 * Event definition and registration
	 *
	 * allocate a new NDI event handle as a nexus driver
	 */
	(void) ndi_event_alloc_hdl(dip, 0, &hwahcp->hwahc_ndi_event_hdl,
	    NDI_SLEEP);

	/*
	 * bind our NDI events with the event handle,
	 * i.e. Define the events set we're to support as a nexus driver.
	 *
	 * These events will be used by bus_ops functions to register callbacks.
	 */
	if (ndi_event_bind_set(hwahcp->hwahc_ndi_event_hdl, &hwahc_ndi_events,
	    NDI_SLEEP)) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: binding event set failed");

		goto fail;
	}


	/*
	 * Register USB events to USBA(the parent) to get callbacks as a
	 * child of (root) hub
	 */
	if (usb_register_event_cbs(dip, &hwahc_events, 0) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: register_events failed");

		goto fail;
	}

	hwahcp->hwahc_flags |= HWAHC_EVENTS_REGISTERED;

	/* create minor nodes */
	if (ddi_create_minor_node(dip, "hwahc", S_IFCHR,
	    instance << HWAHC_MINOR_INSTANCE_SHIFT,
	    DDI_NT_NEXUS, 0) != DDI_SUCCESS) {

		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: cannot create minor node");

		goto fail;
	}

	hwahcp->hwahc_flags |= HWAHC_MINOR_NODE_CREATED;

	hwahcp->hwahc_hcdi_ops = hwahc_alloc_hcdi_ops(hwahcp);

	/* register this hc instance with usba HCD interface */
	hcdi_args.usba_hcdi_register_version = HCDI_REGISTER_VERSION;
	hcdi_args.usba_hcdi_register_dip = dip;
	hcdi_args.usba_hcdi_register_ops = hwahcp->hwahc_hcdi_ops;

	/* use parent dma attr here */
	hcdi_args.usba_hcdi_register_dma_attr = usba_get_hc_dma_attr(dip);
	hcdi_args.usba_hcdi_register_iblock_cookie = NULL;

	if (usba_hcdi_register(&hcdi_args, 0) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: usba_hcdi_register failed");

		goto fail;
	}

	hwahcp->hwahc_flags |= HWAHC_HCDI_REGISTERED;

	/* create hub minor node and register to usba HUBD interface */
	if (hwahc_hub_attach(hwahcp) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_attach: hub attach failed");

		goto fail;
	}
	hwahcp->hwahc_flags |= HWAHC_HUBREG;

	/* intialize WUSB host function related structure */
	hwahc_hc_data_init(hwahcp);
	hwahcp->hwahc_flags |= HWAHC_HC_INITED;

	/* can be combined with wusb_wa_data_init() */
	if (hwahc_wa_start(hwahcp) != USB_SUCCESS) {

		goto fail;
	}

	hwahcp->hwahc_flags |= HWAHC_WA_STARTED;

	/* report this dev */
	ddi_report_dev(dip);

	hwahc_pm_idle_component(hwahcp);

	mutex_enter(&(hwahcp->hwahc_mutex));
	hwahc_print_secrt_data(hwahcp);
	mutex_exit(&(hwahcp->hwahc_mutex));

	if (uwb_dev_online(dip) != USB_SUCCESS) {
		goto fail;
	}

	return (DDI_SUCCESS);

fail:
	pathname = kmem_alloc(MAXPATHLEN, KM_SLEEP);

	/* log this message to usba_debug_buf */
	USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "cannot attach %s", ddi_pathname(dip, pathname));

	kmem_free(pathname, MAXPATHLEN);

	if (hwahcp) {
		hwahc_pm_idle_component(hwahcp);

		rval = hwahc_cleanup(dip, hwahcp);
		if (rval != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    hwahcp->hwahc_log_handle,
			    "failure to complete cleanup after attach failure");
		}
	}

	return (DDI_FAILURE);
}


/*
 * hwahc_detach:
 *	detach or suspend driver instance
 *
 * Note: in detach, only contention threads is from pm and disconnnect.
 */
static int
hwahc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int		instance = ddi_get_instance(dip);
	hwahc_state_t	*hwahcp = ddi_get_soft_state(hwahc_statep, instance);
	int		rval = DDI_FAILURE;


	USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_detach: cmd = %d", cmd);

	switch (cmd) {
	case DDI_DETACH:
		USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "offline uwb device for dip: 0x%p", (void *)dip);
		/* offline the hwarc interface */
		(void) uwb_dev_offline(dip);
		if (hwahcp) {
			rval = hwahc_cleanup(dip, hwahcp);
		}

		break;
	case DDI_SUSPEND:
		rval = hwahc_cpr_suspend(dip);

		break;
	default:

		break;
	}

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


/*
 * hwahc_cleanup:
 *	clean up on attach failure or detach
 */
static int
hwahc_cleanup(dev_info_t *dip, hwahc_state_t *hwahcp)
{
	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_cleanup: start");

	if ((hwahcp->hwahc_flags & HWAHC_LOCK_INITED) == 0) {

		goto done;
	}

	/*
	 * deallocate events, if events are still registered
	 * (ie. children still attached) then we have to fail the detach
	 */
	if (hwahcp->hwahc_ndi_event_hdl &&
	    (ndi_event_free_hdl(hwahcp->hwahc_ndi_event_hdl) != NDI_SUCCESS)) {

		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_cleanup: ndi_event_free_hdl failed");

		return (USB_FAILURE);
	}

	if (hwahcp->hwahc_flags & HWAHC_EVENTS_REGISTERED) {
		/* unregister events */
		usb_unregister_event_cbs(dip, &hwahc_events);
	}

	if (hwahcp->hwahc_flags & HWAHC_HCDI_REGISTERED) {
		/* unregister the instance with usba HCD interface */
		usba_hcdi_unregister(hwahcp->hwahc_dip);
	}

	mutex_enter(&hwahcp->hwahc_mutex);

	if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) {
		/* stop the hw if it is enabled */
		(void) hwahc_hc_final_stop(hwahcp);
	}

	if (hwahcp->hwahc_flags & HWAHC_WA_STARTED) {
		/* can be combined with wusb_wa_data_fini() */
		mutex_exit(&hwahcp->hwahc_mutex);
		hwahc_wa_stop(hwahcp);
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	if (hwahcp->hwahc_flags & HWAHC_HC_INITED) {
		/* deinitialize the WUSB host function related structure */
		hwahc_hc_data_fini(hwahcp);
	}

	mutex_exit(&hwahcp->hwahc_mutex);

	if (hwahcp->hwahc_pm) {
		/* destroy power management components */
		hwahc_destroy_pm_components(hwahcp);
	}

	if (hwahcp->hwahc_flags & HWAHC_HUBREG) {
		/* unregister the instance from usba HUBD interface */
		if (hwahc_hub_detach(hwahcp) != USB_SUCCESS) {

			return (USB_FAILURE);
		}
	}

	if (hwahcp->hwahc_hcdi_ops) {
		usba_free_hcdi_ops(hwahcp->hwahc_hcdi_ops);
	}

	mutex_enter(&hwahcp->hwahc_mutex);
	if (hwahcp->hwahc_secrt_data.secrt_encry_descr) {
		/* free security descrs */
		kmem_free(hwahcp->hwahc_secrt_data.secrt_encry_descr,
		    sizeof (usb_encryption_descr_t) *
		    hwahcp->hwahc_secrt_data.secrt_n_encry);
	}

	if (hwahcp->hwahc_flags & HWAHC_WA_INITED) {
		/* deinitialize data transfer function related structure */
		wusb_wa_data_fini(&hwahcp->hwahc_wa_data);
	}
	mutex_exit(&hwahcp->hwahc_mutex);

	if (hwahcp->hwahc_flags & HWAHC_MINOR_NODE_CREATED) {
		/* remove all the minor nodes */
		ddi_remove_minor_node(dip, NULL);
	}

	/* destroy mutex and cv */
	mutex_destroy(&hwahcp->hwahc_mutex);
	cv_destroy(&hwahcp->hwahc_result_thread_cv);

done:
	/* unregister the client driver from usba */
	usb_client_detach(dip, hwahcp->hwahc_dev_data);

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_cleanup: end");

	usb_free_log_hdl(hwahcp->hwahc_log_handle);

	/* remove all properties created */
	ddi_prop_remove_all(dip);

	/* free the soft state information */
	ddi_soft_state_free(hwahc_statep, ddi_get_instance(dip));

	return (USB_SUCCESS);
}


/*ARGSUSED*/
static int
hwahc_open(dev_t *devp, int flag, int otyp, cred_t *cred_p)
{
	hwahc_state_t	*hwahcp;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    HWAHC_MINOR_TO_INSTANCE(getminor(*devp)))) == NULL) {

		return (ENXIO);
	}

	USB_DPRINTF_L4(PRINT_MASK_OPEN, hwahcp->hwahc_log_handle,
	    "hwahc_open: start");

	mutex_enter(&hwahcp->hwahc_mutex);
	/* exclusive open */
	if ((flag & FEXCL) && (hwahcp->hwahc_open_count > 0)) {
		mutex_exit(&hwahcp->hwahc_mutex);

		return (EBUSY);
	}

	if ((hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) ||
	    (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED)) {
		mutex_exit(&hwahcp->hwahc_mutex);

		return (EIO);
	}

	hwahcp->hwahc_open_count++;

	mutex_exit(&hwahcp->hwahc_mutex);

	/* raise to full power and keep it until close */
	hwahc_pm_busy_component(hwahcp);
	(void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR);

	USB_DPRINTF_L4(PRINT_MASK_OPEN, hwahcp->hwahc_log_handle,
	    "hwahc_open: end");

	return (0);
}


/*ARGSUSED*/
static int
hwahc_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
	hwahc_state_t	*hwahcp;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    HWAHC_MINOR_TO_INSTANCE(getminor(dev)))) == NULL) {

		return (ENXIO);
	}

	USB_DPRINTF_L4(PRINT_MASK_CLOSE, hwahcp->hwahc_log_handle,
	    "hwahc_close: start");

	mutex_enter(&hwahcp->hwahc_mutex);
	if (hwahcp->hwahc_open_count == 0) {
		USB_DPRINTF_L2(PRINT_MASK_CLOSE, hwahcp->hwahc_log_handle,
		    "hwahc_close: already closed");
		mutex_exit(&hwahcp->hwahc_mutex);

		return (EINVAL);
	}

	hwahcp->hwahc_open_count--;
	mutex_exit(&hwahcp->hwahc_mutex);

	hwahc_pm_idle_component(hwahcp);

	USB_DPRINTF_L4(PRINT_MASK_CLOSE, hwahcp->hwahc_log_handle,
	    "hwahc_close: end");

	return (0);
}

/* retrieve port number from devctl data */
static usb_port_t
hwahc_get_port_num(hwahc_state_t *hwahcp, struct devctl_iocdata *dcp)
{
	int32_t port;

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	/* Get which port to operate on.  */
	if (nvlist_lookup_int32(ndi_dc_get_ap_data(dcp), "port", &port) != 0) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_get_port_num: port lookup failed");
		port = 0;
	}

	USB_DPRINTF_L4(PRINT_MASK_CBOPS,  hwahcp->hwahc_log_handle,
	    "hwahc_get_port_num: hwahcp=0x%p, port=%d", (void *)hwahcp,
	    port);

	return ((usb_port_t)port);
}

/* return the child dip on a certain port */
static dev_info_t *
hwahc_get_child_dip(hwahc_state_t *hwahcp, usb_port_t port)
{
	wusb_hc_data_t		*hc_data;
	dev_info_t		*child_dip;

	hc_data = &hwahcp->hwahc_hc_data;

	/* check port range to prevent an illegal number */
	if (port > hc_data->hc_num_ports) {
		return (NULL);
	}

	mutex_enter(&hc_data->hc_mutex);
	child_dip = hc_data->hc_children_dips[port];
	mutex_exit(&hc_data->hc_mutex);

	return (child_dip);
}

/*
 * hwahc_cfgadm_state:
 *
 *	child_dip list		child_state		cfgadm_state
 *	--------------		----------		------------
 *	!= NULL			connected		configured or
 *							unconfigured
 *	!= NULL			not connected		disconnect but
 *							busy/still referenced
 *	NULL			connected		logically disconnected
 *	NULL			not connected		empty
 */
static uint_t
hwahc_cfgadm_state(hwahc_state_t *hwahcp, usb_port_t port)
{
	uint_t		state;
	dev_info_t	*child_dip = hwahc_get_child_dip(hwahcp, port);
	wusb_hc_data_t	*hc_data = &hwahcp->hwahc_hc_data;
	wusb_dev_info_t	*dev_info;

	if (child_dip == NULL) {

		return (HWAHC_CFGADM_INVALID);
	}

	mutex_enter(&hc_data->hc_mutex);
	dev_info = hc_data->hc_dev_infos[port];
	if (dev_info) {
		if (dev_info->wdev_state == WUSB_STATE_CONFIGURED) {
			if (child_dip &&
			    (DEVI_IS_DEVICE_OFFLINE(child_dip) ||
			    !i_ddi_devi_attached(child_dip))) {
				state = HWAHC_CFGADM_UNCONFIGURED;
			} else if (!child_dip) {
				state = HWAHC_CFGADM_UNCONFIGURED;
			} else {
				state = HWAHC_CFGADM_CONFIGURED;
			}
		} else if (dev_info->wdev_state == WUSB_STATE_UNCONNTED) {
			if (child_dip) {
				state = HWAHC_CFGADM_STILL_REFERENCED;
			} else {
				state = HWAHC_CFGADM_DISCONNECTED;
			}
		} else {
			if (child_dip) {
				state = HWAHC_CFGADM_STILL_REFERENCED;
			} else {
				state = HWAHC_CFGADM_UNCONFIGURED;
			}
		}
	} else {
		state = HWAHC_CFGADM_EMPTY;
	}
	mutex_exit(&hc_data->hc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_CBOPS,  hwahcp->hwahc_log_handle,
	    "hwahc_cfgadm_state: hwahcp=0x%p, port=%d state=0x%x",
	    (void *) hwahcp, port, state);

	return (state);
}

/* cfgadm ioctl support, now only implements list function */
/* ARGSUSED */
static int
hwahc_cfgadm_ioctl(hwahc_state_t *hwahcp, int cmd, intptr_t arg,
	int mode, cred_t *credp, int *rvalp)
{
	dev_info_t		*rh_dip;
	dev_info_t		*child_dip;
	struct devctl_iocdata	*dcp = NULL;
	usb_port_t		port = 0;
	devctl_ap_state_t	ap_state;
	int			circ, rh_circ, prh_circ;
	int			rv = 0;
	char			*msg;

	/* read devctl ioctl data */
	if ((cmd != DEVCTL_AP_CONTROL) &&
	    (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)) {

		return (EFAULT);
	}

	mutex_enter(&hwahcp->hwahc_mutex);

	rh_dip = hwahcp->hwahc_hubd->h_usba_device->usb_root_hub_dip;

	switch (cmd) {
	case DEVCTL_AP_DISCONNECT:
	case DEVCTL_AP_UNCONFIGURE:
	case DEVCTL_AP_CONFIGURE:
		if (hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    hwahcp->hwahc_log_handle,
			    "hwahc_cfgadm_ioctl: dev already gone");
			mutex_exit(&hwahcp->hwahc_mutex);
			if (dcp) {
				ndi_dc_freehdl(dcp);
			}

			return (EIO);
		}
		/* FALLTHROUGH */
	case DEVCTL_AP_GETSTATE:
		if ((port = hwahc_get_port_num(hwahcp, dcp)) == 0) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    hwahcp->hwahc_log_handle,
			    "hwahc_cfgadm_ioctl: bad port");
			mutex_exit(&hwahcp->hwahc_mutex);
			if (dcp) {
				ndi_dc_freehdl(dcp);
			}

			return (EINVAL);
		}
		break;
	case DEVCTL_AP_CONTROL:

		break;
	default:
		mutex_exit(&hwahcp->hwahc_mutex);
		if (dcp) {
			ndi_dc_freehdl(dcp);
		}

		return (ENOTTY);
	}

	/* should not happen, just in case */
	if (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED) {
		mutex_exit(&hwahcp->hwahc_mutex);
		if (dcp) {
			ndi_dc_freehdl(dcp);
		}

		return (EIO);
	}

	mutex_exit(&hwahcp->hwahc_mutex);

	ndi_devi_enter(ddi_get_parent(rh_dip), &prh_circ);
	ndi_devi_enter(rh_dip, &rh_circ);
	ndi_devi_enter(hwahcp->hwahc_dip, &circ);

	mutex_enter(&hwahcp->hwahc_mutex);

	switch (cmd) {
	case DEVCTL_AP_DISCONNECT:
		/* TODO: not supported now */
		rv = EIO;
		break;
	case DEVCTL_AP_UNCONFIGURE:
		/* TODO: not supported now */
		rv = EIO;
		break;
	case DEVCTL_AP_CONFIGURE:
		/* TODO: not supported now */
		rv = EIO;
		break;
	case DEVCTL_AP_GETSTATE:
		switch (hwahc_cfgadm_state(hwahcp, port)) {
		case HWAHC_CFGADM_DISCONNECTED:
			/* port previously 'disconnected' by cfgadm */
			ap_state.ap_rstate = AP_RSTATE_DISCONNECTED;
			ap_state.ap_ostate = AP_OSTATE_UNCONFIGURED;
			ap_state.ap_condition = AP_COND_OK;

			break;
		case HWAHC_CFGADM_UNCONFIGURED:
			ap_state.ap_rstate = AP_RSTATE_CONNECTED;
			ap_state.ap_ostate = AP_OSTATE_UNCONFIGURED;
			ap_state.ap_condition = AP_COND_OK;

			break;
		case HWAHC_CFGADM_CONFIGURED:
			ap_state.ap_rstate = AP_RSTATE_CONNECTED;
			ap_state.ap_ostate = AP_OSTATE_CONFIGURED;
			ap_state.ap_condition = AP_COND_OK;

			break;
		case HWAHC_CFGADM_STILL_REFERENCED:
			ap_state.ap_rstate = AP_RSTATE_EMPTY;
			ap_state.ap_ostate = AP_OSTATE_CONFIGURED;
			ap_state.ap_condition = AP_COND_UNUSABLE;

			break;
		case HWAHC_CFGADM_EMPTY:
		default:
			ap_state.ap_rstate = AP_RSTATE_EMPTY;
			ap_state.ap_ostate = AP_OSTATE_UNCONFIGURED;
			ap_state.ap_condition = AP_COND_OK;

			break;
		}

		ap_state.ap_last_change = (time_t)-1;
		ap_state.ap_error_code = 0;
		ap_state.ap_in_transition = 0;

		USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "DEVCTL_AP_GETSTATE: "
		    "ostate=0x%x, rstate=0x%x, condition=0x%x",
		    ap_state.ap_ostate,
		    ap_state.ap_rstate, ap_state.ap_condition);

		/* copy the return-AP-state information to the user space */
		if (ndi_dc_return_ap_state(&ap_state, dcp) != NDI_SUCCESS) {
			rv = EFAULT;
		}

		break;
	case DEVCTL_AP_CONTROL:
	{
		/*
		 * Generic devctl for hardware-specific functionality.
		 * For list of sub-commands see hubd_impl.h
		 */
		hubd_ioctl_data_t	ioc;	/* for 64 byte copies */

		/* copy user ioctl data in first */
#ifdef _MULTI_DATAMODEL
		if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
			hubd_ioctl_data_32_t ioc32;

			if (ddi_copyin((void *)arg, (void *)&ioc32,
			    sizeof (ioc32), mode) != 0) {
				rv = EFAULT;

				break;
			}
			ioc.cmd		= (uint_t)ioc32.cmd;
			ioc.port	= (uint_t)ioc32.port;
			ioc.get_size	= (uint_t)ioc32.get_size;
			ioc.buf		= (caddr_t)(uintptr_t)ioc32.buf;
			ioc.bufsiz	= (uint_t)ioc32.bufsiz;
			ioc.misc_arg	= (uint_t)ioc32.misc_arg;
		} else
#endif /* _MULTI_DATAMODEL */
		if (ddi_copyin((void *)arg, (void *)&ioc, sizeof (ioc),
		    mode) != 0) {
			rv = EFAULT;

			break;
		}

		USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "DEVCTL_AP_CONTROL: ioc: cmd=0x%x port=%d get_size=%d"
		    "\n\tbuf=0x%p, bufsiz=%d,  misc_arg=%d", ioc.cmd,
		    ioc.port, ioc.get_size, (void *) ioc.buf, ioc.bufsiz,
		    ioc.misc_arg);

		/*
		 * To avoid BE/LE and 32/64 issues, a get_size always
		 * returns a 32-bit number.
		 */
		if (ioc.get_size != 0 && ioc.bufsiz != (sizeof (uint32_t))) {
			rv = EINVAL;

			break;
		}

		switch (ioc.cmd) {
		case USB_DESCR_TYPE_DEV:
			msg = "DEVCTL_AP_CONTROL: GET_DEVICE_DESC";
			if (ioc.get_size) {
				/* uint32 so this works 32/64 */
				uint32_t size = sizeof (usb_dev_descr_t);

				if (ddi_copyout((void *)&size, ioc.buf,
				    ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: get_size copyout failed", msg);
					rv = EIO;

					break;
				}
			} else {	/* send out the actual descr */
				usb_dev_descr_t *dev_descrp;

				/* check child_dip */
				if ((child_dip = hwahc_get_child_dip(hwahcp,
				    ioc.port)) == NULL) {
					rv = EINVAL;

					break;
				}

				dev_descrp = usb_get_dev_descr(child_dip);
				if (ioc.bufsiz != sizeof (*dev_descrp)) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: bufsize passed (%d) != sizeof "
					    "usba_device_descr_t (%d)", msg,
					    ioc.bufsiz, dev_descrp->bLength);
					rv = EINVAL;

					break;
				}

				if (ddi_copyout((void *)dev_descrp,
				    ioc.buf, ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout failed.", msg);
					rv = EIO;

					break;
				}
			}
			break;
		case USB_DESCR_TYPE_CFG:
		{
			usba_device_t	*child_ud = NULL;
			uint32_t	idx = ioc.misc_arg;
			uint32_t	cfg_len = 0;

			if ((child_dip =
			    hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) {
				rv = EINVAL;

				break;
			}
			child_ud = usba_get_usba_device(child_dip);
			cfg_len = (uint32_t)child_ud->usb_cfg_array_len[idx];

			msg = "DEVCTL_AP_CONTROL: GET_CONFIG_DESC";
			if (ioc.get_size) {
				if (ddi_copyout((void *)&cfg_len, ioc.buf,
				    ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: get_size copyout failed", msg);
					rv = EIO;

					break;
				}
			} else {	/* send out the actual descr */
				uchar_t *cfg_descr =
				    child_ud->usb_cfg_array[idx];

				if (ioc.bufsiz != cfg_len) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: bufsize passed (%d) != size "
					    "of cfg_descr (%d)", msg,
					    ioc.bufsiz, cfg_len);
					rv = EINVAL;

					break;
				}

				if (ddi_copyout((void *)cfg_descr,
				    ioc.buf, ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout failed.", msg);
					rv = EIO;

					break;
				}
			}
			break;
		}
		case USB_DESCR_TYPE_STRING:
		{
			char		*str;
			uint32_t	size;
			usba_device_t	*usba_device;

			msg = "DEVCTL_AP_CONTROL: GET_STRING_DESCR";
			USB_DPRINTF_L4(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "%s: string request: %d", msg, ioc.misc_arg);

			/* recheck */
			if ((child_dip =
			    hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) {
				rv = EINVAL;

				break;
			}
			usba_device = usba_get_usba_device(child_dip);

			switch (ioc.misc_arg) {
			case HUBD_MFG_STR:
				str = usba_device->usb_mfg_str;

				break;
			case HUBD_PRODUCT_STR:
				str = usba_device->usb_product_str;

				break;
			case HUBD_SERIALNO_STR:
				str = usba_device->usb_serialno_str;

				break;
			case HUBD_CFG_DESCR_STR:
				mutex_enter(&usba_device->usb_mutex);
				str = usba_device->usb_cfg_str_descr[
				    usba_device->usb_active_cfg_ndx];
				mutex_exit(&usba_device->usb_mutex);

				break;
			default:
				USB_DPRINTF_L2(PRINT_MASK_CBOPS,
				    hwahcp->hwahc_log_handle,
				    "%s: Invalid string request", msg);
				rv = EINVAL;

				break;
			} /* end of switch */

			if (rv != 0) {

				break;
			}

			size = (str != NULL) ? strlen(str) + 1 : 0;
			if (ioc.get_size) {
				if (ddi_copyout((void *)&size, ioc.buf,
				    ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout of size failed.", msg);
					rv = EIO;

					break;
				}
			} else {
				if (size == 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: String is NULL", msg);
					rv = EINVAL;

					break;
				}

				if (ioc.bufsiz != size) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: string buf size wrong", msg);
					rv = EINVAL;

					break;
				}

				if (ddi_copyout((void *)str, ioc.buf,
				    ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout failed.", msg);
					rv = EIO;

					break;
				}
			}
			break;
		}
		case HUBD_GET_CFGADM_NAME:
		{
			uint32_t   name_len;
			const char *name;

			/* recheck */
			if ((child_dip =
			    hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) {
				rv = EINVAL;

				break;
			}
			name = ddi_node_name(child_dip);
			if (name == NULL) {
				name = "unsupported";
			}
			name_len = strlen(name) + 1;

			msg = "DEVCTL_AP_CONTROL: HUBD_GET_CFGADM_NAME";
			USB_DPRINTF_L4(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "%s: name=%s name_len=%d", msg, name, name_len);

			if (ioc.get_size) {
				if (ddi_copyout((void *)&name_len,
				    ioc.buf, ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout of size failed", msg);
					rv = EIO;

					break;
				}
			} else {
				if (ioc.bufsiz != name_len) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: string buf length wrong", msg);
					rv = EINVAL;

					break;
				}

				if (ddi_copyout((void *)name, ioc.buf,
				    ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout failed.", msg);
					rv = EIO;

					break;
				}
			}

			break;
		}

		/*
		 * Return the config index for the currently-configured
		 * configuration.
		 */
		case HUBD_GET_CURRENT_CONFIG:
		{
			uint_t		config_index;
			uint32_t	size = sizeof (config_index);
			usba_device_t	*usba_device;

			msg = "DEVCTL_AP_CONTROL: GET_CURRENT_CONFIG";
			USB_DPRINTF_L4(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle, "%s", msg);

			/*
			 * Return the config index for the configuration
			 * currently in use.
			 * Recheck if child_dip exists
			 */
			if ((child_dip =
			    hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) {
				rv = EINVAL;

				break;
			}

			usba_device = usba_get_usba_device(child_dip);
			mutex_enter(&usba_device->usb_mutex);
			config_index = usba_device->usb_active_cfg_ndx;
			mutex_exit(&usba_device->usb_mutex);

			if (ioc.get_size) {
				if (ddi_copyout((void *)&size,
				    ioc.buf, ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout of size failed.", msg);
					rv = EIO;

					break;
				}
			} else {
				if (ioc.bufsiz != size) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: buffer size wrong", msg);
					rv = EINVAL;

					break;
				}
				if (ddi_copyout((void *)&config_index,
				    ioc.buf, ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout failed", msg);
					rv = EIO;
				}
			}

			break;
		}
		case HUBD_GET_DEVICE_PATH:
		{
			char		*path;
			uint32_t	size;

			msg = "DEVCTL_AP_CONTROL: GET_DEVICE_PATH";
			USB_DPRINTF_L4(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle, "%s", msg);

			/* Recheck if child_dip exists */
			if ((child_dip =
			    hwahc_get_child_dip(hwahcp, ioc.port)) == NULL) {
				rv = EINVAL;

				break;
			}

			/* ddi_pathname doesn't supply /devices, so we do. */
			path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
			(void) strcpy(path, "/devices");
			(void) ddi_pathname(child_dip, path + strlen(path));
			size = strlen(path) + 1;

			USB_DPRINTF_L4(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "%s: device path=%s  size=%d", msg, path, size);

			if (ioc.get_size) {
				if (ddi_copyout((void *)&size,
				    ioc.buf, ioc.bufsiz, mode) != 0) {

					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout of size failed.", msg);
					rv = EIO;
				}
			} else {
				if (ioc.bufsiz != size) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: buffer wrong size.", msg);
					rv = EINVAL;
				} else if (ddi_copyout((void *)path,
				    ioc.buf, ioc.bufsiz, mode) != 0) {
					USB_DPRINTF_L2(PRINT_MASK_CBOPS,
					    hwahcp->hwahc_log_handle,
					    "%s: copyout failed.", msg);
					rv = EIO;
				}
			}
			kmem_free(path, MAXPATHLEN);

			break;
		}
		case HUBD_REFRESH_DEVDB:
			msg = "DEVCTL_AP_CONTROL: HUBD_REFRESH_DEVDB";
			USB_DPRINTF_L4(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle, "%s", msg);

			if ((rv = usba_devdb_refresh()) != USB_SUCCESS) {
				USB_DPRINTF_L2(PRINT_MASK_CBOPS,
				    hwahcp->hwahc_log_handle,
				    "%s: Failed: %d", msg, rv);
				rv = EIO;
			}

			break;
		default:
			rv = ENOTSUP;
		}	/* end switch */

		break;
	}

	default:
		rv = ENOTTY;
	}

	if (dcp) {
		ndi_dc_freehdl(dcp);
	}

	mutex_exit(&hwahcp->hwahc_mutex);

	ndi_devi_exit(hwahcp->hwahc_dip, circ);
	ndi_devi_exit(rh_dip, rh_circ);
	ndi_devi_exit(ddi_get_parent(rh_dip), prh_circ);

	return (rv);
}

/* update CHID for the hc driver, return 0 on success */
static int
hwahc_set_chid(hwahc_state_t *hwahcp, uint8_t *chid)
{
	wusb_hc_data_t	*hc_data = &hwahcp->hwahc_hc_data;

	ASSERT(!mutex_owned(&hc_data->hc_mutex));

	/* same as the old CHID, return success */
	if (memcmp(chid, hc_data->hc_chid, 16) == 0) {

		return (0);
	}

	/*
	 * stop hw from working before updating CHID
	 * this may not be necessary but so far we don't know
	 * other ways to do it safely
	 */
	if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) {
		/* use final_stop to fully stop the hwa */
		if (hwahc_hc_final_stop(hwahcp) != USB_SUCCESS) {

			return (EIO);
		}

		mutex_enter(&hc_data->hc_mutex);
		(void) memcpy(hc_data->hc_chid, chid, 16);
		mutex_exit(&hc_data->hc_mutex);

		/* restart the host */
		if (hwahc_hc_initial_start(hwahcp) != USB_SUCCESS) {

			return (EIO);
		}

		return (0);
	}

	/* hc is stopped or partially stopped, simply update */
	mutex_enter(&hc_data->hc_mutex);
	(void) memcpy(hc_data->hc_chid, chid, 16);
	mutex_exit(&hc_data->hc_mutex);

	return (0);
}

/*
 * wusbadm ioctl support
 */
/* ARGSUSED */
static int
hwahc_wusb_ioctl(hwahc_state_t *hwahcp, int cmd, intptr_t arg,
	int mode, cred_t *credp, int *rvalp)
{
	int		rv = 0;
	wusb_hc_data_t	*hc_data = &hwahcp->hwahc_hc_data;

	if (drv_priv(credp) != 0) {
		USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_wusb_ioctl: user must have SYS_DEVICE privilege,"
		    "cmd=%x", cmd);

		return (EPERM);
	}

	mutex_enter(&hwahcp->hwahc_mutex);

	switch (cmd) {
	case WUSB_HC_GET_DSTATE: /* Get device state: wusbadm list */
	{
		wusb_hc_get_dstate_t	state;
		usb_port_t		port = 0;

		if (ddi_copyin((void *)arg, (void *)&state, sizeof (state),
		    mode) != 0) {
			rv = EFAULT;

			break;
		}

		mutex_enter(&hc_data->hc_mutex);

		if (wusb_hc_is_dev_connected(hc_data, &state.cdid[0], &port)) {
			state.state = hc_data->hc_dev_infos[port]->wdev_state;
		} else {
			/* cdid not found */
			state.state = WUSB_STATE_UNCONNTED;
		}

		USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_wusb_ioctl: hc_data=%p, port = %d, state=%d",
		    (void *) hc_data, port, state.state);

		mutex_exit(&hc_data->hc_mutex);

		if (state.state == WUSB_STATE_CONFIGURED) {
			/* Get the bind device node name of this child */
			(void) memset(state.nodename, 0, MAX_USB_NODENAME);
			(void) snprintf(state.nodename, MAX_USB_NODENAME, "%s",
			    ddi_node_name(hwahc_get_child_dip(hwahcp, port)));

			USB_DPRINTF_L3(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_GET_DSTATE: nodename %s", state.nodename);
		}

		if (ddi_copyout((void *)&state, (void *)arg,
		    sizeof (state), mode) != 0) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_GET_DSTATE: copyout failed");
			rv = EIO;
		}

		break;
	}

	case WUSB_HC_GET_MAC_ADDR: /* Get host MAC addr */
	{
		uint8_t		mac_addr[6];

		bzero(mac_addr, 6);

		/*
		 * get UWB 48-bit mac address
		 * Section 8.6.2.2.
		 */
		if (uwb_get_mac_addr(hwahcp->hwahc_dip, mac_addr) !=
		    USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_GET_MAC_ADDR: get mac failed");
			rv = EIO;

			break;
		}

		if (ddi_copyout((void *)mac_addr, (void *)arg,
		    6, mode) != 0) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_GET_MAC_ADDR: copyout failed");
			rv = EIO;
		}

		break;
	}
	case WUSB_HC_ADD_CC:
	{
	/*
	 * add a new device CC to host's list: wusbadm associate
	 * Or, the application can pass in a fake CC with only CHID set
	 * to set the host's CHID.
	 */
		wusb_hc_cc_list_t	*cc_list;

		cc_list = kmem_zalloc(sizeof (wusb_hc_cc_list_t), KM_SLEEP);

		if (ddi_copyin((void *)arg, (void *)&cc_list->cc,
		    sizeof (wusb_cc_t), mode) != 0) {
			rv = EFAULT;
			kmem_free(cc_list, sizeof (wusb_hc_cc_list_t));

			break;
		}

		/* update CHID only when cc list is empty */
		mutex_enter(&hc_data->hc_mutex);
		if (hc_data->hc_cc_list == NULL) {
			mutex_exit(&hc_data->hc_mutex);

			if ((rv = hwahc_set_chid(hwahcp,
			    cc_list->cc.CHID)) != 0) {
				kmem_free(cc_list, sizeof (wusb_hc_cc_list_t));

				break;
			}

			mutex_enter(&hc_data->hc_mutex);
		} else {
			/* fail if the CHID in the new CC does not match */
			if (memcmp(cc_list->cc.CHID, hc_data->hc_chid,
			    16) != 0) {
				rv = EINVAL;
				kmem_free(cc_list, sizeof (wusb_hc_cc_list_t));

				mutex_exit(&hc_data->hc_mutex);
				break;
			}
		}
		cc_list->next = NULL;

		wusb_hc_add_cc(&hc_data->hc_cc_list, cc_list);
		mutex_exit(&hc_data->hc_mutex);

		break;
	}
	case WUSB_HC_REM_CC:
	{
		wusb_cc_t	cc;
		usb_port_t	port;

		if (ddi_copyin((void *)arg, (void *)&cc, sizeof (wusb_cc_t),
		    mode) != 0) {
			rv = EFAULT;

			break;
		}

		/* check if the CHID in the CC matches */
		if (memcmp(cc.CHID, hc_data->hc_chid, 16) != 0) {
			rv = EINVAL;

			break;
		}

		/* if the device is connected, disconnect it first */
		mutex_enter(&hc_data->hc_mutex);
		if (wusb_hc_is_dev_connected(hc_data, cc.CDID, &port)) {
			mutex_exit(&hc_data->hc_mutex);
			mutex_exit(&hwahcp->hwahc_mutex);
			/*
			 * clean up host side state, device not
			 * really disconnected. But user can safely remove
			 * the device now.
			 */
			(void) hwahc_destroy_child(hc_data->hc_dip, port);
			mutex_enter(&hwahcp->hwahc_mutex);
			mutex_enter(&hc_data->hc_mutex);
		}

		wusb_hc_rem_cc(&hc_data->hc_cc_list, &cc);
		mutex_exit(&hc_data->hc_mutex);

		break;
	}
	case WUSB_HC_SET_CHANNEL: /* for debug purpose */
	{
		uint8_t	channel;

		channel = (uint8_t)arg;

		if (hwahcp->hwahc_hc_data.hc_channel == channel) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_SET_CHANNEL ioctl: same as existing");

			break;
		}

		if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) {
			/* beacon is already started, stop it first */
			if (uwb_stop_beacon(hwahcp->hwahc_dip) != USB_SUCCESS) {
				USB_DPRINTF_L2(PRINT_MASK_CBOPS,
				    hwahcp->hwahc_log_handle,
				    "WUSB_HC_SET_CHANNEL ioctl: "
				    "stop beacon failed");
				rv = EIO;

				break;
			}
			/* update channel number */
			hwahcp->hwahc_hc_data.hc_channel = channel;
			/* restart beacon on the new channel */
			if (uwb_start_beacon(hwahcp->hwahc_dip,
			    channel) != USB_SUCCESS) {
				USB_DPRINTF_L2(PRINT_MASK_CBOPS,
				    hwahcp->hwahc_log_handle,
				    "WUSB_HC_SET_CHANNEL ioctl: "
				    "restart beacon failed");
				rv = EIO;
			}

			break;
		}

		/* beacon is not started, simply update channel number */
		hwahcp->hwahc_hc_data.hc_channel = channel;

		break;
	}
	case WUSB_HC_START:
	{
		int	flag;


		flag = (int)arg;

		if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_START ioctl: already started");

			break;
		}

		/*
		 * now we start hc only when the cc list is not NULL
		 * this limitation may be removed if we support
		 * numeric association, but CHID needs to be set
		 * in advance for the hc to work
		 */
		mutex_enter(&hc_data->hc_mutex);
		if (hc_data->hc_cc_list == NULL) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_START ioctl: cc list not inited");
			rv = EINVAL;

			mutex_exit(&hc_data->hc_mutex);

			break;
		}
		mutex_exit(&hc_data->hc_mutex);

		/* cannot be both */
		if ((flag & WUSB_HC_INITIAL_START) && (flag &
		    WUSB_HC_CHANNEL_START)) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_START ioctl: flag cannot coexist");
			rv = EINVAL;

			break;
		}

		/*
		 * init Mac layer 16-bit dev addr. it is important for
		 * authentication. It'd be better to let UWB provide
		 * this address.
		 */
		mutex_enter(&hc_data->hc_mutex);
		if (hc_data->hc_addr == 0) {
			uint16_t dev_addr = HWAHC_DEV_ADDR_BASE +
			    ddi_get_instance(hwahcp->hwahc_dip);

			mutex_exit(&hc_data->hc_mutex);
			/* set UWB 16-bit dev address */
			if (uwb_set_dev_addr(hwahcp->hwahc_dip,
			    dev_addr) != USB_SUCCESS) {
				USB_DPRINTF_L2(PRINT_MASK_CBOPS,
				    hwahcp->hwahc_log_handle,
				    "WUSB_HC_START ioctl: set dev addr failed");
				rv = EIO;

				break;
			}

			/* verify the dev addr is set correctly */
			if (uwb_get_dev_addr(hwahcp->hwahc_dip,
			    &dev_addr) != USB_SUCCESS) {
				USB_DPRINTF_L2(PRINT_MASK_CBOPS,
				    hwahcp->hwahc_log_handle,
				    "WUSB_HC_START ioctl: get dev addr failed");
				rv = EIO;

				break;
			}

			mutex_enter(&hc_data->hc_mutex);
			hc_data->hc_addr = dev_addr;
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "host dev addr = 0x%x", dev_addr);
		}
		mutex_exit(&hc_data->hc_mutex);

		/* start functions of wusb host */
		if ((flag & WUSB_HC_INITIAL_START) &&
		    (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED)) {
			if (hwahc_hc_initial_start(hwahcp) != USB_SUCCESS) {
				rv = EIO;
			}
		} else if ((flag & WUSB_HC_CHANNEL_START) &&
		    (hwahcp->hwahc_hw_state == HWAHC_HW_CH_STOPPED)) {
			if (hwahc_hc_channel_start(hwahcp) != USB_SUCCESS) {
				rv = EIO;
			}
		} else {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_START ioctl: unknown flag (%d) or "
			    "state (%d)", flag, hwahcp->hwahc_hw_state);
			rv = EINVAL;
		}

		break;
	}
	case WUSB_HC_STOP:
	{
		int	flag;

		flag = (int)arg;

		/* cannot be both */
		if ((flag & WUSB_HC_FINAL_STOP) && (flag &
		    WUSB_HC_CHANNEL_STOP)) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_STOP ioctl: flag cannot coexist");
			rv = EINVAL;

			break;
		}

		if (flag & WUSB_HC_FINAL_STOP) {
			if (hwahc_hc_final_stop(hwahcp) != USB_SUCCESS) {
				rv = EIO;
			}
		} else if (flag & WUSB_HC_CHANNEL_STOP) {
			if (hwahc_hc_channel_stop(hwahcp) != USB_SUCCESS) {
				rv = EIO;
			}
		} else {
			/* must be one of the STOP flag */
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_STOP ioctl: invalid flag = %d", flag);
			rv = EINVAL;
		}

		/* REM_ALL_CC flag is optional */
		if ((rv == 0) && (flag & WUSB_HC_REM_ALL_CC)) {
			mutex_enter(&hc_data->hc_mutex);
			if (hc_data->hc_cc_list) {
				wusb_hc_free_cc_list(hc_data->hc_cc_list);
				hc_data->hc_cc_list = NULL;
			}
			mutex_exit(&hc_data->hc_mutex);
		}

		break;
	}
	case WUSB_HC_GET_HSTATE:
	{
		int	state;

		if (hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) {
			state = WUSB_HC_DISCONNTED;
		} else {
			switch (hwahcp->hwahc_hw_state) {
			case HWAHC_HW_STOPPED:
				state = WUSB_HC_STOPPED;
				break;
			case HWAHC_HW_STARTED:
				state = WUSB_HC_STARTED;
				break;
			case HWAHC_HW_CH_STOPPED:
				/*
				 * app can mark the hwa as disabled
				 * for this state
				 */
				state = WUSB_HC_CH_STOPPED;
				break;
			}
		}

		if (ddi_copyout((void *)&state, (void *)arg,
		    sizeof (int), mode) != 0) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "WUSB_HC_GET_HSTATE: copyout failed");
			rv = EIO;
		}

		break;
	}
	default:
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_ioctl: unsupported command");

		rv = ENOTSUP;
	}
	mutex_exit(&hwahcp->hwahc_mutex);

	return (rv);
}

static int
hwahc_ioctl(dev_t dev, int cmd, intptr_t arg,
	int mode, cred_t *credp, int *rvalp)
{
	hwahc_state_t	*hwahcp;
	int		rval;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    HWAHC_MINOR_TO_INSTANCE(getminor(dev)))) == NULL) {

		return (ENXIO);
	}

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_ioctl: cmd=%x, arg=%lx, mode=%x, cred=%p, rval=%p dev=0x%lx",
	    cmd, arg, mode, (void *) credp, (void *) rvalp, dev);

	if (IS_DEVCTL(cmd)) {
		/* for cfgadm cmd support */
		rval = hwahc_cfgadm_ioctl(hwahcp, cmd, arg, mode, credp, rvalp);
	} else {
		/* for wusbadm cmd support */
		rval = hwahc_wusb_ioctl(hwahcp, cmd, arg, mode, credp, rvalp);
	}

	return (rval);
}

/* return the port number corresponding the child dip */
static usb_port_t
hwahc_child_dip2port(hwahc_state_t *hwahcp, dev_info_t *dip)
{
	usb_port_t	port;
	wusb_hc_data_t	*hc_data = &hwahcp->hwahc_hc_data;

	mutex_enter(&hc_data->hc_mutex);
	for (port = 1; port <= hc_data->hc_num_ports; port++) {
		if (hc_data->hc_children_dips[port] == dip) {

			break;
		}
	}
	ASSERT(port <= hc_data->hc_num_ports);
	mutex_exit(&hc_data->hc_mutex);

	return (port);
}

/*
 * child post attach/detach notification
 */
static void
hwahc_post_attach(hwahc_state_t *hwahcp, dev_info_t *rdip,
	struct attachspec *as)
{
	/* we don't need additional process for post-attach now */
	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_post_attach: rdip = 0x%p result = %d", (void *) rdip,
	    as->result);
}

static void
hwahc_post_detach(hwahc_state_t *hwahcp, dev_info_t *rdip,
	struct detachspec *as)
{
	/* we don't need additional process for post-detach now */
	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_post_detach: rdip = 0x%p result = %d", (void *) rdip,
	    as->result);
}

/*
 * bus ctl support.
 * To support different operations, such as a PreAttach preparation,
 * PostAttach operations. HWA only process the interested operations.
 * Other general ones are processed by usba_bus_ctl().
 */
static int
hwahc_bus_ctl(dev_info_t *dip, /* dip could be the parent */
	dev_info_t	*rdip, /* rdip is the dev node to be operated */
	ddi_ctl_enum_t	op,
	void		*arg,
	void		*result)
{
	usba_device_t *usba_device = usba_get_usba_device(rdip);
	dev_info_t *hubdip = usba_device->usb_root_hub_dip;
	hwahc_state_t *hwahcp;
	struct attachspec *as;
	struct detachspec *ds;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (DDI_FAILURE);
	}

	USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_bus_ctl:\n\t"
	    "dip = 0x%p, rdip = 0x%p, op = 0x%x, arg = 0x%p",
	    (void *) dip, (void *) rdip, op, (void *) arg);

	switch (op) {
	case DDI_CTLOPS_ATTACH:
		as = (struct attachspec *)arg;

		switch (as->when) {
		case DDI_PRE :
			/* nothing to do basically */
			USB_DPRINTF_L2(PRINT_MASK_EVENTS,
			    hwahcp->hwahc_log_handle,
			    "DDI_PRE DDI_CTLOPS_ATTACH");
			break;
		case DDI_POST :
			hwahc_post_attach(hwahcp, rdip,
			    (struct attachspec *)arg);
			break;
		}

		break;
	case DDI_CTLOPS_DETACH:
		ds = (struct detachspec *)arg;

		switch (ds->when) {
		case DDI_PRE :
			/* nothing to do basically */
			USB_DPRINTF_L2(PRINT_MASK_EVENTS,
			    hwahcp->hwahc_log_handle,
			    "DDI_PRE DDI_CTLOPS_DETACH");
			break;
		case DDI_POST :
			hwahc_post_detach(hwahcp, rdip,
			    (struct detachspec *)arg);
			break;
		}

		break;
	case DDI_CTLOPS_REPORTDEV: /* the workhorse behind ddi_report_dev */
	{
		char *name, compat_name[64];

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

		cmn_err(CE_CONT,
		    "?USB %x.%x %s (%s) operating wirelessly with "
		    "HWA device: "
		    "%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,
		    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, "?\t%s\n", name);
		}
		kmem_free(name, MAXNAMELEN);

		break;
	}
	default:
		/* pass to usba to handle */
		return (usba_bus_ctl(hubdip, rdip, op, arg, result));
	}

	return (DDI_SUCCESS);
}

/*
 * bus enumeration entry points
 *  Configures the named device(BUS_CONFIG_ONE) or all devices under
 *  the nexus(BUS_CONFIG_ALL). Drives devinfo state to DS_READY,i.e.device
 *  is fully operational.
 *
 *  This operation is driven from devfs(reading /devices), devctl, libdevinfo;
 *  or from within the kernel to attach a boot device or layered underlying
 *  driver.
 */
static int
hwahc_bus_config(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op,
	void *arg, dev_info_t **child)
{
	hwahc_state_t	*hwahcp;
	int		rval, circ;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (NDI_FAILURE);
	}

	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_bus_config: op=%d", op);

	if (hwahc_bus_config_debug) {
		flag |= NDI_DEVI_DEBUG;
	}

	ndi_devi_enter(dip, &circ);
	rval = ndi_busop_bus_config(dip, flag, op, arg, child, 0);
	ndi_devi_exit(dip, circ);

	return (rval);
}

/*
 * Unconfigures the named device or all devices under the nexus. The
 * devinfo state is not DS_READY anymore.
 * This operations is driven by modunload, devctl or DR branch removal or
 * rem_drv(1M).
 */
static int
hwahc_bus_unconfig(dev_info_t *dip, uint_t flag, ddi_bus_config_op_t op,
	void *arg)
{
	hwahc_state_t	*hwahcp;
	wusb_hc_data_t	*hc_data;
	dev_info_t	*cdip;
	usb_port_t	port;
	int		rval, circ;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (NDI_FAILURE);
	}

	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_bus_unconfig: op=%d", op);

	if (hwahc_bus_config_debug) {
		flag |= NDI_DEVI_DEBUG;
	}

	if ((op == BUS_UNCONFIG_ALL) && (flag & NDI_AUTODETACH) == 0) {
		flag |= NDI_DEVI_REMOVE;
	}

	/* serialize access */
	ndi_devi_enter(dip, &circ);

	/* unconfig children, detach them */
	rval = ndi_busop_bus_unconfig(dip, flag, op, arg);

	/* logically zap children's list */
	hc_data = &hwahcp->hwahc_hc_data;

	mutex_enter(&hc_data->hc_mutex);
	for (port = 1; port <= hc_data->hc_num_ports; port++) {
		hc_data->hc_children_state[port] |= WUSB_CHILD_ZAP;
	}
	mutex_exit(&hc_data->hc_mutex);

	/* fill in what's left */
	for (cdip = ddi_get_child(dip); cdip;
	    cdip = ddi_get_next_sibling(cdip)) {
		usba_device_t *usba_device = usba_get_usba_device(cdip);

		if (usba_device == NULL) {

			continue;
		}
		mutex_enter(&hc_data->hc_mutex);
		port = usba_device->usb_port;
		hc_data->hc_children_dips[port] = cdip;
		hc_data->hc_children_state[port] &= ~WUSB_CHILD_ZAP;
		mutex_exit(&hc_data->hc_mutex);
	}

	/* physically zap the children we didn't find */
	mutex_enter(&hc_data->hc_mutex);
	for (port = 1; port <= hc_data->hc_num_ports; port++) {
		if (hc_data->hc_children_state[port] & WUSB_CHILD_ZAP) {
			wusb_dev_info_t		*dev_info;
			wusb_secrt_data_t	*csecrt_data;
			usba_device_t		*child_ud;

			USB_DPRINTF_L3(PRINT_MASK_EVENTS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_bus_unconfig: physically zap port %d", port);

			child_ud = hc_data->hc_usba_devices[port];
			mutex_exit(&hc_data->hc_mutex);
			/* zap the dip and usba_device structure as well */
			usba_free_usba_device(child_ud);
			mutex_enter(&hc_data->hc_mutex);
			hc_data->hc_usba_devices[port] = NULL;

			/* dip freed in usba_destroy_child_devi */
			hc_data->hc_children_dips[port] = NULL;
			hc_data->hc_children_state[port] &= ~WUSB_CHILD_ZAP;

			/* free hc_dev_infos[port] */
			dev_info = hc_data->hc_dev_infos[port];
			if (dev_info == NULL) {

				continue;
			}

			/* stop the device's trust timer before deallocate it */
			hwahc_stop_trust_timer(dev_info);

			if (dev_info->wdev_secrt_data.secrt_encry_descr) {
				csecrt_data = &dev_info->wdev_secrt_data;
				kmem_free(csecrt_data->secrt_encry_descr,
				    sizeof (usb_encryption_descr_t) *
				    csecrt_data->secrt_n_encry);
			}
			if (dev_info->wdev_uwb_descr) {
				kmem_free(dev_info->wdev_uwb_descr,
				    sizeof (usb_uwb_cap_descr_t));
			}
			kmem_free(dev_info, sizeof (wusb_dev_info_t));
			hc_data->hc_dev_infos[port] = NULL;
		}
	}
	mutex_exit(&hc_data->hc_mutex);

	ndi_devi_exit(dip, circ);

	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_bus_unconfig: rval=%d", rval);

	return (rval);
}

/*
 * busctl event support
 *
 * Called by ndi_busop_get_eventcookie(). Return a event cookie
 * associated with one event name.
 * The eventname should be the one we defined in hwahc_ndi_event_defs
 */
static int
hwahc_busop_get_eventcookie(dev_info_t *dip,
	dev_info_t	*rdip,
	char		*eventname,
	ddi_eventcookie_t *cookie)
{
	hwahc_state_t	*hwahcp;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (NDI_FAILURE);
	}

	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_busop_get_eventcookie: dip=0x%p, rdip=0x%p, "
	    "event=%s", (void *)dip, (void *)rdip, eventname);
	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "(dip=%s%d, rdip=%s%d)",
	    ddi_driver_name(dip), ddi_get_instance(dip),
	    ddi_driver_name(rdip), ddi_get_instance(rdip));

	/* return event cookie, iblock cookie, and level */
	return (ndi_event_retrieve_cookie(hwahcp->hwahc_ndi_event_hdl,
	    rdip, eventname, cookie, NDI_EVENT_NOPASS));
}

/*
 * Add event handler for a given event cookie
 */
static int
hwahc_busop_add_eventcall(dev_info_t *dip,
	dev_info_t	*rdip,
	ddi_eventcookie_t cookie,
	void		(*callback)(dev_info_t *dip,
			ddi_eventcookie_t cookie, void *arg,
			void *bus_impldata),
	void *arg, ddi_callback_id_t *cb_id)
{
	hwahc_state_t	*hwahcp;
	usb_port_t	port;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (NDI_FAILURE);
	}

	port = hwahc_child_dip2port(hwahcp, rdip);

	mutex_enter(&hwahcp->hwahc_mutex);

	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_busop_add_eventcall: dip=0x%p, rdip=0x%p "
	    "cookie=0x%p, cb=0x%p, arg=0x%p",
	    (void *)dip, (void *)rdip, (void *)cookie, (void *)callback, arg);
	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "(dip=%s%d, rdip=%s%d, event=%s)",
	    ddi_driver_name(dip), ddi_get_instance(dip),
	    ddi_driver_name(rdip), ddi_get_instance(rdip),
	    ndi_event_cookie_to_name(hwahcp->hwahc_ndi_event_hdl, cookie));

	/* Set flag on children registering events */
	switch (ndi_event_cookie_to_tag(hwahcp->hwahc_ndi_event_hdl, cookie)) {
	case USBA_EVENT_TAG_HOT_REMOVAL:
		hwahcp->hwahc_child_events[port] |=
		    HWAHC_CHILD_EVENT_DISCONNECT;

		break;
	case USBA_EVENT_TAG_PRE_SUSPEND:
		hwahcp->hwahc_child_events[port] |=
		    HWAHC_CHILD_EVENT_PRESUSPEND;

		break;
	default:

		break;
	}

	mutex_exit(&hwahcp->hwahc_mutex);

	/* add callback to our event set */
	return (ndi_event_add_callback(hwahcp->hwahc_ndi_event_hdl,
	    rdip, cookie, callback, arg, NDI_SLEEP, cb_id));

}


/*
 * Remove a callback previously added by bus_add_eventcall()
 */
static int
hwahc_busop_remove_eventcall(dev_info_t *dip, ddi_callback_id_t cb_id)
{
	hwahc_state_t	*hwahcp;
	ndi_event_callbacks_t *id = (ndi_event_callbacks_t *)cb_id;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (NDI_FAILURE);
	}

	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_busop_remove_eventcall: dip=0x%p, rdip=0x%p "
	    "cookie=0x%p", (void *)dip, (void *) id->ndi_evtcb_dip,
	    (void *)id->ndi_evtcb_cookie);
	USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "(dip=%s%d, rdip=%s%d, event=%s)",
	    ddi_driver_name(dip), ddi_get_instance(dip),
	    ddi_driver_name(id->ndi_evtcb_dip),
	    ddi_get_instance(id->ndi_evtcb_dip),
	    ndi_event_cookie_to_name(hwahcp->hwahc_ndi_event_hdl,
	    id->ndi_evtcb_cookie));

	/* remove event registration from our event set */
	return (ndi_event_remove_callback(hwahcp->hwahc_ndi_event_hdl, cb_id));
}

/*
 * hwahc_post_event
 *	post event to a single child on the port depending on the type, i.e.
 *	to invoke the child's registered callback.
 */
static void
hwahc_post_event(hwahc_state_t *hwahcp, usb_port_t port, usba_event_t type)
{
	int		rval;
	dev_info_t	*dip;
	usba_device_t	*usba_device;
	ddi_eventcookie_t cookie, rm_cookie, suspend_cookie;
	wusb_hc_data_t	*hc_data = &hwahcp->hwahc_hc_data;

	USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_post_event: port=%d event=%s", port,
	    ndi_event_tag_to_name(hwahcp->hwahc_ndi_event_hdl, type));

	cookie = ndi_event_tag_to_cookie(hwahcp->hwahc_ndi_event_hdl, type);
	rm_cookie = ndi_event_tag_to_cookie(hwahcp->hwahc_ndi_event_hdl,
	    USBA_EVENT_TAG_HOT_REMOVAL);
	suspend_cookie = ndi_event_tag_to_cookie(hwahcp->hwahc_ndi_event_hdl,
	    USBA_EVENT_TAG_PRE_SUSPEND);

	/*
	 * Hotplug daemon may be attaching a driver that may be registering
	 * event callbacks. So it already has got the device tree lock and
	 * event handle mutex. So to prevent a deadlock while posting events,
	 * we grab and release the locks in the same order.
	 */
	mutex_enter(&hwahcp->hwahc_mutex);
	dip = hwahcp->hwahc_hc_data.hc_children_dips[port];
	usba_device = hwahcp->hwahc_hc_data.hc_usba_devices[port];
	mutex_exit((&hwahcp->hwahc_mutex));

	switch (type) {
	case USBA_EVENT_TAG_HOT_REMOVAL:
		/* stop this device's timer to prevent its further process */
		mutex_enter(&hc_data->hc_mutex);

		hwahc_stop_trust_timer(hc_data->hc_dev_infos[port]);
		mutex_exit(&hc_data->hc_mutex);

		/* Clear the registered event flag */
		mutex_enter(&hwahcp->hwahc_mutex);
		hwahcp->hwahc_child_events[port] &=
		    ~HWAHC_CHILD_EVENT_DISCONNECT;
		mutex_exit(&hwahcp->hwahc_mutex);

		(void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl,
		    dip, cookie, NULL);
		usba_persistent_pipe_close(usba_device);

		/*
		 * Mark the dip for deletion only after the driver has
		 * seen the disconnect event to prevent cleanup thread
		 * from stepping in between.
		 */
#ifndef __lock_lint
		mutex_enter(&DEVI(dip)->devi_lock);
		DEVI_SET_DEVICE_REMOVED(dip);
		mutex_exit(&DEVI(dip)->devi_lock);
#endif

		break;
	case USBA_EVENT_TAG_PRE_SUSPEND:
		mutex_enter(&hwahcp->hwahc_mutex);
		hwahcp->hwahc_child_events[port] &=
		    ~HWAHC_CHILD_EVENT_PRESUSPEND;
		mutex_exit(&hwahcp->hwahc_mutex);

		(void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl,
		    dip, cookie, NULL);
		/*
		 * persistent pipe close for this event is taken care by the
		 * caller after verfying that all children can suspend
		 */

		break;
	case USBA_EVENT_TAG_HOT_INSERTION:
		/*
		 * Check if this child has missed the disconnect event before
		 * it registered for event callbacks
		 */
		mutex_enter(&hwahcp->hwahc_mutex);
		if (hwahcp->hwahc_child_events[port] &
		    HWAHC_CHILD_EVENT_DISCONNECT) {
			/* clear the flag and post disconnect event */
			hwahcp->hwahc_child_events[port] &=
			    ~HWAHC_CHILD_EVENT_DISCONNECT;
			mutex_exit(&hwahcp->hwahc_mutex);

			(void) ndi_event_do_callback(
			    hwahcp->hwahc_ndi_event_hdl,
			    dip, rm_cookie, NULL);
			usba_persistent_pipe_close(usba_device);
			mutex_enter(&hwahcp->hwahc_mutex);
		}
		mutex_exit(&hwahcp->hwahc_mutex);

		/*
		 * Mark the dip as reinserted to prevent cleanup thread
		 * from stepping in.
		 */
#ifndef __lock_lint
		mutex_enter(&(DEVI(dip)->devi_lock));
		DEVI_SET_DEVICE_REINSERTED(dip);
		mutex_exit(&(DEVI(dip)->devi_lock));
#endif

		rval = usba_persistent_pipe_open(usba_device);
		if (rval != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_EVENTS,
			    hwahcp->hwahc_log_handle,
			    "failed to reopen all pipes on reconnect");
		}

		(void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl,
		    dip, cookie, NULL);

		/*
		 * We might see a connect event only if hotplug thread for
		 * disconnect event don't run in time.
		 * Set the flag again, so we don't miss posting a
		 * disconnect event.
		 */
		mutex_enter(&hwahcp->hwahc_mutex);
		hwahcp->hwahc_child_events[port] |=
		    HWAHC_CHILD_EVENT_DISCONNECT;
		mutex_exit(&hwahcp->hwahc_mutex);

		break;
	case USBA_EVENT_TAG_POST_RESUME:
		/*
		 * Check if this child has missed the pre-suspend event before
		 * it registered for event callbacks
		 */
		mutex_enter(&hwahcp->hwahc_mutex);
		if (hwahcp->hwahc_child_events[port] &
		    HWAHC_CHILD_EVENT_PRESUSPEND) {
			/* clear the flag and post pre_suspend event */
			hwahcp->hwahc_child_events[port] &=
			    ~HWAHC_CHILD_EVENT_PRESUSPEND;
			mutex_exit(&hwahcp->hwahc_mutex);
			(void) ndi_event_do_callback(
			    hwahcp->hwahc_ndi_event_hdl,
			    dip, suspend_cookie, NULL);
			mutex_enter(&hwahcp->hwahc_mutex);
		}
		mutex_exit(&hwahcp->hwahc_mutex);

		mutex_enter(&usba_device->usb_mutex);
		usba_device->usb_no_cpr = 0;
		mutex_exit(&usba_device->usb_mutex);

		/*
		 * Since the pipe has already been opened by whub
		 * at DDI_RESUME time, there is no need for a
		 * persistent pipe open
		 */
		(void) ndi_event_do_callback(hwahcp->hwahc_ndi_event_hdl,
		    dip, cookie, NULL);

		/*
		 * Set the flag again, so we don't miss posting a
		 * pre-suspend event. This enforces a tighter
		 * dev_state model.
		 */
		mutex_enter(&hwahcp->hwahc_mutex);
		hwahcp->hwahc_child_events[port] |=
		    HWAHC_CHILD_EVENT_PRESUSPEND;
		mutex_exit(&hwahcp->hwahc_mutex);
		break;
	}
}

/*
 * hwahc_run_callbacks:
 *	Send an event to all children
 */
static void
hwahc_run_callbacks(hwahc_state_t *hwahcp, usba_event_t type)
{
	usb_port_t	port;
	wusb_hc_data_t	*hc_data = &hwahcp->hwahc_hc_data;

	USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_run_callbacks:");

	mutex_enter(&hc_data->hc_mutex);
	for (port = 1; port <= hc_data->hc_num_ports; port++) {
		if (hc_data->hc_children_dips[port]) {
			mutex_exit(&hc_data->hc_mutex);
			hwahc_post_event(hwahcp, port, type);
			mutex_enter(&hc_data->hc_mutex);
		}
	}
	mutex_exit(&hc_data->hc_mutex);
}

/*
 * hwahc_disconnect_event_cb:
 *	Called when hwa device hotplug-removed.
 *		Close pipes
 *		Post event to child
 *		Set state to DISCONNECTED
 */
static int
hwahc_disconnect_event_cb(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	hwahc_state_t	*hwahcp;
	int		circ;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) {

		return (USB_FAILURE);
	}

	USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_disconnect_event_cb: dip = 0x%p", (void *)dip);

	ndi_devi_enter(dip, &circ);

	mutex_enter(&hwahcp->hwahc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_disconnect_event_cb: devstate= %d hw-state=%d",
	    hwahcp->hwahc_dev_state, hwahcp->hwahc_hw_state);
	switch (hwahcp->hwahc_dev_state) {
	case USB_DEV_ONLINE:
	case USB_DEV_PWRED_DOWN:
		hwahcp->hwahc_dev_state = USB_DEV_DISCONNECTED;

		if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) {
			mutex_exit(&hwahcp->hwahc_mutex);
			wusb_wa_stop_nep(&hwahcp->hwahc_wa_data);
			mutex_enter(&hwahcp->hwahc_mutex);
			hwahc_stop_result_thread(hwahcp);
			hwahc_drain_notif_queue(hwahcp);
		}
		/* FALLTHROUGH */
	case USB_DEV_SUSPENDED:
		/* remain in this state */
		mutex_exit(&hwahcp->hwahc_mutex);
		hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_HOT_REMOVAL);
		mutex_enter(&hwahcp->hwahc_mutex);

		hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE;

		break;
	case USB_DEV_DISCONNECTED:
		USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
		    "hwahc_disconnect_event_cb: already disconnected");

		break;
	default:
		USB_DPRINTF_L2(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
		    "hwahc_disconnect_event_cb: illegal devstate=%d",
		    hwahcp->hwahc_dev_state);

		break;
	}
	mutex_exit(&hwahcp->hwahc_mutex);

	ndi_devi_exit(dip, circ);

	return (USB_SUCCESS);
}


/*
 * hwahc_reconnect_event_cb:
 *	Called with device hotplug-inserted
 *		Restore state
 */
static int
hwahc_reconnect_event_cb(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	hwahc_state_t	*hwahcp;
	int		circ;


	if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) {

		return (USB_FAILURE);
	}

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_reconnect_event_cb: dip = 0x%p", (void *)dip);

	ndi_devi_enter(dip, &circ);
	hwahc_restore_device_state(dip, hwahcp);
	ndi_devi_exit(dip, circ);

	return (USB_SUCCESS);
}


/*
 * hwahc_pre_suspend_event_cb:
 *	Called before HWA device suspend
 */
static int
hwahc_pre_suspend_event_cb(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	hwahc_state_t	*hwahcp;
	int		circ;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) {

		return (USB_FAILURE);
	}

	USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_pre_suspend_event_cb: dip = 0x%p", (void *)dip);

	mutex_enter(&hwahcp->hwahc_mutex);
	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_pre_suspend_event_cb: start, hw state = %d, softstate = %d",
	    hwahcp->hwahc_hw_state, hwahcp->hwahc_hc_soft_state);
	mutex_exit(&hwahcp->hwahc_mutex);

	/* keep PM out till we see a cpr resume */
	(void) hwahc_pm_busy_component(hwahcp);
	(void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR);

	ndi_devi_enter(dip, &circ);
	hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_PRE_SUSPEND);
	ndi_devi_exit(dip, circ);

	/*
	 * rc driver is always suspended first, that fails the hc suspend.
	 * need to suspend hc before rc is suspended, so move the suspend
	 * operations here
	 */
	mutex_enter(&hwahcp->hwahc_mutex);
	if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) {
		USB_DPRINTF_L3(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
		    "hwahc_pre_suspend_event_cb: dev_state = %d",
		    hwahcp->hwahc_dev_state);
		mutex_exit(&hwahcp->hwahc_mutex);

		return (USB_SUCCESS);
	}

	if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) {
		/*
		 * notify children the host is going to stop
		 */
		(void) hwahc_hc_channel_suspend(hwahcp);
	}

	/* stop the hc from functioning */
	if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) {
		mutex_exit(&hwahcp->hwahc_mutex);
		wusb_wa_stop_nep(&hwahcp->hwahc_wa_data);

		mutex_enter(&hwahcp->hwahc_mutex);
		hwahc_stop_result_thread(hwahcp);
		hwahc_drain_notif_queue(hwahcp);

		mutex_exit(&hwahcp->hwahc_mutex);
		(void) wusb_wa_disable(&hwahcp->hwahc_wa_data,
		    hwahcp->hwahc_default_pipe);
		mutex_enter(&hwahcp->hwahc_mutex);

	}

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_pre_suspend_event_cb: end, devstate=%d "
	    "hwstate=%d softstate = %d",
	    hwahcp->hwahc_dev_state, hwahcp->hwahc_hw_state,
	    hwahcp->hwahc_hc_soft_state);

	mutex_exit(&hwahcp->hwahc_mutex);

	return (USB_SUCCESS);
}


/*
 * hwahc_post_resume_event_cb:
 *	Call after HWA device resume
 */
static int
hwahc_post_resume_event_cb(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	hwahc_state_t	*hwahcp;
	int		circ;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep, instance)) == NULL) {

		return (USB_FAILURE);
	}

	USB_DPRINTF_L4(PRINT_MASK_EVENTS, hwahcp->hwahc_log_handle,
	    "hwahc_post_resume_event_cb: dip = 0x%p", (void *)dip);

	mutex_enter(&hwahcp->hwahc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_post_resume_event_cb: start, hw state = %d, softstate = %d",
	    hwahcp->hwahc_hw_state, hwahcp->hwahc_hc_soft_state);
	mutex_exit(&hwahcp->hwahc_mutex);

	ndi_devi_enter(dip, &circ);

	/* need to place hc restore here to make sure rc has resumed */
	hwahc_restore_device_state(dip, hwahcp);

	hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_POST_RESUME);

	ndi_devi_exit(dip, circ);

	/* enable PM */
	(void) hwahc_pm_idle_component(hwahcp);

	return (USB_SUCCESS);
}


/*
 * hwahc_restore_device_state:
 *	Called during hotplug-reconnect and resume.
 *		re-enable power management
 *		Verify the device is the same as before the disconnect/suspend.
 *		Restore device state
 *		Thaw any IO which was frozen.
 *		Quiesce device.  (Other routines will activate if thawed IO.)
 *		Set device online.
 *		Leave device disconnected if there are problems.
 */
static void
hwahc_restore_device_state(dev_info_t *dip, hwahc_state_t *hwahcp)
{
	int	rval;
	int	old_hw_state;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_restore_device_state: dip = 0x%p", (void *)dip);

	mutex_enter(&hwahcp->hwahc_mutex);

	ASSERT((hwahcp->hwahc_dev_state == USB_DEV_DISCONNECTED) ||
	    (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED));

	/* raise power */
	mutex_exit(&hwahcp->hwahc_mutex);
	hwahc_pm_busy_component(hwahcp);
	(void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR);

	/*
	 * Check if we are talking to the same device
	 * Some host controllers may see all devices disconnected
	 * when they just resume. This may be a cause of not
	 * finding the same device.
	 *
	 * Some HWA devices need to download firmware when it is
	 * powered on. Before the firmware is downloaded, the device
	 * will look differently.
	 */
	if (usb_check_same_device(dip, hwahcp->hwahc_log_handle,
	    USB_LOG_L0, PRINT_MASK_ALL,
	    USB_CHK_BASIC | USB_CHK_SERIAL | USB_CHK_VIDPID, NULL) !=
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: not the same device");
		/* change the device state from suspended to disconnected */
		mutex_enter(&hwahcp->hwahc_mutex);
		hwahcp->hwahc_dev_state = USB_DEV_DISCONNECTED;
		hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_ERROR_STATE;
		mutex_exit(&hwahcp->hwahc_mutex);
		hwahc_pm_idle_component(hwahcp);

		return;
	}

	USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_restore_device_state: Hwahc has been reconnected but"
	    " data may have been lost");

	mutex_enter(&hwahcp->hwahc_mutex);

	/* reinitialize the hw */
	hwahcp->hwahc_dev_state = USB_DEV_ONLINE;
	hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE;

	if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) {
		mutex_exit(&hwahcp->hwahc_mutex);
		/* no need to start hc */
		hwahc_pm_idle_component(hwahcp);
		USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: stopped hwa");

		return;
	}


	rval = wusb_hc_set_cluster_id(&hwahcp->hwahc_hc_data,
	    hwahcp->hwahc_hc_data.hc_cluster_id);

	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: set cluster id fails");

		goto err;
	}

	if (hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) {
		old_hw_state = hwahcp->hwahc_hw_state;
		hwahcp->hwahc_hw_state = HWAHC_HW_CH_STOPPED;
		rval = hwahc_hc_channel_start(hwahcp);
		if (rval != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    hwahcp->hwahc_log_handle,
			    "hwahc_restore_device_state: start hc fails");
			hwahcp->hwahc_hw_state = old_hw_state;

			goto err;
		}
		hwahcp->hwahc_hw_state = old_hw_state;
	}

	rval = wusb_hc_set_num_dnts(&hwahcp->hwahc_hc_data,
	    HWAHC_DEFAULT_DNTS_INTERVAL, HWAHC_DEFAULT_DNTS_SLOT_NUM);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: set num dnts fails");

		goto err;
	}

	/* set default GTK */
	rval = wusb_hc_set_gtk(&hwahcp->hwahc_hc_data, dft_gtk, dft_gtkid);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: set gtk fails");

		goto err;
	}

	mutex_exit(&hwahcp->hwahc_mutex);

	rval = wusb_wa_enable(&hwahcp->hwahc_wa_data,
	    hwahcp->hwahc_default_pipe);
	mutex_enter(&hwahcp->hwahc_mutex);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: enable wa fails");

		goto err;
	}

	/*
	 * This is a workaround, sometimes the ioctl and reconnect will
	 * happen at the sametime, so the ioctl will start nep which makes
	 * the below sart nep fail. Need more work to do to avoid such
	 * issues
	 */
	(void) wusb_wa_stop_nep(&hwahcp->hwahc_wa_data);

	rval = wusb_wa_start_nep(&hwahcp->hwahc_wa_data, USB_FLAGS_SLEEP);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: start notifep fails rval =%d",
		    rval);
		mutex_exit(&hwahcp->hwahc_mutex);
		(void) wusb_wa_disable(&hwahcp->hwahc_wa_data,
		    hwahcp->hwahc_default_pipe);
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* Handle transfer results on bulk-in ep */
	rval = hwahc_start_result_thread(hwahcp);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_restore_device_state: start result thread fails");
		mutex_exit(&hwahcp->hwahc_mutex);
		wusb_wa_stop_nep(&hwahcp->hwahc_wa_data);
		(void) wusb_wa_disable(&hwahcp->hwahc_wa_data,
		    hwahcp->hwahc_default_pipe);
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* if the device had remote wakeup earlier, enable it again */
	if (hwahcp->hwahc_pm && hwahcp->hwahc_pm->hwahc_wakeup_enabled) {
		mutex_exit(&hwahcp->hwahc_mutex);
		(void) usb_handle_remote_wakeup(hwahcp->hwahc_dip,
		    USB_REMOTE_WAKEUP_ENABLE);
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	hwahcp->hwahc_hw_state = HWAHC_HW_STARTED;
	hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_OPERATIONAL_STATE;
	mutex_exit(&hwahcp->hwahc_mutex);
	hwahc_pm_idle_component(hwahcp);

	return;

err:
	hwahcp->hwahc_hw_state = HWAHC_HW_STOPPED;
	mutex_exit(&hwahcp->hwahc_mutex);
	hwahc_pm_idle_component(hwahcp);
}


/*
 * hwahc_cpr_suspend:
 *	Clean up device.
 *	Wait for any IO to finish, then close pipes.
 *	Quiesce device.
 * due to the dependency on hwarc, the actual suspend operations are
 * moved to hwahc_pre_suspend_event_cb function.
 */
static int
hwahc_cpr_suspend(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	hwahc_state_t	*hwahcp = ddi_get_soft_state(hwahc_statep, instance);

	if (hwahcp == NULL) {

		return (USB_FAILURE);
	}

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_cpr_suspend: start");

	mutex_enter(&hwahcp->hwahc_mutex);

	/* Don't suspend if the device is open. */
	if (hwahcp->hwahc_open_count > 0) {
		USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_cpr_suspend: Device is open, cannot suspend");
		mutex_exit(&hwahcp->hwahc_mutex);

		return (USB_FAILURE);
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	/* raise power */
	hwahc_pm_busy_component(hwahcp);
	(void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR);

	mutex_enter(&hwahcp->hwahc_mutex);
	switch (hwahcp->hwahc_dev_state) {
	case USB_DEV_ONLINE:
	/* real suspend operations put in pre_suspend function */
		/* FALLTHRU */
	case USB_DEV_DISCONNECTED:
	case USB_DEV_PWRED_DOWN:
		hwahcp->hwahc_dev_state = USB_DEV_SUSPENDED;
		hwahcp->hwahc_hw_state = HWAHC_HW_CH_SUSPEND;

		break;
	case USB_DEV_SUSPENDED:
	default:
		USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_cpr_suspend: illegal dev state=%d",
		    hwahcp->hwahc_dev_state);

		break;
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	hwahc_pm_idle_component(hwahcp);

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_cpr_suspend: end");

	return (USB_SUCCESS);
}


/*
 * hwahc_cpr_resume:
 *
 *	hwahc_restore_device_state marks success by putting device back online
 */
static int
hwahc_cpr_resume(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	hwahc_state_t	*hwahcp = ddi_get_soft_state(hwahc_statep, instance);

	if (hwahcp == NULL) {

		return (USB_FAILURE);
	}

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_cpr_resume: hw state = %d, softstate = %d",
	    hwahcp->hwahc_hw_state, hwahcp->hwahc_hc_soft_state);

	/*
	 * rc is always resumed after hc. restoring hc before rc would fail.
	 * move the restoring operations to hwahc_post_resume_event_cb.
	 */

	return (USB_SUCCESS);
}

/*
 * hwahc_create_pm_components:
 *	Create power managements components
 */
static void
hwahc_create_pm_components(dev_info_t *dip, hwahc_state_t *hwahcp)
{
	hwahc_power_t	*hwahcpm;
	uint_t		pwr_states;

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_create_pm_components: Begin");

	/* Allocate the state structure */
	hwahcpm = kmem_zalloc(sizeof (hwahc_power_t), KM_SLEEP);
	hwahcp->hwahc_pm = hwahcpm;
	hwahcpm->hwahc_state = hwahcp;
	hwahcpm->hwahc_pm_capabilities = 0;
	hwahcpm->hwahc_current_power = USB_DEV_OS_FULL_PWR;

	if (usb_create_pm_components(dip, &pwr_states) == USB_SUCCESS) {
		USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_create_pm_components: created PM components");

		if (usb_handle_remote_wakeup(dip,
		    USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) {
			hwahcpm->hwahc_wakeup_enabled = 1;
		}
		hwahcpm->hwahc_pwr_states = (uint8_t)pwr_states;
		/* make device busy till end of attach */
		hwahc_pm_busy_component(hwahcp);
		(void) pm_raise_power(hwahcp->hwahc_dip, 0,
		    USB_DEV_OS_FULL_PWR);
	} else {
		USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_create_pm_components: failed");
	}

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_create_pm_components: End");
}

/*
 * hwahc_destroy_pm_components:
 *	Shut down and destroy power management and remote wakeup functionality
 */
static void
hwahc_destroy_pm_components(hwahc_state_t *hwahcp)
{
	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_destroy_pm_components: Begin");

	ASSERT(!mutex_owned(&hwahcp->hwahc_mutex));

	mutex_enter(&hwahcp->hwahc_mutex);
	if (hwahcp->hwahc_pm && (hwahcp->hwahc_dev_state !=
	    USB_DEV_DISCONNECTED)) {
		mutex_exit(&hwahcp->hwahc_mutex);
		hwahc_pm_busy_component(hwahcp);
		mutex_enter(&hwahcp->hwahc_mutex);

		if (hwahcp->hwahc_pm->hwahc_wakeup_enabled) {
			int rval;

			mutex_exit(&hwahcp->hwahc_mutex);
			(void) pm_raise_power(hwahcp->hwahc_dip, 0,
			    USB_DEV_OS_FULL_PWR);

			if ((rval = usb_handle_remote_wakeup(
			    hwahcp->hwahc_dip,
			    USB_REMOTE_WAKEUP_DISABLE)) !=
			    USB_SUCCESS) {
				USB_DPRINTF_L3(PRINT_MASK_PM,
				    hwahcp->hwahc_log_handle,
				    "hwahc_destroy_pm_components: "
				    "Error disabling rmt wakeup: rval = %d",
				    rval);
			}
		} else {
			mutex_exit(&hwahcp->hwahc_mutex);
		}

		/*
		 * Since remote wakeup is disabled now,
		 * no one can raise power and get to device
		 * once power is lowered here.
		 */
		(void) pm_lower_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_PWR_OFF);

		hwahc_pm_idle_component(hwahcp);
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	if (hwahcp->hwahc_pm) {
		kmem_free(hwahcp->hwahc_pm, sizeof (hwahc_power_t));
		hwahcp->hwahc_pm = NULL;
	}
	mutex_exit(&hwahcp->hwahc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_destroy_pm_components: End");
}

/* mark component busy */
static void
hwahc_pm_busy_component(hwahc_state_t *hwahcp)
{
	ASSERT(!mutex_owned(&hwahcp->hwahc_mutex));

	if (hwahcp->hwahc_pm != NULL) {
		mutex_enter(&hwahcp->hwahc_mutex);
		hwahcp->hwahc_pm->hwahc_pm_busy++;
		USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_pm_busy_component: %d",
		    hwahcp->hwahc_pm->hwahc_pm_busy);
		mutex_exit(&hwahcp->hwahc_mutex);

		if (pm_busy_component(hwahcp->hwahc_dip, 0) !=
		    DDI_SUCCESS) {
			mutex_enter(&hwahcp->hwahc_mutex);
			hwahcp->hwahc_pm->hwahc_pm_busy--;
			USB_DPRINTF_L2(PRINT_MASK_PM,
			    hwahcp->hwahc_log_handle,
			    "hwahc_pm_busy_component failed: %d",
			    hwahcp->hwahc_pm->hwahc_pm_busy);
			mutex_exit(&hwahcp->hwahc_mutex);
		}
	}
}

/* mark component idle */
static void
hwahc_pm_idle_component(hwahc_state_t *hwahcp)
{
	ASSERT(!mutex_owned(&hwahcp->hwahc_mutex));

	if (hwahcp->hwahc_pm != NULL) {

		if (pm_idle_component(hwahcp->hwahc_dip, 0) ==
		    DDI_SUCCESS) {
			mutex_enter(&hwahcp->hwahc_mutex);
			ASSERT(hwahcp->hwahc_pm->hwahc_pm_busy > 0);
			hwahcp->hwahc_pm->hwahc_pm_busy--;
			USB_DPRINTF_L4(PRINT_MASK_PM,
			    hwahcp->hwahc_log_handle,
			    "hwahc_pm_idle_component: %d",
			    hwahcp->hwahc_pm->hwahc_pm_busy);
			mutex_exit(&hwahcp->hwahc_mutex);
		}
	}
}

/*
 * hwahc_power :
 *	Power entry point, the workhorse behind pm_raise_power, pm_lower_power,
 *	usb_req_raise_power and usb_req_lower_power.
 */
/* ARGSUSED */
static int
hwahc_power(dev_info_t *dip, int comp, int level)
{
	hwahc_state_t	*hwahcp;
	hwahc_power_t	*pm;
	int		rval = USB_FAILURE;

	hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip));

	if (hwahcp == NULL) {

		return (DDI_FAILURE);
	}

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_power: dip = 0x%p", (void *)dip);

	mutex_enter(&hwahcp->hwahc_mutex);

	if (hwahcp->hwahc_pm == NULL) {

		goto done;
	}

	pm = hwahcp->hwahc_pm;

	/* Check if we are transitioning to a legal power level */
	if (USB_DEV_PWRSTATE_OK(pm->hwahc_pwr_states, level)) {
		USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_power: illegal power level = %d "
		    "pwr_states: %x", level, pm->hwahc_pwr_states);

		goto done;
	}

	switch (level) {
	case USB_DEV_OS_PWR_OFF :
		rval = hwahc_pwrlvl0(hwahcp);

		break;
	case USB_DEV_OS_PWR_1:
		rval = hwahc_pwrlvl1(hwahcp);

		break;
	case USB_DEV_OS_PWR_2:
		rval = hwahc_pwrlvl2(hwahcp);

		break;
	case USB_DEV_OS_FULL_PWR :
		rval = hwahc_pwrlvl3(hwahcp);

		break;
	}
done:
	mutex_exit(&hwahcp->hwahc_mutex);

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

/*
 * hwahc_pwrlvl0:
 * Functions to handle power transition for OS levels 0 -> 3
 *	OS 0 <--> USB D3, no or minimal power
 */
static int
hwahc_pwrlvl0(hwahc_state_t *hwahcp)
{
	int	rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_pwrlvl0: %d", hwahcp->hwahc_pm->hwahc_pm_busy);

	switch (hwahcp->hwahc_dev_state) {
	case USB_DEV_ONLINE:
		/* Deny the powerdown request if the device is busy */
		if (hwahcp->hwahc_pm->hwahc_pm_busy != 0) {
			USB_DPRINTF_L2(PRINT_MASK_PM,
			    hwahcp->hwahc_log_handle,
			    "hwahc_pwrlvl0: hwahc_pm is busy");

			return (USB_FAILURE);
		}
		/*
		 * only when final_stop gets called, we allow the system
		 * to do PM on us. At this moment, we don't need to do
		 * more operations other than those in final_stop.
		 */

		/* Issue USB D3 command to the device here */
		rval = usb_set_device_pwrlvl3(hwahcp->hwahc_dip);
		ASSERT(rval == USB_SUCCESS);

		hwahcp->hwahc_dev_state = USB_DEV_PWRED_DOWN;

		hwahcp->hwahc_pm->hwahc_current_power = USB_DEV_OS_PWR_OFF;

		break;
	case USB_DEV_DISCONNECTED:
	case USB_DEV_SUSPENDED:
	case USB_DEV_PWRED_DOWN:
	default:
		break;
	}

	return (USB_SUCCESS);
}

/*
 * hwahc_pwrlvl1:
 *	Functions to handle power transition to OS levels -> 2
 *	OS level 1 <--> D2
 */
static int
hwahc_pwrlvl1(hwahc_state_t *hwahcp)
{
	int	rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_pwrlvl1:");

	/* Issue USB D2 command to the device here */
	rval = usb_set_device_pwrlvl2(hwahcp->hwahc_dip);
	ASSERT(rval == USB_SUCCESS);

	return (USB_FAILURE);
}

/*
 * hwahc_pwrlvl2:
 *	Functions to handle power transition to OS levels -> 1
 *	OS leve 2 <--> D1
 */
static int
hwahc_pwrlvl2(hwahc_state_t *hwahcp)
{
	int	rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_pwrlvl2:");

	/* Issue USB D1 command to the device here */
	rval = usb_set_device_pwrlvl1(hwahcp->hwahc_dip);
	ASSERT(rval == USB_SUCCESS);

	return (USB_FAILURE);
}


/*
 * hwahc_pwrlvl3:
 *	Functions to handle power transition to OS level -> 0
 *	OS level 3 <--> D0 (full power)
 */
static int
hwahc_pwrlvl3(hwahc_state_t *hwahcp)
{
	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_pwrlvl3: %d", hwahcp->hwahc_pm->hwahc_pm_busy);

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	switch (hwahcp->hwahc_dev_state) {
	case USB_DEV_PWRED_DOWN:
		/* Issue USB D0 command to the device here */
		(void) usb_set_device_pwrlvl0(hwahcp->hwahc_dip);

		/*
		 * Due to our current PM policy, it's not possible
		 * for hwa to be in USB_DEV_PWRED_DOWN between
		 * initial_start and final_stop. If it's PWRED_DOWN,
		 * it should not start. We don't need to resume
		 * soft or hardware state in this case.
		 */
		if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) {
			/* no need to start hc */
			hwahcp->hwahc_dev_state = USB_DEV_ONLINE;
			hwahcp->hwahc_pm->hwahc_current_power =
			    USB_DEV_OS_FULL_PWR;

			return (USB_SUCCESS);
		}

		hwahcp->hwahc_pm->hwahc_current_power = USB_DEV_OS_FULL_PWR;

		/* FALLTHRU */
	case USB_DEV_ONLINE:
		/* we are already in full power */
		/* FALLTHRU */
	case USB_DEV_DISCONNECTED:
	case USB_DEV_SUSPENDED:
		/*
		 * PM framework tries to put you in full power
		 * during system shutdown. If we are disconnected
		 * return success. Also, we should not change state
		 * when we are disconnected or suspended or about to
		 * transition to that state
		 */

		return (USB_SUCCESS);
	default:
		USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_pwrlvl3: illegal dev_state=%d",
		    hwahcp->hwahc_dev_state);


		return (USB_FAILURE);
	}
}

/*
 * Host power management: stop channel
 * 	See Section 4.16.2.1 for details
 *	See Section 8.1.0 for HWA suspend/resume
 */
static int
hwahc_hc_channel_suspend(hwahc_state_t *hwahcp)
{
	int			rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
	    "hwahc_hc_channel_suspend:");

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	/* no need to suspend if host hw was not started */
	if (hwahcp->hwahc_hw_state != HWAHC_HW_STARTED) {
		USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_suspend: hw already stopped");

		return (USB_SUCCESS);
	}

	if (hwahcp->hwahc_hw_state == HWAHC_HW_CH_SUSPEND) {
		USB_DPRINTF_L3(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_suspend: already suspended");

		return (USB_SUCCESS);
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	/* suspend host, refer to WUSB 1.0 spec 8.5.3.14 */
	rval = wusb_hc_stop_ch(&hwahcp->hwahc_hc_data, 10000); /* 10ms */
	mutex_enter(&hwahcp->hwahc_mutex);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_PM, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_suspend: wusb channel stop fails");

		return (rval);
	}

	hwahcp->hwahc_hw_state = HWAHC_HW_CH_SUSPEND;

	return (USB_SUCCESS);
}

/*
 * Parse security descriptors, see T.8-43
 * 	put result in secrt_data
 */
static int
hwahc_parse_security_data(wusb_secrt_data_t *secrt_data,
	usb_cfg_data_t *cfg_data)
{
	int		i, j;
	usb_cvs_data_t	*cvs_data;
	size_t		count, len;

	if ((secrt_data == NULL) || (cfg_data == NULL)) {
		return (USB_INVALID_ARGS);
	}

	for (i = 0; i < cfg_data->cfg_n_cvs; i++) {
		cvs_data = &cfg_data->cfg_cvs[i];
		if (cvs_data == NULL) {
			continue;
		}
		if (cvs_data->cvs_buf[1] == USB_DESCR_TYPE_SECURITY) {
			count = usb_parse_data("ccsc",
			    cvs_data->cvs_buf, cvs_data->cvs_buf_len,
			    (void *)&secrt_data->secrt_descr,
			    (size_t)USB_SECURITY_DESCR_SIZE);
			if (count != USB_SECURITY_DESCR_SIZE) {

				return (USB_FAILURE);
			} else {
				secrt_data->secrt_n_encry =
				    secrt_data->secrt_descr.bNumEncryptionTypes;
				len = sizeof (usb_encryption_descr_t) *
				    secrt_data->secrt_n_encry;

				secrt_data->secrt_encry_descr =
				    (usb_encryption_descr_t *)kmem_alloc(len,
				    KM_SLEEP);

				for (j = 0; j < secrt_data->secrt_n_encry;
				    j++) {
					cvs_data =
					    &cfg_data->cfg_cvs[i + j + 1];
					if (cvs_data->cvs_buf[1] !=
					    USB_DESCR_TYPE_ENCRYPTION) {
						kmem_free(secrt_data->
						    secrt_encry_descr, len);

						return (USB_FAILURE);
					}

					/* Table 7-34 */
					count = usb_parse_data("ccccc",
					    cvs_data->cvs_buf,
					    cvs_data->cvs_buf_len,
					    (void *)&secrt_data->
					    secrt_encry_descr[j],
					    USB_ENCRYPTION_DESCR_SIZE);
					if (count !=
					    USB_ENCRYPTION_DESCR_SIZE) {
						kmem_free(secrt_data->
						    secrt_encry_descr, len);

						return (USB_FAILURE);

					}
				}
				return (USB_SUCCESS);
			}
		}
	}

	return (USB_FAILURE);
}

/* initialize wusb_hc_data_t structure */
static void
hwahc_hc_data_init(hwahc_state_t *hwahcp)
{
	wusb_hc_data_t	*hc_data = &hwahcp->hwahc_hc_data;

	hc_data->hc_dip = hwahcp->hwahc_dip;
	hc_data->hc_private_data = (void *)hwahcp;

	(void) memset(hc_data->hc_chid, 0, sizeof (hc_data->hc_chid));

	hc_data->hc_num_mmcies = hwahcp->hwahc_wa_data.wa_descr.bNumMMCIEs;

	ASSERT(hc_data->hc_num_mmcies != 0);

	hc_data->hc_mmcie_list = kmem_zalloc((hc_data->hc_num_mmcies *
	    sizeof (wusb_ie_header_t *)), KM_SLEEP);

	/* initialize frequently used IE */
	hc_data->hc_alive_ie.bIEIdentifier = WUSB_IE_DEV_KEEPALIVE;

	/* register callbacks */
	hc_data->disconnect_dev = hwahc_disconnect_dev;
	hc_data->reconnect_dev = hwahc_reconnect_dev;
	hc_data->create_child = hwahc_create_child;
	hc_data->destroy_child = hwahc_destroy_child;

	/* HWA HC operation functions */
	hc_data->set_encrypt = hwahc_set_encrypt;
	hc_data->set_ptk = hwahc_set_ptk;
	hc_data->set_gtk = hwahc_set_gtk;
	hc_data->set_device_info = hwahc_set_device_info;
	hc_data->set_cluster_id = hwahc_set_cluster_id;
	hc_data->set_stream_idx = hwahc_set_stream_idx;
	hc_data->set_wusb_mas = hwahc_set_wusb_mas;
	hc_data->add_mmc_ie = hwahc_add_mmc_ie;
	hc_data->rem_mmc_ie = hwahc_remove_mmc_ie;
	hc_data->stop_ch = hwahc_stop_ch;
	hc_data->set_num_dnts = hwahc_set_num_dnts;
	hc_data->get_time = hwahc_get_time;

	hc_data->hc_num_ports = hwahcp->hwahc_wa_data.wa_descr.bNumPorts;

	hc_data->hc_cd_list_length = (sizeof (dev_info_t **)) *
	    (hc_data->hc_num_ports + 1);

	hc_data->hc_children_dips = (dev_info_t **)kmem_zalloc(
	    hc_data->hc_cd_list_length, KM_SLEEP);
	hc_data->hc_usba_devices = (usba_device_t **)kmem_zalloc(
	    hc_data->hc_cd_list_length, KM_SLEEP);
	hc_data->hc_dev_infos = (wusb_dev_info_t **)kmem_zalloc(
	    hc_data->hc_cd_list_length, KM_SLEEP);

	mutex_init(&hc_data->hc_mutex, NULL, MUTEX_DRIVER, NULL);
}

/* deinitialize wusb_hc_data_t structure */
static void
hwahc_hc_data_fini(hwahc_state_t *hwahcp)
{
	int			i;
	wusb_hc_data_t		*hc_data = &hwahcp->hwahc_hc_data;
	wusb_ie_header_t	*hdr;

#ifdef DEBUG
	usb_port_t	port;
#endif

	if (hc_data->hc_mmcie_list) {
		/* Free all recorded IEs except statically allocated IEs */
		for (i = 0; i < hc_data->hc_num_mmcies; i++) {
			if (hc_data->hc_mmcie_list[i] != NULL) {
				hdr = hc_data->hc_mmcie_list[i];
				if ((hdr->bIEIdentifier !=
				    WUSB_IE_DEV_KEEPALIVE)) {
					kmem_free(hdr, hdr->bLength);
				}
				hc_data->hc_mmcie_list[i] = NULL;
			}
		}

		kmem_free(hc_data->hc_mmcie_list,
		    hc_data->hc_num_mmcies * sizeof (wusb_ie_header_t *));
	}

	if (hc_data->hc_cluster_id) {
		wusb_hc_free_cluster_id(hc_data->hc_cluster_id);
	}

	if (hc_data->hc_cc_list) {
		wusb_hc_free_cc_list(hc_data->hc_cc_list);
	}

#ifdef DEBUG
	for (port = 1; port <= hc_data->hc_num_ports; port++) {
		ASSERT(hc_data->hc_usba_devices[port] == NULL);
		ASSERT(hc_data->hc_children_dips[port] == NULL);
		ASSERT(hc_data->hc_dev_infos[port] == NULL);
	}
#endif

	kmem_free(hc_data->hc_children_dips, hc_data->hc_cd_list_length);
	kmem_free(hc_data->hc_usba_devices, hc_data->hc_cd_list_length);
	kmem_free(hc_data->hc_dev_infos, hc_data->hc_cd_list_length);

	mutex_destroy(&hc_data->hc_mutex);
}

/* fully start the HWA hw */
static int
hwahc_hc_initial_start(hwahc_state_t *hwahcp)
{
	uint8_t	stream_idx;
	uint8_t	mas[WUSB_SET_WUSB_MAS_LEN];
	int	rval;
	uint8_t	cluster_id = 0;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_hc_initial_start:");

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: invalid dev state = %d",
		    hwahcp->hwahc_dev_state);

		return (USB_INVALID_REQUEST);
	}

	if (hwahcp->hwahc_hw_state != HWAHC_HW_STOPPED) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: invalid hw state");

		return (USB_INVALID_REQUEST);
	}

	/*
	 * start beacon of radio layer
	 * We're not sure if previouse channel is occupied or not. So, let
	 * UWB allocates a free channel for this hwa. Then we can start
	 * beacon.
	 */
	hwahcp->hwahc_hc_data.hc_channel =
	    uwb_allocate_channel(hwahcp->hwahc_dip);
	if (hwahcp->hwahc_hc_data.hc_channel  == 0) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA,
		    hwahcp->hwahc_log_handle,
		    "wusb_hc_initial_start: channel = %d",
		    hwahcp->hwahc_hc_data.hc_channel);
		return (USB_FAILURE);
	}

	if ((rval = uwb_start_beacon(hwahcp->hwahc_dip,
	    hwahcp->hwahc_hc_data.hc_channel)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA,
		    hwahcp->hwahc_log_handle,
		    "wusb_hc_initial_start: start uwb beacon failed");

		return (rval);
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	/* reset wire adapter */
	rval = wusb_wa_reset(&hwahcp->hwahc_wa_data,
	    hwahcp->hwahc_default_pipe);
	mutex_enter(&hwahcp->hwahc_mutex);
	if (rval != SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: reset wa fails");

		goto err;
	}

	/* reuse the old cluster id or assign one */
	if (hwahcp->hwahc_hc_data.hc_cluster_id) {
		cluster_id = hwahcp->hwahc_hc_data.hc_cluster_id;
	} else {
		cluster_id = wusb_hc_get_cluster_id();
		if (cluster_id == 0) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    hwahcp->hwahc_log_handle,
			    "hwahc_hc_initial_start: cannot get cluster id");
			rval = USB_NO_RESOURCES;

			goto err;
		}
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	/* set cluster id for the wusb channel */
	rval = wusb_hc_set_cluster_id(&hwahcp->hwahc_hc_data, cluster_id);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: set cluster id %d fails",
		    cluster_id);
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* UWB should be responsible for assigning stream index */
	stream_idx = 1;

	rval = wusb_hc_set_stream_idx(&hwahcp->hwahc_hc_data, stream_idx);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: set stream idx %d fails",
		    stream_idx);
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* set dnts slot */
	rval = wusb_hc_set_num_dnts(&hwahcp->hwahc_hc_data,
	    HWAHC_DEFAULT_DNTS_INTERVAL, HWAHC_DEFAULT_DNTS_SLOT_NUM);

	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: set num dnts fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* set host info IE */
	rval = wusb_hc_add_host_info(&hwahcp->hwahc_hc_data, stream_idx);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: add hostinfo ie fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* reserve MAS slots for the host, need a way to assign */
	(void) memset(mas, 0xff, WUSB_SET_WUSB_MAS_LEN);
	mas[0] = 0xf0;	/* the first 4 slots are for beacons */
	rval = wusb_hc_set_wusb_mas(&hwahcp->hwahc_hc_data, mas);
	mutex_enter(&hwahcp->hwahc_mutex);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: set wusb mas fails");

		goto err;
	}

	/* record the available MAS slots */
	(void) memcpy(hwahcp->hwahc_hc_data.hc_mas, mas, WUSB_SET_WUSB_MAS_LEN);

	/* Set initial GTK/TKID to random values */
	(void) random_get_pseudo_bytes(dft_gtk, 16);
	(void) random_get_pseudo_bytes(dft_gtkid, 3);

	/* set default GTK, need a way to dynamically compute it */
	mutex_exit(&hwahcp->hwahc_mutex);
	rval = wusb_hc_set_gtk(&hwahcp->hwahc_hc_data, dft_gtk, dft_gtkid);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: set gtk fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* enable wire adapter */
	rval = wusb_wa_enable(&hwahcp->hwahc_wa_data,
	    hwahcp->hwahc_default_pipe);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: enable wa fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	/* Start Notification endpoint */
	rval = wusb_wa_start_nep(&hwahcp->hwahc_wa_data, USB_FLAGS_SLEEP);

	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: start notification ep fails");
		(void) wusb_wa_disable(&hwahcp->hwahc_wa_data,
		    hwahcp->hwahc_default_pipe);

		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}

	mutex_enter(&hwahcp->hwahc_mutex);

	/*
	 * Handle transfer results on bulk-in ep
	 * The bulk-in ep needs to be polled no matter the completion
	 * notification is received or not to avoid miss result.
	 */
	rval = hwahc_start_result_thread(hwahcp);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_initial_start: start result thread fails, "
		    "rval = %d", rval);
		mutex_exit(&hwahcp->hwahc_mutex);
		wusb_wa_stop_nep(&hwahcp->hwahc_wa_data);
		(void) wusb_wa_disable(&hwahcp->hwahc_wa_data,
		    hwahcp->hwahc_default_pipe);
		mutex_enter(&hwahcp->hwahc_mutex);

		goto err;
	}
	USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_hc_initial_start: start result thread success");

	hwahcp->hwahc_hw_state = HWAHC_HW_STARTED;
	hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_OPERATIONAL_STATE;

	/* Don't do PM on an active beacon hwa until explicitly stopped */
	mutex_exit(&hwahcp->hwahc_mutex);
	hwahc_pm_busy_component(hwahcp);
	mutex_enter(&hwahcp->hwahc_mutex);

	return (USB_SUCCESS);

err:
	if (cluster_id != 0) {
		wusb_hc_free_cluster_id(cluster_id);
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	(void) uwb_stop_beacon(hwahcp->hwahc_dip);
	mutex_enter(&hwahcp->hwahc_mutex);

	return (rval);
}

/* entirely stop the HWA from working */
static int
hwahc_hc_final_stop(hwahc_state_t *hwahcp)
{
	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_hc_final_stop:");

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_final_stop: already stopped");

		return (USB_SUCCESS);
	}

	if (hwahcp->hwahc_dev_state == USB_DEV_SUSPENDED) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_final_stop: invalid dev state = %d",
		    hwahcp->hwahc_dev_state);

		return (USB_INVALID_REQUEST);
	}

	/* might have been powered down before detaching */
	mutex_exit(&hwahcp->hwahc_mutex);
	(void) pm_raise_power(hwahcp->hwahc_dip, 0, USB_DEV_OS_FULL_PWR);
	mutex_enter(&hwahcp->hwahc_mutex);

	if (hwahcp->hwahc_dev_state != USB_DEV_DISCONNECTED) {
		/* notify children the host is going to stop */
		(void) hwahc_hc_channel_suspend(hwahcp);

		/* release mutex here to avoid deadlock with exc_cb */
		mutex_exit(&hwahcp->hwahc_mutex);

		/* stop notification endpoint */
		wusb_wa_stop_nep(&hwahcp->hwahc_wa_data);
		mutex_enter(&hwahcp->hwahc_mutex);

		/* stop bulk-in ept from listening result */
		hwahc_stop_result_thread(hwahcp);

		/* drain the device notifications */
		hwahc_drain_notif_queue(hwahcp);

		/* disable wire adapter */
		mutex_exit(&hwahcp->hwahc_mutex);
		(void) wusb_wa_disable(&hwahcp->hwahc_wa_data,
		    hwahcp->hwahc_default_pipe);

		/* stop beaconing. Not necessary to unreserve mas */
		(void) uwb_stop_beacon(hwahcp->hwahc_dip);

		wusb_hc_rem_host_info(&hwahcp->hwahc_hc_data);

		/* Manually remove all connected children */
		hwahc_run_callbacks(hwahcp, USBA_EVENT_TAG_HOT_REMOVAL);

		/* delete all the children */
		(void) hwahc_cleanup_child(hwahcp->hwahc_dip);
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	/*
	 * we make it busy at hwahc_hc_initial_start(). This idle operation
	 * is to match that busy operation.
	 * All other busy/idle operations should have been matched.
	 */
	if ((hwahcp->hwahc_hw_state == HWAHC_HW_STARTED) &&
	    (hwahcp->hwahc_hc_soft_state == HWAHC_CTRL_OPERATIONAL_STATE)) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_final_stop: pm_busy=%d",
		    hwahcp->hwahc_pm->hwahc_pm_busy);
		mutex_exit(&hwahcp->hwahc_mutex);
		hwahc_pm_idle_component(hwahcp);
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	hwahcp->hwahc_hw_state = HWAHC_HW_STOPPED;
	if (hwahcp->hwahc_hc_soft_state == HWAHC_CTRL_OPERATIONAL_STATE) {
		hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_INIT_STATE;
	}

	return (USB_SUCCESS);
}

/*
 * init WUSB channel, this is only part of the full hw start operations
 * including setting wusb channel stream idx, wusb MAS slots reservation
 * and adding host info IE
 */
static int
hwahc_hc_channel_start(hwahc_state_t *hwahcp)
{
	uint8_t			stream_idx;
	uint8_t			mas[WUSB_SET_WUSB_MAS_LEN];
	int			rval;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_hc_channel_start:");

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_start: invalid dev_state = %d",
		    hwahcp->hwahc_dev_state);

		return (USB_INVALID_REQUEST);
	}

	if (hwahcp->hwahc_hw_state != HWAHC_HW_CH_STOPPED) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_start: invalid hw state");

		return (USB_INVALID_REQUEST);
	}

	/* set stream idx */
	stream_idx = 1;

	mutex_exit(&hwahcp->hwahc_mutex);
	rval = wusb_hc_set_stream_idx(&hwahcp->hwahc_hc_data, stream_idx);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_start: set stream idx %d fails",
		    stream_idx);
		mutex_enter(&hwahcp->hwahc_mutex);

		return (rval);
	}

	/* reserve MAS slots for the host. Should be allocated by UWB */
	(void) memset(mas, 0xff, WUSB_SET_WUSB_MAS_LEN);
	mas[0] = 0xf0;	/* for beacons */
	rval = wusb_hc_set_wusb_mas(&hwahcp->hwahc_hc_data, mas);

	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_start: set wusb mas fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		return (rval);
	}
	(void) memcpy(hwahcp->hwahc_hc_data.hc_mas, mas, WUSB_SET_WUSB_MAS_LEN);

	/* set host info IE */
	rval = wusb_hc_add_host_info(&hwahcp->hwahc_hc_data, stream_idx);

	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_start: add hostinfo ie fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		return (rval);
	}

	mutex_enter(&hwahcp->hwahc_mutex);
	hwahcp->hwahc_hw_state = HWAHC_HW_STARTED;
	hwahcp->hwahc_hc_soft_state = HWAHC_CTRL_OPERATIONAL_STATE;

	/* do not PM this device, once we're ready to accept DN */
	mutex_exit(&hwahcp->hwahc_mutex);
	hwahc_pm_busy_component(hwahcp);
	mutex_enter(&hwahcp->hwahc_mutex);

	return (USB_SUCCESS);
}

/*
 * stop WUSB channel, this only stops part of the hw function
 * it mainly unreserve the MAS slots and remove the host info IE
 */
static int
hwahc_hc_channel_stop(hwahc_state_t *hwahcp)
{
	uint8_t			stream_idx;
	uint8_t			mas[WUSB_SET_WUSB_MAS_LEN];
	int			rval;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_hc_channel_stop:");

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	if (hwahcp->hwahc_dev_state != USB_DEV_ONLINE) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_stop: invalid dev state %d",
		    hwahcp->hwahc_dev_state);

		return (USB_INVALID_REQUEST);
	}

	if (hwahcp->hwahc_hw_state == HWAHC_HW_CH_STOPPED) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_stop: already partially stopped");

		return (USB_SUCCESS);
	}

	if (hwahcp->hwahc_hw_state == HWAHC_HW_STOPPED) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_stop: already stopped, invalid state");

		return (USB_INVALID_REQUEST);
	}

	/* send host disconect IE so that the children know to disconnect */
	mutex_exit(&hwahcp->hwahc_mutex);
	rval = wusb_hc_send_host_disconnect(&hwahcp->hwahc_hc_data);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_stop: send host disconnect ie fails");

		mutex_enter(&hwahcp->hwahc_mutex);

		return (rval);
	}

	/* remove host info IE */
	wusb_hc_rem_host_info(&hwahcp->hwahc_hc_data);

	/* unset stream idx */
	stream_idx = 0;

	rval = wusb_hc_set_stream_idx(&hwahcp->hwahc_hc_data, stream_idx);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_stop: set stream idx 0 fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		return (rval);
	}

	/* unreserve MAS slots */
	(void) memset(mas, 0, WUSB_SET_WUSB_MAS_LEN);
	rval = wusb_hc_set_wusb_mas(&hwahcp->hwahc_hc_data, mas);

	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_hc_channel_stop: set null wusb mas fails");
		mutex_enter(&hwahcp->hwahc_mutex);

		return (rval);
	}

	mutex_enter(&hwahcp->hwahc_mutex);
	(void) memcpy(hwahcp->hwahc_hc_data.hc_mas, mas, WUSB_SET_WUSB_MAS_LEN);

	hwahcp->hwahc_hw_state = HWAHC_HW_CH_STOPPED;

	/* Channel is stopped, can be PM'ed */
	mutex_exit(&hwahcp->hwahc_mutex);
	hwahc_pm_idle_component(hwahcp);
	mutex_enter(&hwahcp->hwahc_mutex);

	return (USB_SUCCESS);
}

/* initialize data transfer related resources */
static int
hwahc_wa_start(hwahc_state_t *hwahcp)
{
	int	rval;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_wa_start:");

	/* get all rpipe descrs */
	if ((rval = wusb_wa_get_rpipe_descrs(&hwahcp->hwahc_wa_data,
	    hwahcp->hwahc_default_pipe, PRINT_MASK_ATTA,
	    hwahcp->hwahc_log_handle)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_wa_start: get rpipe descrs fails, rval=%d", rval);

		return (rval);
	}

	/* open all data transfer epts */
	if ((rval = wusb_wa_open_pipes(&hwahcp->hwahc_wa_data)) !=
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_wa_start: open pipes fails, rval=%d", rval);
		(void) wusb_wa_disable(&hwahcp->hwahc_wa_data,
		    hwahcp->hwahc_default_pipe);

		return (rval);
	}

	/* init notification list */
	usba_init_list(&hwahcp->hwahc_dn_notif_queue, NULL,
	    hwahcp->hwahc_dev_data->dev_iblock_cookie);

	return (USB_SUCCESS);
}

/* deinitialize data transfer related resources */
static void
hwahc_wa_stop(hwahc_state_t *hwahcp)
{
	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_wa_stop:");

	usba_destroy_list(&hwahcp->hwahc_dn_notif_queue);
	wusb_wa_close_pipes(&hwahcp->hwahc_wa_data);
}

/*
 * HUBD related initialization
 * To mimic standard hub attach process to create a fake "root hub"
 * for HWA
 */
static int
hwahc_hub_attach(hwahc_state_t *hwahcp)
{
	hubd_t		*hubd = NULL;
	dev_info_t	*dip = hwahcp->hwahc_dip;
	int		instance = ddi_get_instance(dip);
	int		i;
	int		rval;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_hub_attach:");

	if (ndi_prop_create_boolean(DDI_DEV_T_NONE, dip,
	    "wire-adapter") != NDI_SUCCESS) {

		return (USB_FAILURE);
	}

	/* allocate hubd structure */
	hubd = hwahcp->hwahc_hubd = kmem_zalloc(sizeof (hubd_t), KM_SLEEP);

	hubd->h_log_handle = usb_alloc_log_hdl(dip, "husb", &hubd_errlevel,
	    &hubd_errmask, &hubd_instance_debug, 0);
	hubd->h_usba_device = usba_get_usba_device(dip);
	hubd->h_usba_device->usb_is_wa = TRUE;
	hubd->h_dip = dip;
	hubd->h_instance = instance;
	hubd->h_ignore_pwr_budget = B_TRUE;
	hubd->h_cleanup_child = hwahc_cleanup_child;

	mutex_enter(&hubd->h_usba_device->usb_mutex);
	hubd->h_usba_device->usb_root_hubd = hubd;
	mutex_exit(&hubd->h_usba_device->usb_mutex);

	if (usb_get_dev_data(dip, &hubd->h_dev_data,
	    USB_PARSE_LVL_IF, 0) != USB_SUCCESS) {
		USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle,
		    "cannot get dev_data");

		goto fail;
	}

	/* init hubd mutex */
	mutex_init(HUBD_MUTEX(hubd), NULL, MUTEX_DRIVER,
	    hubd->h_dev_data->dev_iblock_cookie);

	usb_free_descr_tree(dip, hubd->h_dev_data);

	hubd->h_init_state |= HUBD_LOCKS_DONE;

	/* register the instance to usba HUBDI */
	rval = usba_hubdi_register(dip, 0);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle,
		    "usba_hubdi_register failed");

		goto fail;
	}

	mutex_enter(HUBD_MUTEX(hubd));
	hubd->h_init_state |= HUBD_HUBDI_REGISTERED;

	hubd->h_ancestry_str = (char *)kmem_zalloc(HUBD_APID_NAMELEN,
	    KM_SLEEP);
	hubd_get_ancestry_str(hubd);

	/* create cfgadm minor nodes */
	for (i = 1; i <= hwahcp->hwahc_wa_data.wa_descr.bNumPorts; i++) {
		char ap_name[HUBD_APID_NAMELEN];

		(void) snprintf(ap_name, HUBD_APID_NAMELEN, "%s%d",
		    hubd->h_ancestry_str, i);
		USB_DPRINTF_L3(DPRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "ap_name=%s", ap_name);

		if (ddi_create_minor_node(dip, ap_name, S_IFCHR,
		    (instance << HWAHC_MINOR_INSTANCE_SHIFT) | i,
		    DDI_NT_USB_ATTACHMENT_POINT, 0) != DDI_SUCCESS) {
			USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle,
			    "cannot create attachment point node (%d)",
			    instance);
			mutex_exit(HUBD_MUTEX(hubd));

			goto fail;
		}
	}
	i = hwahcp->hwahc_wa_data.wa_descr.bNumPorts;
	mutex_exit(HUBD_MUTEX(hubd));

	/* create hubd minor node */
	if (ddi_create_minor_node(dip, "hubd", S_IFCHR,
	    instance << HWAHC_MINOR_INSTANCE_SHIFT,
	    DDI_NT_NEXUS, 0) != DDI_SUCCESS) {
		USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle,
		    "cannot create devctl minor node (%d)", instance);

		goto fail;
	}

	mutex_enter(HUBD_MUTEX(hubd));
	hubd->h_init_state |= HUBD_MINOR_NODE_CREATED;
	mutex_exit(HUBD_MUTEX(hubd));

	if (ndi_prop_update_int(DDI_DEV_T_NONE, dip,
	    "usb-port-count", i) != DDI_PROP_SUCCESS) {
		USB_DPRINTF_L2(DPRINT_MASK_ATTA, hubd->h_log_handle,
		    "usb-port-count update failed");
	}

	return (USB_SUCCESS);

fail:
	if (hwahc_hub_detach(hwahcp) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "fail to cleanup after hub attach failure");
	}

	return (USB_FAILURE);
}

/* HUBD related deinitialization */
static int
hwahc_hub_detach(hwahc_state_t *hwahcp)
{
	hubd_t		*hubd = hwahcp->hwahc_hubd;
	dev_info_t	*dip = hwahcp->hwahc_dip;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_hub_detach:");

	if ((hubd->h_init_state & HUBD_LOCKS_DONE) == 0) {
		goto done;
	}

	if (hubd->h_init_state & HUBD_MINOR_NODE_CREATED) {
		/* remove minor nodes */
		ddi_remove_minor_node(dip, NULL);
	}

	if (hubd->h_init_state & HUBD_HUBDI_REGISTERED) {
		/* unregister with usba HUBDI */
		(void) usba_hubdi_unregister(dip);
	}

	if (hubd->h_init_state & HUBD_LOCKS_DONE) {
		mutex_destroy(HUBD_MUTEX(hubd));
	}

	if (hubd->h_ancestry_str) {
		kmem_free(hubd->h_ancestry_str, HUBD_APID_NAMELEN);
	}

done:
	if (hubd->h_dev_data) {
		/* unregister client from usba */
		usb_client_detach(dip, hubd->h_dev_data);
	}

	usb_free_log_hdl(hubd->h_log_handle);
	kmem_free(hubd, sizeof (hubd_t));
	ddi_prop_remove_all(dip);

	return (USB_SUCCESS);
}

/* print security descrs */
static void
hwahc_print_secrt_data(hwahc_state_t *hwahcp)
{
	int			i;
	wusb_secrt_data_t	*secrt_data = &hwahcp->hwahc_secrt_data;

	USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "The Host Wire Adapter security descriptor:");
	USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "bLength = 0x%x\t\t bDescriptorType = 0x%x",
	    secrt_data->secrt_descr.bLength,
	    secrt_data->secrt_descr.bDescriptorType);
	USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "wTotalLength = 0x%x\t bNumEncryptionTypes = 0x%x",
	    secrt_data->secrt_descr.wTotalLength,
	    secrt_data->secrt_descr.bNumEncryptionTypes);

	for (i = 0; i < secrt_data->secrt_n_encry; i++) {
		USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "The Host Wire Adapter encryption descriptor %d:", i + 1);
		USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "bLength = 0x%x\t\t bDescriptorType = 0x%x",
		    secrt_data->secrt_encry_descr[i].bLength,
		    secrt_data->secrt_encry_descr[i].bDescriptorType);
		USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "bEncryptionType = 0x%x\t bEncryptionValue = 0x%x",
		    secrt_data->secrt_encry_descr[i].bEncryptionType,
		    secrt_data->secrt_encry_descr[i].bEncryptionValue);
		USB_DPRINTF_L3(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "bAuthKeyIndex = 0x%x",
		    secrt_data->secrt_encry_descr[i].bAuthKeyIndex);
	}
}

/* drain device notifications */
static void
hwahc_drain_notif_queue(hwahc_state_t *hwahcp)
{
	int	i;

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_drain_notif_queue: started");

	if ((hwahcp->hwahc_notif_thread_id == NULL) &&
	    (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) != 0)) {
		/* kick off a notif thread to drain the queue */
		if (usb_async_req(hwahcp->hwahc_dip, hwahc_notif_thread,
		    (void *)hwahcp, 0) != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_drain_notif_queue: no notif thread started");
		} else {
			hwahcp->hwahc_notif_thread_id = (kthread_t *)1;
		}
	}

	for (i = 0; i < HWAHC_NOTIF_DRAIN_TIMEOUT; i++) {
		/* loop until the queue is completed or it timeouts */
		if ((hwahcp->hwahc_notif_thread_id == NULL) &&
		    (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) ==
		    0)) {

			break;
		}
		mutex_exit(&hwahcp->hwahc_mutex);
		delay(drv_usectohz(1000000));
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	/* cleanup the queue if not completed */
	while (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) != 0) {
		hwahc_dn_notif_list_t	*nlist;

		nlist = (hwahc_dn_notif_list_t *)usba_rm_first_pvt_from_list(
		    &hwahcp->hwahc_dn_notif_queue);
		ASSERT(nlist != NULL);
		ASSERT(nlist->dn_notif != NULL);
		usba_destroy_list(&nlist->notif_list);
		kmem_free(nlist->dn_notif, nlist->dn_notif->bLength);
		kmem_free(nlist, sizeof (hwahc_dn_notif_list_t));
	}

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_drain_notif_queue: ended");
}


/* normal callback for notification ept */
static void
hwahc_intr_cb(usb_pipe_handle_t ph, struct usb_intr_req *reqp)
{
	dev_info_t		*dip = (USBA_REQ2WRP(reqp))->wr_dip;
	hwahc_state_t		*hwahcp;
	mblk_t			*data = reqp->intr_data;

	ASSERT(dip != NULL);
	hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip));
	ASSERT(hwahcp != NULL);

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_intr_cb: ph = 0x%p reqp = 0x%p", (void *)ph,
	    (void *)reqp);

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

	if (data == NULL) {
		usb_free_intr_req(reqp);

		return;
	}

	/* handle the notification */
	hwahc_handle_notif(hwahcp, data);

	usb_free_intr_req(reqp);
}

/*
 * See Section 8.3.3.3 for Transfer Notification format and
 * Section 8.5.4 for HWA specific notifications.
 *	Three kinds of Notifications:
 *		- Transfer Completion
 *		- DN Received
 *		- BPST ADJ
 */
/* handle the notification according to notification type */
static void
hwahc_handle_notif(hwahc_state_t *hwahcp, mblk_t *data)
{
	int			len;
	uint8_t			*p;
	wa_notif_header_t	*hdr;

	if (data == NULL) {

		return;
	}

	len = MBLKL(data);
	p = data->b_rptr;
	USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_handle_notif: data len = %d", len);

	/*
	 * according to WUSB 1.0/8.1.2, multiple notifications might be sent
	 * at a time, need to parse one by one
	 */
	while (len > 0) {
		if (len < 2) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_handle_notif: short packet len = %d",
			    len);

			break;
		}

		hdr = (wa_notif_header_t *)p;
		if (len < hdr->bLength) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_handle_notif: length not match, "
			    "hdr length = %d, actual length = %d",
			    hdr->bLength, len);

			break;
		}

		switch (hdr->bNotifyType) {
		case WA_NOTIF_TYPE_TRANSFER:
		{
			uint8_t		ept = p[2];

			/* deal with transfer completion notification */
			hwahc_handle_xfer_result(hwahcp, ept);

			break;
		}
		case HWA_NOTIF_TYPE_DN_RECEIVED:
		{
			hwa_notif_dn_recvd_t	*dn_notif;

			dn_notif = kmem_alloc(hdr->bLength, KM_NOSLEEP);
			(void) memcpy(dn_notif, p, hdr->bLength);

			/* deal with device notification */
			hwahc_handle_dn_notif(hwahcp, dn_notif);

			break;
		}
		case HWA_NOTIF_TYPE_BPST_ADJ:
			USB_DPRINTF_L3(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_handle_notif: received BPST adjust "
			    "notification, bAdjustment = %d", p[2]);

			break;
		default:
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_handle_notif: unknown notification 0x%x",
			    hdr->bNotifyType);

			break;
		}
		p += hdr->bLength;
		len -= hdr->bLength;
	}
}

/*
 * start listening on bulk-in ept for transfer result
 *
 * Dispatches a task to read the BULK IN endpoint to get the result of
 * last request. usb_async_req() will have system_taskq to process the tasks.
 */
int
hwahc_start_result_thread(hwahc_state_t *hwahcp)
{
	wusb_wa_data_t *wa_data;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_start_result_thread:");

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	if (hwahcp->hwahc_result_thread_id != 0) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_start_result_thread: already started");

		return (USB_SUCCESS);
	}

	wa_data = &hwahcp->hwahc_wa_data;

	mutex_enter(&wa_data->wa_mutex);
	if ((wa_data->wa_bulkin_ph != NULL) &&
	    (wa_data->wa_bulkin_pipe_state != WA_PIPE_STOPPED)) {
		mutex_exit(&wa_data->wa_mutex);

		return (USB_INVALID_PIPE);
	}
	mutex_exit(&wa_data->wa_mutex);

	if (wa_data->wa_bulkin_ph == NULL) {
		mutex_exit(&hwahcp->hwahc_mutex);
		if (usb_pipe_open(wa_data->wa_dip, &wa_data->wa_bulkin_ept,
		    &wa_data->wa_pipe_policy, USB_FLAGS_SLEEP,
		    &wa_data->wa_bulkin_ph) != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    hwahcp->hwahc_log_handle,
			    "hwahc_start_result_thread: open pipe failed");


			mutex_enter(&hwahcp->hwahc_mutex);
			return (USB_FAILURE);
		}
		mutex_enter(&hwahcp->hwahc_mutex);

		mutex_enter(&wa_data->wa_mutex);
		wa_data->wa_bulkin_pipe_state = WA_PIPE_STOPPED;
		mutex_exit(&wa_data->wa_mutex);
	}

	/* kick off an asynchronous thread to handle transfer result */
	if (usb_async_req(hwahcp->hwahc_dip, hwahc_result_thread,
	    (void *)hwahcp, 0) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_start_result_thread: failed to start result thread");

		return (USB_FAILURE);
	}
	hwahcp->hwahc_result_thread_id = (kthread_t *)1;

	/* pipe state is active while the result thread is on */
	mutex_enter(&wa_data->wa_mutex);
	wa_data->wa_bulkin_pipe_state = WA_PIPE_ACTIVE;
	mutex_exit(&wa_data->wa_mutex);

	return (USB_SUCCESS);
}

/* stop the bulk-in ept from listening */
static void
hwahc_stop_result_thread(hwahc_state_t *hwahcp)
{
	wusb_wa_data_t *wa_data;

	ASSERT(mutex_owned(&hwahcp->hwahc_mutex));

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_stop_result_thread:");

	if (hwahcp->hwahc_result_thread_id == 0) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_stop_result_thread: already stopped");

		return;
	}

	wa_data = &hwahcp->hwahc_wa_data;
	mutex_enter(&wa_data->wa_mutex);
	if ((wa_data->wa_bulkin_ph == NULL) ||
	    (wa_data->wa_bulkin_pipe_state != WA_PIPE_ACTIVE)) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
		    "hwahc_stop_result_thread: invalid pipe state");

		mutex_exit(&wa_data->wa_mutex);

		return;
	}
	mutex_exit(&wa_data->wa_mutex);

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_stop_result_thread: reset hwa bulk-in pipe");
	mutex_exit(&hwahcp->hwahc_mutex);
	usb_pipe_reset(wa_data->wa_dip, wa_data->wa_bulkin_ph,
	    USB_FLAGS_SLEEP, NULL, NULL);

	/*
	 * have to close pipe here to fail the bulk-in transfer
	 * that never timeouts
	 */
	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_stop_result_thread: close hwa bulk-in pipe");
	usb_pipe_close(wa_data->wa_dip, wa_data->wa_bulkin_ph,
	    USB_FLAGS_SLEEP, NULL, NULL);
	mutex_enter(&hwahcp->hwahc_mutex);

	mutex_enter(&wa_data->wa_mutex);
	wa_data->wa_bulkin_ph = NULL;
	wa_data->wa_bulkin_pipe_state = WA_PIPE_STOPPED;
	mutex_exit(&wa_data->wa_mutex);

	while (hwahcp->hwahc_result_thread_id != 0) {
		/* wait the result thread to exit */
		cv_wait(&hwahcp->hwahc_result_thread_cv, &hwahcp->hwahc_mutex);
	}
}

/*
 * keep listening for transfer result by setting timeout to 0 while the
 * bulk-in pipe is active
 * the thread would be stopped by closing bulk-in pipe or encountering
 * transaction error, eg, hot-removal of hwa device
 */
static void
hwahc_result_thread(void *arg)
{
	hwahc_state_t	*hwahcp = (hwahc_state_t *)arg;
	wusb_wa_data_t	*wa_data = &hwahcp->hwahc_wa_data;
	int		rval;
	uint8_t		retry = 0;

	mutex_enter(&hwahcp->hwahc_mutex);
	ASSERT(hwahcp->hwahc_result_thread_id == (kthread_t *)1);
	hwahcp->hwahc_result_thread_id = curthread;
	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_result_thread: started, thread_id=0x%p",
	    (void *)hwahcp->hwahc_result_thread_id);

	/* keep polling the bulk IN endpoint to get the result */
	mutex_enter(&wa_data->wa_mutex);
	while (wa_data->wa_bulkin_pipe_state == WA_PIPE_ACTIVE) {
		mutex_exit(&wa_data->wa_mutex);
		mutex_exit(&hwahcp->hwahc_mutex);

		if ((rval = wusb_wa_get_xfer_result(wa_data)) != USB_SUCCESS) {
			retry++;
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    hwahcp->hwahc_log_handle,
			    "hwahc_result_thread: get xfer result failed, "
			    "rval = %d, retry = %d", rval, retry);

			/* retry 3 times upon failure */
			if (retry >= 3) {
				mutex_enter(&hwahcp->hwahc_mutex);
				mutex_enter(&wa_data->wa_mutex);

				break;
			}
		}

		mutex_enter(&hwahcp->hwahc_mutex);
		mutex_enter(&wa_data->wa_mutex);
	}

	hwahcp->hwahc_result_thread_id = 0;
	wa_data->wa_bulkin_pipe_state = WA_PIPE_STOPPED;
	mutex_exit(&wa_data->wa_mutex);

	/* signal to the thread requesting stopping if any */
	cv_signal(&hwahcp->hwahc_result_thread_cv);

	USB_DPRINTF_L4(PRINT_MASK_ATTA, hwahcp->hwahc_log_handle,
	    "hwahc_result_thread: ended");

	mutex_exit(&hwahcp->hwahc_mutex);
}

/*
 * nothing to do here, just check if the ept number in the transfer
 * completion notification is valid
 * the actual handling of transfer result is performed by the result thread
 */
static void
hwahc_handle_xfer_result(hwahc_state_t *hwahcp, uint8_t ept)
{
	usb_ep_descr_t	*epdt;

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_handle_xfer_result: result on ept %d", ept);

	epdt = &hwahcp->hwahc_wa_data.wa_bulkin_ept;

	/* the result should be on the bulk-in ept */
	if ((epdt->bEndpointAddress & USB_EP_NUM_MASK) != ept) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_handle_xfer_result: ept number not match");

		return;
	}
}


/*
 * Section 8.5.4.2.
 *	Copy the DN Notification and add it to the instance's global
 *	nofication list. If the worker thread is not started yet, start
 *	it.
 */
static void
hwahc_handle_dn_notif(hwahc_state_t *hwahcp, hwa_notif_dn_recvd_t *dn_notif)
{
	hwahc_dn_notif_list_t	*nlist;

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_handle_dn_notif: notif = 0x%p", (void *)dn_notif);

	nlist = kmem_zalloc(sizeof (hwahc_dn_notif_list_t), KM_NOSLEEP);

	mutex_enter(&hwahcp->hwahc_mutex);
	nlist->dn_notif = dn_notif;

	usba_init_list(&nlist->notif_list, (usb_opaque_t)nlist,
	    hwahcp->hwahc_dev_data->dev_iblock_cookie);

	/* queue the new notification to the list */
	usba_add_to_list(&hwahcp->hwahc_dn_notif_queue, &nlist->notif_list);

	/* handle the notification queue with an asynchronous thread */
	if (hwahcp->hwahc_notif_thread_id == 0) {
		if (usb_async_req(hwahcp->hwahc_dip, hwahc_notif_thread,
		    (void *)hwahcp, 0) != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_handle_dn_notif: no notif thread started");
			mutex_exit(&hwahcp->hwahc_mutex);

			return;
		}
		hwahcp->hwahc_notif_thread_id = (kthread_t *)1;
	}

	mutex_exit(&hwahcp->hwahc_mutex);
}

/* handle the notifications in the notification queue in sequence */
static void
hwahc_notif_thread(void *arg)
{
	hwahc_state_t		*hwahcp = (hwahc_state_t *)arg;
	hwahc_dn_notif_list_t	*nlist;
	hwa_notif_dn_recvd_t	*dn_notif;

	mutex_enter(&hwahcp->hwahc_mutex);
	ASSERT(hwahcp->hwahc_notif_thread_id == (kthread_t *)1);
	hwahcp->hwahc_notif_thread_id = curthread;

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_notif_thread: started, thread_id=0x%p",
	    (void *)hwahcp->hwahc_notif_thread_id);

	while (usba_list_entry_count(&hwahcp->hwahc_dn_notif_queue) != 0) {
		/*
		 * first in first out, only one notification will be handled
		 * at a time, so it assures no racing in attach or detach
		 */
		if ((nlist =
		    (hwahc_dn_notif_list_t *)usba_rm_first_pvt_from_list(
		    &hwahcp->hwahc_dn_notif_queue)) == NULL) {

			continue;
		}
		dn_notif = nlist->dn_notif;
		mutex_exit(&hwahcp->hwahc_mutex);
		hwahc_handle_dn(hwahcp, dn_notif);
		usba_destroy_list(&nlist->notif_list);
		kmem_free(nlist, sizeof (hwahc_dn_notif_list_t));
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	hwahcp->hwahc_notif_thread_id = 0;

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_notif_thread: ended");

	mutex_exit(&hwahcp->hwahc_mutex);
}

/* Set the child device's active bit to 1 */
static void
hwahc_set_device_active(hwahc_state_t *hwahcp, uint8_t devaddr)
{
	wusb_dev_info_t *dev_info;
	wusb_hc_data_t *hc_data = &hwahcp->hwahc_hc_data;
	int i;

	mutex_enter(&hc_data->hc_mutex);
	for (i = 1; i <= hc_data->hc_num_ports; i++) {
		dev_info = hc_data->hc_dev_infos[i];
		if ((dev_info != NULL) && (dev_info->wdev_addr == devaddr)) {
			dev_info->wdev_active = 1;
			USB_DPRINTF_L3(DPRINT_MASK_EVENTS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_set_device_active:device(%p) updated ",
			    (void *)dev_info);

			break;
		}
	}
	mutex_exit(&hc_data->hc_mutex);
}

/*
 * handle a specific device notification
 * assuming the raw data in HWA DN_RECEIVED notification pkt includes
 * no more than one dn pkt
 */
static void
hwahc_handle_dn(hwahc_state_t *hwahcp, hwa_notif_dn_recvd_t *dn_notif)
{
	uint8_t			*p;
	size_t			len;
	uint8_t			dntype;
	int circ;
	wusb_hc_data_t		*hc_data = &hwahcp->hwahc_hc_data;

	if (dn_notif->bLength < 4) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_handle_dn: bLength too short %d", dn_notif->bLength);
		kmem_free(dn_notif, dn_notif->bLength);

		return;
	}

	p = dn_notif->notifdata;
	len = dn_notif->bLength - 4;

	/*
	 * WUSB Errata 06.12 specifies that the raw data in the DN_RECEIVED
	 * notification must not include the WUSB header, but only the bType
	 * and Notification specific data
	 */
	if (len == 0) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_handle_dn: no raw data");
		kmem_free(dn_notif, dn_notif->bLength);

		return;
	}
	dntype = *p;

	/* update the device's status bit, no matter what the DN is */
	hwahc_set_device_active(hwahcp, dn_notif->bSourceDeviceAddr);

	ndi_devi_enter(hwahcp->hwahc_dip, &circ);
	switch (dntype) {
	case WUSB_DN_CONNECT:
		/* DN_Connect */
		wusb_hc_handle_dn_connect(
		    hc_data, hwahcp->hwahc_default_pipe,
		    hwahcp->hwahc_wa_data.wa_ifno, p, len,
		    &hwahcp->hwahc_secrt_data);

		break;
	case WUSB_DN_DISCONNECT:
		/* DN_Disconnect */
		wusb_hc_handle_dn_disconnect(
		    hc_data, dn_notif->bSourceDeviceAddr,
		    p, len);

		break;
	case WUSB_DN_ALIVE:
		/* We only send KeepAlive IE to one device at a comment */
		mutex_enter(&hc_data->hc_mutex);
		if (dn_notif->bSourceDeviceAddr ==
		    hc_data->hc_alive_ie.bDeviceAddress[0]) {
			mutex_exit(&hc_data->hc_mutex);
			wusb_hc_rem_ie(hc_data,
			    (wusb_ie_header_t *)&hc_data->hc_alive_ie);
			mutex_enter(&hc_data->hc_mutex);
		}
		mutex_exit(&hc_data->hc_mutex);

		break;
	case WUSB_DN_EPRDY:
	case WUSB_DN_MASAVAILCHANGED:
	case WUSB_DN_REMOTEWAKEUP:
	case WUSB_DN_SLEEP:
	default:
		USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_handle_dn: dn type 0x%x not supported yet",
		    dntype);

		break;
	}

	kmem_free(dn_notif, dn_notif->bLength);
	ndi_devi_exit(hwahcp->hwahc_dip, circ);
}

/* exceptional callback for notification ept */
/* ARGSUSED */
static void
hwahc_intr_exc_cb(usb_pipe_handle_t ph, struct usb_intr_req *reqp)
{
	dev_info_t	*dip = (USBA_REQ2WRP(reqp))->wr_dip;
	hwahc_state_t	*hwahcp;

	ASSERT(dip != NULL);
	hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip));
	ASSERT(hwahcp != NULL);

	USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_intr_exc_cb: receive intr exception cb, cr=%d",
	    reqp->intr_completion_reason);

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

	mutex_enter(&hwahcp->hwahc_mutex);

	switch (reqp->intr_completion_reason) {
	case USB_CR_PIPE_RESET:
		/* only restart nep after autoclearing */
		if (hwahcp->hwahc_dev_state == USB_DEV_ONLINE) {
			hwahcp->hwahc_wa_data.wa_intr_pipe_state =
			    WA_PIPE_STOPPED;
			mutex_exit(&hwahcp->hwahc_mutex);
			(void) wusb_wa_start_nep(&hwahcp->hwahc_wa_data,
			    USB_FLAGS_NOSLEEP);
			mutex_enter(&hwahcp->hwahc_mutex);
		}

		break;
	case USB_CR_DEV_NOT_RESP:
	case USB_CR_STOPPED_POLLING:
	case USB_CR_PIPE_CLOSING:
	case USB_CR_UNSPECIFIED_ERR:
		/* never restart nep on these conditions */
	default:
		/* for all others, wait for the autoclearing PIPE_RESET cb */

		break;
	}

	usb_free_intr_req(reqp);
	mutex_exit(&hwahcp->hwahc_mutex);
}

/*
 * callback function called by WA to resubmit a periodic request for
 * interrupt polling or isochronous transfer.
 */
static int
hwahc_pipe_submit_periodic_req(wusb_wa_data_t *wa_data,
	usba_pipe_handle_data_t *ph)
{
	hwahc_state_t *hwahcp = wa_data->wa_private_data;
	hwahc_pipe_private_t *pp = (hwahc_pipe_private_t *)ph->p_hcd_private;
	int rval;

	mutex_enter(&hwahcp->hwahc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_pipe_submit_periodic_req: hwahcp=0x%p, pp=0x%p,"
	    " pipe state = %d", (void *)hwahcp, (void *)pp, pp->pp_state);

	if (pp->pp_state != HWAHC_PIPE_STATE_ACTIVE) {
		/* pipe error or pipe closing, don't resubmit any more */
		USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_pipe_submit_periodic_req: pipe not active = %d",
		    pp->pp_state);

		mutex_exit(&hwahcp->hwahc_mutex);

		return (USB_PIPE_ERROR);
	}

	mutex_exit(&hwahcp->hwahc_mutex);

	/* re-submit the original request */
	rval = wusb_wa_intr_xfer(wa_data, pp->pp_rp, ph,
	    (usb_intr_req_t *)pp->pp_client_periodic_in_reqp, 0);

	return (rval);
}

/* call HCD callback for completion handling */
static void
hwahc_rpipe_xfer_cb(dev_info_t *dip, usba_pipe_handle_data_t *ph,
	wusb_wa_trans_wrapper_t *wr, usb_cr_t cr)
{
	hwahc_state_t		*hwahcp;
	hwahc_pipe_private_t	*pp;
	usb_opaque_t		req;
	wusb_hc_data_t		*hc_data;

	hwahcp = ddi_get_soft_state(hwahc_statep, ddi_get_instance(dip));
	if (hwahcp == NULL) {

		return;
	}

	hc_data = &hwahcp->hwahc_hc_data;

	mutex_enter(&hwahcp->hwahc_mutex);
	USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_rpipe_xfer_cb: ph = 0x%p, wr = 0x%p cr = 0x%x",
	    (void *)ph, (void *)wr, cr);

	pp = (hwahc_pipe_private_t *)ph->p_hcd_private;

	mutex_enter(&hc_data->hc_mutex);
	pp->pp_wdev->wdev_active = 1; /* this device is active on xfer */
	mutex_exit(&hc_data->hc_mutex);

	switch (cr) {
	case USB_CR_OK:
		break;
	case USB_CR_NOT_SUPPORTED:
	case USB_CR_NO_RESOURCES:
	case USB_CR_PIPE_RESET:
	case USB_CR_STOPPED_POLLING:
		pp->pp_state = HWAHC_PIPE_STATE_IDLE;
		break;
	case USB_CR_PIPE_CLOSING:
		break;
	default:
		pp->pp_state = HWAHC_PIPE_STATE_ERROR;

		break;
	}

	if (wr && wr->wr_reqp) {
		req = wr->wr_reqp;

		mutex_enter(&wr->wr_rp->rp_mutex);
		wr->wr_reqp = NULL;
		mutex_exit(&wr->wr_rp->rp_mutex);

	} else { /* periodic pipe cleanup */

		/* the original request is cleared and returned to client */
		req = pp->pp_client_periodic_in_reqp;
		pp->pp_client_periodic_in_reqp = NULL;
	}

	mutex_exit(&hwahcp->hwahc_mutex);

	USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_rpipe_xfer_cb: call usba_hcdi_cb for req= 0x%p",
	    (void *)req);

	usba_hcdi_cb(ph, req, cr);
}

/* post disconnect event to child on a certain port */
static void
hwahc_disconnect_dev(dev_info_t *dip, usb_port_t port)
{
	hwahc_state_t	*hwahcp;
	int		circ;
	dev_info_t	*child_dip;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return;
	}

	ndi_devi_enter(dip, &circ);
	mutex_enter(&hwahcp->hwahc_mutex);

	child_dip = hwahcp->hwahc_hc_data.hc_children_dips[port];
	if ((hwahcp->hwahc_dev_state == USB_DEV_ONLINE) && child_dip) {
		mutex_exit(&hwahcp->hwahc_mutex);

		/* if the child driver remains attached */
		if (i_ddi_devi_attached(child_dip)) {
			hwahc_post_event(hwahcp, port,
			    USBA_EVENT_TAG_HOT_REMOVAL);
		}
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	ndi_devi_exit(dip, circ);
}

/* post reconect event to child on a certain port */
static void
hwahc_reconnect_dev(dev_info_t *dip, usb_port_t port)
{
	hwahc_state_t	*hwahcp;
	int		circ;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return;
	}
	ndi_devi_enter(dip, &circ);
	mutex_enter(&hwahcp->hwahc_mutex);

	if ((hwahcp->hwahc_dev_state == USB_DEV_ONLINE) &&
	    (hwahcp->hwahc_hc_data.hc_children_dips[port])) {
		mutex_exit(&hwahcp->hwahc_mutex);
		hwahc_post_event(hwahcp, port, USBA_EVENT_TAG_HOT_INSERTION);
		mutex_enter(&hwahcp->hwahc_mutex);
	}

	mutex_exit(&hwahcp->hwahc_mutex);
	ndi_devi_exit(dip, circ);
}


/*
 * Device TrustTimeout timer operations:
 * hwahc_start_trust_timer: start the trust timer for a newly connected device
 * hwahc_trust_timeout_handler: timer handler
 * hwahc_stop_trust_timer: stop a device's trust timer
 */
static void
hwahc_start_trust_timer(wusb_dev_info_t *dev)
{
	if (hwahc_enable_trust_timeout == 0) {

		return;
	}

	if (dev->wdev_trust_timer == NULL) {
		dev->wdev_trust_timer = timeout(hwahc_trust_timeout_handler,
		    (void *)dev, drv_usectohz(WUSB_TRUST_TIMEOUT_US));
	}
}

/* timeout handler for device TrustTimeout. See section 4.14 */
static void
hwahc_trust_timeout_handler(void *arg)
{
	wusb_dev_info_t *dev = (wusb_dev_info_t *)arg;
	usb_port_t port;
	uint16_t   dev_addr;
	wusb_hc_data_t *hc_data = dev->wdev_hc;
	uint8_t	retry = 3;
	int rval;

	mutex_enter(&hc_data->hc_mutex);

	dev->wdev_trust_timer = 0;
	dev_addr = dev->wdev_addr;

	if (dev->wdev_active == 1) {
	/* device is active during the past period. Restart the timer */
		dev->wdev_active = 0; /* expect device DN set it to 1 */
	} else {
		/* send a KeepAlive IE to query the device */
		for (retry = 0; retry < 3; retry++) {
			mutex_exit(&hc_data->hc_mutex);
			rval = wusb_hc_send_keepalive_ie(hc_data,
			    dev_addr);
			mutex_enter(&hc_data->hc_mutex);

			if (rval == USB_SUCCESS) {
				break;
			}
			/* retry 3 times if fail to send KeepAlive IE */
		}

		if (dev->wdev_active == 0) {
			/* still no activity! Delete this device */
			if (wusb_hc_is_dev_connected(hc_data, dev->wdev_cdid,
			    &port)) {
				mutex_exit(&hc_data->hc_mutex);
				(void) hwahc_destroy_child(hc_data->hc_dip,
				    port);

				/* the device comes to the end of its life */
				return;
			}
		}
	}

	/* active or we received DN during query */
	hwahc_start_trust_timer(dev);

	mutex_exit(&hc_data->hc_mutex);
}

/* stop a child device's trust timeout handler */
void
hwahc_stop_trust_timer(wusb_dev_info_t *dev)
{
	timeout_id_t tid;
	wusb_hc_data_t *hc_data = dev->wdev_hc;

	ASSERT(mutex_owned(&hc_data->hc_mutex));

	if (hwahc_enable_trust_timeout == 0) {
		return;
	}

	tid = dev->wdev_trust_timer;

	dev->wdev_trust_timer = NULL;
	mutex_exit(&hc_data->hc_mutex);

	if (tid != NULL) {
		(void) untimeout(tid);
	}

	mutex_enter(&hc_data->hc_mutex);
}

/* configure child device and attach child on a certain port */
static int
hwahc_create_child(dev_info_t *dip, usb_port_t port)
{
	hwahc_state_t		*hwahcp;
	wusb_hc_data_t		*hc_data;
	wusb_dev_info_t		*dev_info;
	usb_pipe_handle_t	ph;
	int			rval;
	dev_info_t		*child_dip;
	usba_device_t		*child_ud = NULL;
	mblk_t			*pdata = NULL;
	usb_cr_t		completion_reason;
	usb_cb_flags_t		cb_flags;
	size_t			size;
	uint8_t			address;
	int			user_conf_index;
	uint_t			config_index;
	int			prh_circ, rh_circ, circ;
	dev_info_t		*rh_dip;
	usb_dev_descr_t		usb_dev_descr;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (USB_INVALID_ARGS);
	}

	rh_dip = hwahcp->hwahc_hubd->h_usba_device->usb_root_hub_dip;
	ndi_hold_devi(dip);	/* avoid racing with dev detach */
	/* exclude other threads */
	ndi_devi_enter(ddi_get_parent(rh_dip), &prh_circ);
	ndi_devi_enter(rh_dip, &rh_circ);
	ndi_devi_enter(dip, &circ);

	_NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dev_info));

	hc_data = &hwahcp->hwahc_hc_data;
	mutex_enter(&hc_data->hc_mutex);
	dev_info = hc_data->hc_dev_infos[port];

	/* Created in whcdi.c before authed */
	child_dip = hc_data->hc_children_dips[port];

	child_ud = usba_get_usba_device(child_dip);
	ph = dev_info->wdev_ph;

	mutex_exit(&hc_data->hc_mutex);
	/*
	 * HWA maintains the address space as a separate bus and
	 * will not occupy parent's address space
	 */
	address = child_ud->usb_addr;
	if (address < 0x80) {
		USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_create_child: reconnecting, address = %d",
		    address);

	} else {
		/* SetAddress(0) */
		if ((rval = usb_pipe_sync_ctrl_xfer(child_dip, ph,
		    USB_DEV_REQ_HOST_TO_DEV,
		    USB_REQ_SET_ADDRESS,	/* bRequest */
		    0,				/* wValue */
		    0,				/* wIndex */
		    0,				/* wLength */
		    NULL, 0,
		    &completion_reason, &cb_flags, 0)) != USB_SUCCESS) {
			char buffer[64];
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "setting address failed (cr=%s cb_flags=%s "
			    "rval=%d)", usb_str_cr(completion_reason),
			    usb_str_cb_flags(cb_flags, buffer, sizeof (buffer)),
			    rval);

			goto done;
		}

		USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "set address 0 done");

		usb_pipe_close(child_dip, ph,
		    USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, NULL, NULL);

		child_ud->usb_addr = 0;
		dev_info->wdev_addr = 0;
		dev_info->wdev_ph = NULL;

		/* need to be called each time dev addr is changed */
		if ((rval = wusb_hc_set_device_info(&hwahcp->hwahc_hc_data,
		    port)) != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "update device info failed, rval = %d", rval);

			goto done;
		}

		/* new ph is stored in usba_device */
		if ((rval = usb_pipe_open(child_dip, NULL, NULL,
		    USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, &ph)) !=
		    USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "usb_pipe_open failed (%d)", rval);

			goto done;
		}

		/* provide at least 2ms time for address change, 7.3.1.3 */
		delay(drv_usectohz(2000));

		/* start normal enumeration process */
		/*
		 * wusb bus address has 1:1 relationship with port number
		 * and wusb bus address starts from 2, so as to follow
		 * the convention that USB bus address 1 is reserved for
		 * host controller device. As such, only 126 WUSB devices
		 * are supported on a WUSB host
		 */
		address = port + 1;
		if (address >= 0x80) {
			USB_DPRINTF_L3(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "hwahc_create_child: address for port %d exceeds "
			    "0x80", port);
			rval = USB_FAILURE;

			goto done;
		}
		/* Set the address of the device */
		if ((rval = usb_pipe_sync_ctrl_xfer(child_dip, ph,
		    USB_DEV_REQ_HOST_TO_DEV,
		    USB_REQ_SET_ADDRESS,	/* bRequest */
		    address,			/* wValue */
		    0,				/* wIndex */
		    0,				/* wLength */
		    NULL, 0,
		    &completion_reason, &cb_flags, 0)) != USB_SUCCESS) {
			char buffer[64];
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "setting address failed (cr=%s cb_flags=%s "
			    "rval=%d)", usb_str_cr(completion_reason),
			    usb_str_cb_flags(cb_flags, buffer, sizeof (buffer)),
			    rval);

			goto done;
		}

		USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "set address 0x%x done", address);

		usb_pipe_close(child_dip, ph,
		    USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, NULL, NULL);

		child_ud->usb_addr = address;
		dev_info->wdev_addr = address;
		dev_info->wdev_ph = NULL;

		if ((rval = wusb_hc_set_device_info(&hwahcp->hwahc_hc_data,
		    port)) != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "update device info failed, rval = %d", rval);

			goto done;
		}

		/* new ph is stored in usba_device */
		if ((rval = usb_pipe_open(child_dip, NULL, NULL,
		    USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, &ph)) !=
		    USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_CBOPS,
			    hwahcp->hwahc_log_handle,
			    "usb_pipe_open failed (%d)", rval);

			goto done;
		}

		/* provide at least 2ms time for address change, 7.3.1.3 */
		delay(drv_usectohz(2000));
	}

	/* get device descriptor ignoring device reconnection */
	rval = usb_pipe_sync_ctrl_xfer(child_dip, ph,
	    USB_DEV_REQ_DEV_TO_HOST | USB_DEV_REQ_TYPE_STANDARD,
	    USB_REQ_GET_DESCR,			/* bRequest */
	    USB_DESCR_TYPE_SETUP_DEV,		/* wValue */
	    0,					/* wIndex */
	    512,				/* wLength */
	    &pdata, USB_ATTRS_SHORT_XFER_OK,
	    &completion_reason, &cb_flags, 0);

	if (rval != USB_SUCCESS) {
		if (pdata) {
			freemsg(pdata);
			pdata = NULL;
		}

		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_create_child: get device descriptor failed "
		    "(%s 0x%x %d)", usb_str_cr(completion_reason),
		    cb_flags, rval);

		goto done;
	}

	ASSERT(pdata != NULL);
	size = usb_parse_dev_descr(
	    pdata->b_rptr,
	    MBLKL(pdata),
	    &usb_dev_descr,
	    sizeof (usb_dev_descr_t));
	freemsg(pdata);

	if (size < USB_DEV_DESCR_SIZE) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_create_child: get device descriptor size = %lu "
		    "expected size = %u", size, USB_DEV_DESCR_SIZE);
		rval = USB_FAILURE;

		goto done;
	}

	bcopy(&usb_dev_descr, child_ud->usb_dev_descr,
	    sizeof (usb_dev_descr_t));
	child_ud->usb_n_cfgs = usb_dev_descr.bNumConfigurations;

	if (usb_dev_descr.bNumConfigurations == 0) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "device descriptor:\n\t"
		    "l=0x%x type=0x%x USB=0x%x class=0x%x subclass=0x%x\n\t"
		    "protocol=0x%x maxpktsize=0x%x "
		    "Vid=0x%x Pid=0x%x rel=0x%x\n\t"
		    "Mfg=0x%x P=0x%x sn=0x%x #config=0x%x",
		    usb_dev_descr.bLength, usb_dev_descr.bDescriptorType,
		    usb_dev_descr.bcdUSB, usb_dev_descr.bDeviceClass,
		    usb_dev_descr.bDeviceSubClass,
		    usb_dev_descr.bDeviceProtocol,
		    usb_dev_descr.bMaxPacketSize0,
		    usb_dev_descr.idVendor,
		    usb_dev_descr.idProduct, usb_dev_descr.bcdDevice,
		    usb_dev_descr.iManufacturer, usb_dev_descr.iProduct,
		    usb_dev_descr.iSerialNumber,
		    usb_dev_descr.bNumConfigurations);

		rval = USB_FAILURE;

		goto done;
	}

	/* get the device string descriptor(s) */
	usba_get_dev_string_descrs(child_dip, child_ud);

	/* retrieve config cloud for all configurations */
	rval = hubd_get_all_device_config_cloud(hwahcp->hwahc_hubd,
	    child_dip, child_ud);
	if (rval != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "failed to get configuration descriptor(s)");

		goto done;
	}

	/* get the preferred configuration for this device */
	user_conf_index = hubd_select_device_configuration(hwahcp->hwahc_hubd,
	    port, child_dip, child_ud);

	/* Check if the user selected configuration index is in range */
	if ((user_conf_index >= usb_dev_descr.bNumConfigurations) ||
	    (user_conf_index < 0)) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "Configuration index for device idVendor=%d "
		    "idProduct=%d is=%d, and is out of range[0..%d]",
		    usb_dev_descr.idVendor, usb_dev_descr.idProduct,
		    user_conf_index, usb_dev_descr.bNumConfigurations - 1);

		/* treat this as user didn't specify configuration */
		user_conf_index = USBA_DEV_CONFIG_INDEX_UNDEFINED;
	}

	if (user_conf_index == USBA_DEV_CONFIG_INDEX_UNDEFINED) {
		if (child_ud->usb_preferred_driver) {
			/*
			 * It is the job of the "preferred driver" to put the
			 * device in the desired configuration. Till then
			 * put the device in config index 0.
			 */
			/* h_ignore_pwr_budget = TRUE, not care the power */
			if ((rval = usba_hubdi_check_power_budget(dip, child_ud,
			    USB_DEV_DEFAULT_CONFIG_INDEX)) != USB_SUCCESS) {

				goto done;
			}

			child_dip = hubd_ready_device(hwahcp->hwahc_hubd,
			    child_dip, child_ud, USB_DEV_DEFAULT_CONFIG_INDEX);

			/*
			 * Assign the dip before onlining to avoid race
			 * with busctl
			 */
			mutex_enter(&hc_data->hc_mutex);
			hc_data->hc_children_dips[port] = child_dip;
			mutex_exit(&hc_data->hc_mutex);

			(void) usba_bind_driver(child_dip);
		} else {
			/*
			 * loop through all the configurations to see if we
			 * can find a driver for any one config. If not, set
			 * the device in config_index 0
			 */
			rval = USB_FAILURE;
			for (config_index = 0;
			    (config_index < usb_dev_descr.bNumConfigurations) &&
			    (rval != USB_SUCCESS); config_index++) {

				child_dip = hubd_ready_device(
				    hwahcp->hwahc_hubd,
				    child_dip, child_ud, config_index);

				/*
				 * Assign the dip before onlining to avoid race
				 * with busctl
				 */
				mutex_enter(&hc_data->hc_mutex);
				hc_data->hc_children_dips[port] = child_dip;
				mutex_exit(&hc_data->hc_mutex);

				rval = usba_bind_driver(child_dip);

				if (rval == USB_SUCCESS) {
					/* always succeed for WUSB device */
					if ((usba_hubdi_check_power_budget(dip,
					    child_ud, config_index)) !=
					    USB_SUCCESS) {
						rval = USB_FAILURE;

						goto done;
					}
				}
			}

			if (rval != USB_SUCCESS) {
				if ((usba_hubdi_check_power_budget(dip,
				    child_ud, 0)) != USB_SUCCESS) {

					goto done;
				}

				child_dip = hubd_ready_device(
				    hwahcp->hwahc_hubd,
				    child_dip, child_ud, 0);
				mutex_enter(&hc_data->hc_mutex);
				hc_data->hc_children_dips[port] = child_dip;
				mutex_exit(&hc_data->hc_mutex);
			}
		} /* end else loop all configs */
	} else {
		if ((usba_hubdi_check_power_budget(dip, child_ud,
		    (uint_t)user_conf_index)) != USB_SUCCESS) {
			rval = USB_FAILURE;

			goto done;
		}

		child_dip = hubd_ready_device(hwahcp->hwahc_hubd, child_dip,
		    child_ud, (uint_t)user_conf_index);

		/*
		 * Assign the dip before onlining to avoid race
		 * with busctl
		 */
		mutex_enter(&hc_data->hc_mutex);
		hc_data->hc_children_dips[port] = child_dip;
		mutex_exit(&hc_data->hc_mutex);

		(void) usba_bind_driver(child_dip);

		rval = USB_SUCCESS;
	}

	/* workaround for non response after ctrl write */
	usb_pipe_close(child_dip, ph,
	    USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, NULL, NULL);

	if ((rval = usb_pipe_open(child_dip, NULL, NULL,
	    USB_FLAGS_SLEEP | USBA_FLAGS_PRIVILEGED, &ph)) !=
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_CBOPS,
		    hwahcp->hwahc_log_handle,
		    "usb_pipe_open failed (%d)", rval);

		goto done;
	}

done:
	_NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*dev_info));

	ndi_devi_exit(dip, circ);
	ndi_devi_exit(rh_dip, rh_circ);
	ndi_devi_exit(ddi_get_parent(rh_dip), prh_circ);

	(void) devfs_clean(rh_dip, NULL, 0);

	if (rval == USB_SUCCESS) {
		(void) ndi_devi_online(child_dip, 0);

		USB_DPRINTF_L2(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
		    "hwahc_create_child: create timer for child %p",
		    (void *)dev_info);

		mutex_enter(&hc_data->hc_mutex);
		hwahc_start_trust_timer(dev_info);
		mutex_exit(&hc_data->hc_mutex);
	}

	ndi_rele_devi(dip);

	return (rval);
}

/* offline child on a certain port */
static int
hwahc_destroy_child(dev_info_t *dip, usb_port_t port)
{
	hwahc_state_t	*hwahcp;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (USB_INVALID_ARGS);
	}

	hwahc_post_event(hwahcp, port, USBA_EVENT_TAG_HOT_REMOVAL);

	USB_DPRINTF_L3(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_destroy_child: scheduling cleanup");

	/* schedule cleanup thread */
	hubd_schedule_cleanup(hwahcp->hwahc_hubd->h_usba_device->
	    usb_root_hub_dip);

	return (USB_SUCCESS);
}

/*
 * called by cleanup thread to offline child and cleanup child resources
 * Child's callback functions have been called before calling this routine.
 *	dip - hwahc's dip
 */
static int
hwahc_cleanup_child(dev_info_t *dip)
{
	hwahc_state_t	*hwahcp;
	wusb_hc_data_t	*hc_data;
	usb_port_t	port;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (USB_INVALID_ARGS);
	}

	hc_data = &hwahcp->hwahc_hc_data;
	mutex_enter(&hc_data->hc_mutex);
	for (port = 1; port <= hc_data->hc_num_ports; port++) {
		dev_info_t *cdip = hc_data->hc_children_dips[port];

		if (cdip == NULL || DEVI_IS_DEVICE_REMOVED(cdip) == 0) {

			continue;
		}

		/*
		 * child's callback has been called and its dip has been
		 * marked REMOVED. Do further cleanup in hwa driver for
		 * this child.
		 */
		mutex_exit(&hc_data->hc_mutex);
		(void) hwahc_delete_child(dip, port, NDI_DEVI_REMOVE, B_TRUE);
		mutex_enter(&hc_data->hc_mutex);
	}
	mutex_exit(&hc_data->hc_mutex);

	return (USB_SUCCESS);
}

/* offline child and cleanup child resources */
static int
hwahc_delete_child(dev_info_t *dip, usb_port_t port, uint_t flag,
	boolean_t retry)
{
	hwahc_state_t	*hwahcp;
	dev_info_t	*child_dip;
	usba_device_t	*usba_device;
	wusb_hc_data_t	*hc_data;
	int		rval;

	if ((hwahcp = ddi_get_soft_state(hwahc_statep,
	    ddi_get_instance(dip))) == NULL) {

		return (USB_INVALID_ARGS);
	}

	child_dip = hwahc_get_child_dip(hwahcp, port);
	if (child_dip == NULL) {

		return (USB_SUCCESS);
	}

	usba_device = usba_get_usba_device(child_dip);
	hc_data = &hwahcp->hwahc_hc_data;

	USB_DPRINTF_L4(PRINT_MASK_CBOPS, hwahcp->hwahc_log_handle,
	    "hwahc_delete_child: port=%d, dip=0x%p usba_device=0x%p",
	    port, (void *)child_dip, (void *)usba_device);

	if (usba_device) {
		usba_hubdi_incr_power_budget(dip, usba_device);
	}

	/* remove this child's dip. If it's <DS_INITIALIZED, free it */
	rval = usba_destroy_child_devi(child_dip, flag);

	if ((rval == USB_SUCCESS) && (flag & NDI_DEVI_REMOVE)) {
		/*
		 * if the child was still < DS_INITIALIZED
		 * then our bus_unconfig was not called and
		 * we have to zap the child here
		 */
		mutex_enter(&hc_data->hc_mutex);
		if (hc_data->hc_children_dips[port] == child_dip) {
			usba_device_t *ud = hc_data->hc_usba_devices[port];
			wusb_dev_info_t *dev_info = hc_data->hc_dev_infos[port];

			hc_data->hc_children_dips[port] = NULL;
			if (ud) {
				mutex_exit(&hc_data->hc_mutex);

				mutex_enter(&ud->usb_mutex);
				ud->usb_ref_count = 0;
				mutex_exit(&ud->usb_mutex);

				usba_free_usba_device(ud);
				mutex_enter(&hc_data->hc_mutex);
				hc_data->hc_usba_devices[port] = NULL;
			}

			/* free the child's wusb_dev_info data */
			if (dev_info) {
				wusb_secrt_data_t *secrt_data;

				if (dev_info->
				    wdev_secrt_data.secrt_encry_descr) {
					secrt_data = &dev_info->wdev_secrt_data;
					kmem_free(secrt_data->secrt_encry_descr,
					    sizeof (usb_encryption_descr_t) *
					    secrt_data->secrt_n_encry);
				}
				if (dev_info->wdev_uwb_descr) {
					kmem_free(dev_info->wdev_uwb_descr,
					    sizeof (usb_uwb_cap_descr_t));
				}
				kmem_free(dev_info, sizeof (wusb_dev_info_t));
				hc_data->hc_dev_infos[port] = NULL;
			}
		}
		mutex_exit(&hc_data->hc_mutex);
	}

	if ((rval != USB_SUCCESS) && retry) {

		hubd_schedule_cleanup(usba_device->usb_root_hub_dip);
	}

	return (rval);
}

/*
 * Set encryption type for WUSB host, refer to WUSB 1.0/8.5.3.6
 * index = port number - 1
 */
int
hwahc_set_dev_encrypt(usb_pipe_handle_t ph, uint8_t ifc,
	usb_port_t index, wusb_secrt_data_t *secrt_data, uint8_t type)
{
	int16_t			value;
	usb_ctrl_setup_t	setup;
	usb_cr_t		cr;
	usb_cb_flags_t		cb_flags;

	USB_DPRINTF_L4(DPRINT_MASK_WHCDI, whcdi_log_handle,
	    "hwahc_set_dev_encrypt: device index = %d", index);

	if (type == USB_ENC_TYPE_UNSECURE) {
		value = 0;
	} else if (type == USB_ENC_TYPE_CCM_1) {
		if (secrt_data == NULL) {

			return (USB_INVALID_ARGS);
		}

		value = wusb_get_ccm_encryption_value(secrt_data);
		if (value == -1) {
			USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle,
			    "hwahc_set_dev_encrypt: cannot find ccm "
			    "encryption type");

			return (USB_FAILURE);
		}
		USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle,
		    "hwahc_set_dev_encrypt: ccm encryption value is %d",
		    value);
	} else {
		USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle,
		    "hwahc_set_dev_encrypt: unsupported encryption type %d",
		    type);

		return (USB_INVALID_ARGS);
	}

	setup.bmRequestType = USB_DEV_REQ_HOST_TO_DEV |
	    USB_DEV_REQ_TYPE_CLASS | USB_DEV_REQ_RCPT_IF;
	setup.bRequest = USB_REQ_SET_ENCRYPTION;
	setup.wValue = (uint16_t)value;
	setup.wIndex = (index << 8) | ifc;
	setup.wLength = 0;
	setup.attrs = USB_ATTRS_NONE;

	USB_DPRINTF_L2(DPRINT_MASK_WHCDI, whcdi_log_handle,
	    "bmRequestType=0x%x, bRequest=0x%x, wValue=0x%x, wIndex=0x%x",
	    setup.bmRequestType, setup.bRequest, setup.wValue, setup.wIndex);

	return (usb_pipe_ctrl_xfer_wait(ph, &setup, NULL,
	    &cr, &cb_flags, USB_FLAGS_SLEEP));
}