/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

	hdl->hdl_ugenp = ugenp;

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

		return (NULL);
	}

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

		return (NULL);
	}

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

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

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

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

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

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

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

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

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

		return (NULL);
	}

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

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

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

	mutex_exit(&ugenp->ug_mutex);

	return ((usb_ugen_hdl_t)hdl);
}


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

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

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


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

	if (usb_ugen_hdl == NULL) {

		return (USB_FAILURE);
	}

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


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

	switch (cmd) {
	case DDI_ATTACH:

		break;
	case DDI_RESUME:
		ugen_cpr_resume(ugenp);

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

		return (USB_FAILURE);
	}

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

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

		goto fail;
	}

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

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

		goto fail;
	}

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

		goto fail;
	}

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

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

	mutex_exit(&ugenp->ug_mutex);

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

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

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

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

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

	return (DDI_FAILURE);
}


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

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

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

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

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

			break;
		default:

			break;
		}
	}

	return (rval);
}


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

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

	if (ugenp->ug_cleanup_flags & UGEN_INIT_LOCKS) {

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

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

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

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


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

		usb_fini_serialization(ugenp->ug_ser_cookie);
	}

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

	ugen_free_devt(ugenp);

	return (USB_SUCCESS);
}


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

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

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

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

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

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

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

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

				rval =	USB_FAILURE;
				break;
			}

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

			usb_release_access(ugenp->ug_ser_cookie);

			mutex_enter(&ugenp->ug_mutex);
		}

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

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

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

	return (rval);
}

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

	ugen_restore_state(ugenp);
}

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

	if (usb_ugen_hdl_impl == NULL) {

		return (USB_FAILURE);
	}

	ugenp = usb_ugen_hdl_impl->hdl_ugenp;

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

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

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

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

		mutex_enter(&ugenp->ug_mutex);
	}


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

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

	return (USB_SUCCESS);
}


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

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

	ugen_restore_state(ugenp);

	return (USB_SUCCESS);
}


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

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

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

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

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

		mutex_exit(&ugenp->ug_mutex);

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

		return;
	}

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

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

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

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

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

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

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


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

	if (usb_ugen_hdl == NULL) {

		return (EINVAL);
	}

	ugenp = usb_ugen_hdl_impl->hdl_ugenp;

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

		return (EINVAL);
	}

	minor_node_type = UGEN_MINOR_TYPE(ugenp, *devp);

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

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

		return (rval);
	}

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

		return (EINTR);
	}

	mutex_enter(&ugenp->ug_mutex);

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

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

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

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

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

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

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

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

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

		break;
	default:
		rval = EINVAL;

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

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

	mutex_exit(&ugenp->ug_mutex);

	usb_release_access(ugenp->ug_ser_cookie);

	return (rval);
}


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

	if (usb_ugen_hdl == NULL) {

		return (EINVAL);
	}

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

		return (EINVAL);
	}

	minor_node_type = UGEN_MINOR_TYPE(ugenp, dev);

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

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

		return (EINTR);
	}

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

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

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

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

		return (EINVAL);
	}

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

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

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

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

	mutex_exit(&ugenp->ug_mutex);

	usb_release_access(ugenp->ug_ser_cookie);

	return (0);
}


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

	if (usb_ugen_hdl == NULL) {

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

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

		return (EINVAL);
	}

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


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

	if (usb_ugen_hdl == NULL) {

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

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

		return (EINVAL);
	}

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


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

	if (usb_ugen_hdl == NULL) {

		return (EINVAL);
	}

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

		return (EINVAL);
	}

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

	mutex_enter(&ugenp->ug_mutex);

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

	*reventsp = 0;

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

				/*
				 * if we are not polling, force another
				 * read to kick off polling
				 */
				mutex_enter(&epp->ep_mutex);
				if ((epp->ep_data) ||
				    ((epp->ep_state &
				    UGEN_EP_STATE_INTR_IN_POLLING_ON) == 0)) {
					*reventsp |= POLLIN;
				} else if (!anyyet) {
					*phpp = &epp->ep_pollhead;
					epp->ep_state |=
					    UGEN_EP_STATE_INTR_IN_POLL_PENDING;
				}
				mutex_exit(&epp->ep_mutex);
			} else {
				/* no poll on other ep nodes */
				*reventsp |= POLLERR;
			}

			break;
		case UGEN_MINOR_DEV_STAT_NODE:
			if (ugenp->ug_ds.dev_stat & UGEN_DEV_STATUS_CHANGED) {
				*reventsp |= POLLIN;
			} else if (!anyyet) {
				*phpp = &ugenp->ug_ds.dev_pollhead;
				ugenp->ug_ds.dev_stat |=
				    UGEN_DEV_STATUS_POLL_PENDING;
			}

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

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

	mutex_exit(&ugenp->ug_mutex);

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

	return (0);
}


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

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

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

		return (EINVAL);
	}

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

	bp_mapin(bp);

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

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

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

		break;
	default:
		rval = EINVAL;

		break;
	}

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

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

	mutex_exit(&ugenp->ug_mutex);

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

	biodone(bp);

	return (0);
}


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

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

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

			break;
		case USB_EP_ATTR_INTR:
		case USB_EP_ATTR_CONTROL:
		default:

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

		break;
	}
}


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

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

	switch (minor_node_type) {
	case UGEN_MINOR_EP_XFER_NODE:
		epp = &ugenp->ug_ep[UGEN_MINOR_EPIDX(ugenp, dev)];
		switch (UGEN_XFER_TYPE(epp)) {
		case USB_EP_ATTR_CONTROL:
			/* read and write must be set, ndelay not allowed */
			if (((flag & (FREAD | FWRITE)) != (FREAD | FWRITE)) ||
			    (flag & (FNDELAY | FNONBLOCK))) {
				rval = EACCES;
			}

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

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

			break;
		default:
			rval = EINVAL;

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

		break;
	case UGEN_MINOR_EP_STAT_NODE:

		break;
	default:
		rval = EINVAL;

		break;
	}

	return (rval);
}


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

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

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

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

		return (USB_FAILURE);
	}

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

						return (USB_FAILURE);
					}
				}
			}
		}
	}

	return (USB_SUCCESS);
}


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

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

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

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

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

	mutex_enter(&epp->ep_mutex);

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

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

	mutex_exit(&epp->ep_mutex);

	/* create minor nodes for all alts */

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


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

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


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

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


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

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

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

		return (USB_FAILURE);
	}

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

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

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

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

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

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

	ASSERT(minor < L_MAXMIN);

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

		return (USB_FAILURE);
	}

	ugen_store_devt(ugenp, minor);

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

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

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

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

	ASSERT(minor < L_MAXMIN);

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

		return (USB_FAILURE);
	}

	ugen_store_devt(ugenp, minor);

	return (USB_SUCCESS);
}


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

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

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


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

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

			return (cfgidx);
		}
	}

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

	return (0);
}


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

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

		mutex_enter(&epp->ep_mutex);

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

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

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

	return (USB_FAILURE);
}


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

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

		mutex_enter(&epp->ep_mutex);

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

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

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

	return (USB_SUCCESS);
}


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

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

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

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

		return (rval);
	}

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

		return (rval);
	}

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

		mutex_enter(&epp->ep_mutex);

		cur_cfgidx = ugen_cfgval2idx(ugenp, cfgval);

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

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

				return (USB_BUSY);
			}

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

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

		mutex_exit(&epp->ep_mutex);
	}

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

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

				return (rval);
			}
		}
	}

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

	return (rval);
}


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

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

			break;
		}
	}
}


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

	mutex_enter(&epp->ep_mutex);

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

	ASSERT(epp->ep_state & UGEN_EP_STATE_ACTIVE);

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

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

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

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

	mutex_exit(&epp->ep_mutex);

	return (usb_rval2errno(rval));
}


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

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

	ugen_epx_close_pipe(ugenp, epp);

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


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

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

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

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

		/* open pipe */
		rval = usb_pipe_open(ugenp->ug_dip,
		    &epp->ep_descr, &epp->ep_pipe_policy,
		    USB_FLAGS_SLEEP, &epp->ep_ph);

		mutex_enter(&epp->ep_mutex);

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

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

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

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

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

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

	return (rval);
}


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

	mutex_enter(&epp->ep_mutex);
	if (epp->ep_state & UGEN_EP_STATE_XFER_OPEN) {
		epp->ep_state &= ~(UGEN_EP_STATE_XFER_OPEN |
				UGEN_EP_STATE_INTR_IN_POLLING_IS_STOPPED |
				UGEN_EP_STATE_INTR_IN_POLLING_ON);

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

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

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

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


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

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

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

		return (EINTR);
	}

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

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

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

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

		break;
	}

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

	mutex_exit(&ugenp->ug_mutex);

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

		return (rval);
	}

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

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

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

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

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

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

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

				mutex_enter(&epp->ep_mutex);

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

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

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

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

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

	return (usb_rval2errno(rval));
}


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

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

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

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

		return (USB_SUCCESS);
	}

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

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

		return (USB_NO_RESOURCES);
	}

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

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

		goto fail;
	}

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

			goto fail;
		default:

			break;
		}
	}

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

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

			goto fail;
		}
	}

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

		goto fail;
	}
done:
	*wait = B_TRUE;

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

	usb_free_ctrl_req(reqp);

	return (rval);
}


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

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

	mutex_enter(&epp->ep_mutex);

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

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

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

		break;
	case USB_CR_PIPE_RESET:

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

		break;
	}

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

	usb_free_ctrl_req(reqp);

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


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

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

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

		return (USB_NO_RESOURCES);
	}

	ASSERT(epp->ep_state & UGEN_EP_STATE_XS_OPEN);

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

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

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

	return (rval);
}


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

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

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

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

			break;
		case USB_CR_PIPE_RESET:

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

	usb_free_bulk_req(reqp);
}


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

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

	*wait = B_FALSE;

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

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

			goto done;
		}
	}

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

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

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

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

	return (rval);
}


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

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

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

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

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

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

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


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

	return (rval);
}


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

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

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


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

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

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


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

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

		goto done;
	}

	mutex_enter(&epp->ep_mutex);

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

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

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

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

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

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

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

		break;
	case USB_CR_PIPE_RESET:
	case USB_CR_STOPPED_POLLING:

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

		break;
	}

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

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

		ugen_epx_intr_IN_poll_wakeup(ugenp, epp);

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

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

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

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

done:
	usb_free_intr_req(reqp);
}


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

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

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

		return (USB_NO_RESOURCES);
	}

	ASSERT(epp->ep_state & UGEN_EP_STATE_XS_OPEN);

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

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

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

	return (rval);
}


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

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

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

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

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

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

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

				break;
			case USB_CR_PIPE_RESET:

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

	usb_free_intr_req(reqp);
}


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

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

	ASSERT(epp->ep_state & UGEN_EP_STATE_ACTIVE);

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

	return (rval);
}


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

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

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

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

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


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

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

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

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

			mutex_exit(&epp->ep_mutex);

			return (EINVAL);
		}

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

			mutex_exit(&epp->ep_mutex);

			return (EINVAL);
		}

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

	return (0);
}


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

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

		return (USB_FAILURE);
	}


	return (USB_SUCCESS);
}


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


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

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

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

		return (EBUSY);
	}
}


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

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


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

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

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

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

		return (0);
	}

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

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

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

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

		break;
	}

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

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

	mutex_exit(&ugenp->ug_mutex);

	return (0);
}


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

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


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

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


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

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

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

		return (USB_FAILURE);
	}

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

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

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

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

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

	ASSERT(minor < L_MAXMIN);

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

		return (USB_FAILURE);
	}

	ugen_store_devt(ugenp, minor);

	return (USB_SUCCESS);
}


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

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

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

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

	return (USB_LC_STAT_UNSPECIFIED_ERR);
}


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

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

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

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

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

		return (-1);
	}
}


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

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

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


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

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

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

	return (USB_FAILURE);
}


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

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


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

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


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


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

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

			break;
		}
	}

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

			break;
		}
	}

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



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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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

	if (usb_ugen_hdl == NULL) {

		return (USB_FAILURE);
	}

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

	if (ugenp->ug_pm == NULL) {

		return (USB_SUCCESS);
	}

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

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

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

		goto done;
	}

	pm = ugenp->ug_pm;

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

		goto done;
	}

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

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

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

			break;
		default:
			rval = USB_SUCCESS;

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

			break;
		default:
			ugenp->ug_dev_state = USB_DEV_ONLINE;

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

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

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

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

	return (rval);
}


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

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

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

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

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

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


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

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

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

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


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

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

	t = ugen_devt_list.list_next;

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

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


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

	mutex_enter(&ugen_devt_list_mutex);

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

			mutex_exit(&ugen_devt_list_mutex);

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

	t = ugen_devt_list.list_next;

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

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

	return (ugenp);
}


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

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

		next = e->list_next;

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

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