view usr/src/uts/common/io/usb/clients/video/usbvc/usbvc.c @ 20600:a9ce21b31e86

10081 smatch indenting fixes for usr/src/uts Reviewed by: Toomas Soome <tsoome@me.com> Reviewed by: Peter Tribble <peter.tribble@gmail.com> Reviewed by: Andy Fiddaman <andy@omniosce.org> Approved by: Robert Mustacchi <rm@joyent.com>
author John Levon <john.levon@joyent.com>
date Mon, 14 Jan 2019 19:42:58 +0000
parents fd645929e06e
children 7616bf36dcb7
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Copyright (c) 2018, Joyent, Inc.
 */

/*
 * USB video class driver (usbvc(7D))
 *
 * 1. Overview
 * ------------
 *
 * This driver supports USB video class devices that used to capture video,
 * e.g., some webcams. It is developed according to "USB Device Class
 * Definition for Video Devices" spec. This spec defines detail info needed by
 * designing a USB video device. It is available at:
 * http://www.usb.org/developers/devclass_docs
 *
 * This driver implements:
 *
 *   - V4L2 interfaces for applications to communicate with video devices.
 *     V4L2 is an API that is widely used by video applications, like Ekiga,
 *     luvcview, etc. The API spec is at:
 *     http://www.thedirks.org/v4l2/
 *     This driver is according to V4L2 spec version 0.20
 *
 *   - Video capture function. (Video output is not supported by now.)
 *
 *   - Isochronous transfer for video data. (Bulk transfer is not supported.)
 *
 *   - read & mmap I/O methods for userland video applications to get video
 *     data. Userland video applications can use read() system call directly,
 *     it is the simplest way but not the most efficient way. Applications can
 *     also use mmap() system call to map several bufs (they are linked as a
 *     buf list), and then use some specific ioctls to start/stop isoc polling,
 *     to queue/dequeue bufs.
 *
 * 2. Source and header files
 * ---------------------------
 *
 * There are two source files and three header files for this driver:
 *
 *   - usbvc.c		Main source file, implements usb video class spec.
 *
 *   - usbvc_v4l2.c	V4L2 interface specific code.
 *
 *   - usbvc_var.h	Main header file, includes soft state structure.
 *
 *   - usbvc.h		The descriptors in usb video class spec.
 *
 *   - videodev2.h	This header file is included in V4L2 spec. It defines
 *     ioctls and data structures that used as an interface between video
 *     applications and video drivers. This is the only header file that
 *     usbvc driver should export to userland application.
 *
 * 3. USB video class devices overview
 * -----------------------------------
 * According to UVC spec, there must be one control interface in a UVC device.
 * Control interface is used to receive control commands from user, all the
 * commands are sent through default ctrl pipe. usbvc driver implements V4L2
 * API, so ioctls are implemented to relay user commands to UVC device.
 *
 * There can be no or multiple stream interfaces in a UVC device. Stream
 * interfaces are used to do video data I/O. In practice, if no stream
 * interface, the video device can do nothing since it has no data I/O.
 *
 * usbvc driver parses descriptors of control interface and stream interfaces.
 * The descriptors tell the function layout and the capability of the device.
 * During attach, usbvc driver set up some key data structures according to
 * the descriptors.
 *
 * 4. I/O methods
 * ---------------
 *
 * Userland applications use ioctls to set/get video formats of the device,
 * and control brightness, contrast, image size, etc.
 *
 * Besides implementing standard read I/O method to get video data from
 * the device, usbvc driver also implements some specific ioctls to implement
 * mmap I/O method.
 *
 * A view from userland application: ioctl and mmap flow chart:
 *
 * REQBUFS -> QUERYBUF -> mmap() ->
 *
 *    -> QBUF -> STREAMON -> DQBUF -> process image -> QBUF
 *			       ^			|
 *			       |			|
 *			       |			v
 *			       |---<--------------------
 *
 * The above queue and dequeue buf operations can be stopped by issuing a
 * STREAMOFF ioctl.
 *
 * 5. Device states
 * ----------------
 *
 * The device has four states (refer to usbai.h):
 *
 *	- USB_DEV_ONLINE: In action or ready for action.
 *
 *	- USB_DEV_DISCONNECTED: Hotplug removed, or device not present/correct
 *				on resume (CPR).
 *
 *	- USB_DEV_SUSPENDED: Device has been suspended along with the system.
 *
 *	- USB_DEV_PWRED_DOWN: Device has been powered down.  (Note that this
 *		driver supports only two power states, powered down and
 *		full power.)
 *
 * 6. Serialize
 * -------------
 * In order to avoid race conditions between driver entry points, access to
 * the device is serialized. All the ioctls, and read, open/close are
 * serialized. The functions usbvc_serialize/release_access are implemented
 * for this purpose.
 *
 * 7. PM & CPR
 * ------------
 * PM & CPR are supported. pm_busy_component and pm_idle_component mark
 * the device as busy or idle to the system.
 */

#if defined(lint) && !defined(DEBUG)
#define	DEBUG
#endif

#define	USBDRV_MAJOR_VER	2
#define	USBDRV_MINOR_VER	0

#include <sys/usb/usba.h>
#include <sys/fcntl.h>
#include <sys/cmn_err.h>
#include <sys/usb/clients/video/usbvc/usbvc_var.h>
#include <sys/videodev2.h> /* V4L2 API header file */

/* Descriptors according to USB video class spec */
#include <sys/usb/clients/video/usbvc/usbvc.h>

static uint_t	usbvc_errmask		= (uint_t)PRINT_MASK_ALL;
static uint_t	usbvc_errlevel = 4;
static uint_t	usbvc_instance_debug = (uint_t)-1;

static char	*name = "usbvc";	/* Driver name, used all over. */

/*
 * Function Prototypes
 */

/* Entries */
static int	usbvc_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int	usbvc_attach(dev_info_t *, ddi_attach_cmd_t);
static int	usbvc_detach(dev_info_t *, ddi_detach_cmd_t);
static void	usbvc_cleanup(dev_info_t *, usbvc_state_t *);
static int	usbvc_open(dev_t *, int, int, cred_t *);
static int	usbvc_close(dev_t, int, int, cred_t *);
static int	usbvc_read(dev_t, struct uio *uip_p, cred_t *);
static int	usbvc_strategy(struct buf *);
static void	usbvc_minphys(struct buf *);
static int	usbvc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int	usbvc_devmap(dev_t, devmap_cookie_t, offset_t,
		    size_t, size_t *, uint_t);

/* pm and cpr */
static int	usbvc_power(dev_info_t *, int, int);
static void	usbvc_init_power_mgmt(usbvc_state_t *);
static void	usbvc_destroy_power_mgmt(usbvc_state_t *);
static void	usbvc_pm_busy_component(usbvc_state_t *);
static void	usbvc_pm_idle_component(usbvc_state_t *);
static int	usbvc_pwrlvl0(usbvc_state_t *);
static int	usbvc_pwrlvl1(usbvc_state_t *);
static int	usbvc_pwrlvl2(usbvc_state_t *);
static int	usbvc_pwrlvl3(usbvc_state_t *);
static void	usbvc_cpr_suspend(dev_info_t *);
static void	usbvc_cpr_resume(dev_info_t *);
static void	usbvc_restore_device_state(dev_info_t *, usbvc_state_t *);

/* Events */
static int	usbvc_disconnect_event_cb(dev_info_t *);
static int	usbvc_reconnect_event_cb(dev_info_t *);

/* Sync objs and lists */
static void	usbvc_init_sync_objs(usbvc_state_t *);
static void	usbvc_fini_sync_objs(usbvc_state_t *);
static void	usbvc_init_lists(usbvc_state_t *);
static void	usbvc_fini_lists(usbvc_state_t *);
static void	usbvc_free_ctrl_descr(usbvc_state_t *);
static void	usbvc_free_stream_descr(usbvc_state_t *);

/* Parse descriptors */
static int	usbvc_chk_descr_len(uint8_t, uint8_t, uint8_t,
		    usb_cvs_data_t *);
static usbvc_stream_if_t *usbvc_parse_stream_if(usbvc_state_t *, int);
static int	usbvc_parse_ctrl_if(usbvc_state_t *);
static int	usbvc_parse_stream_ifs(usbvc_state_t *);
static void	usbvc_parse_color_still(usbvc_state_t *,
		    usbvc_format_group_t *, usb_cvs_data_t *, uint_t, uint_t);
static void	usbvc_parse_frames(usbvc_state_t *, usbvc_format_group_t *,
		    usb_cvs_data_t *, uint_t, uint_t);
static int	usbvc_parse_format_group(usbvc_state_t *,
		    usbvc_format_group_t *, usb_cvs_data_t *, uint_t, uint_t);
static int	usbvc_parse_format_groups(usbvc_state_t *, usbvc_stream_if_t *);
static int	usbvc_parse_stream_header(usbvc_state_t *, usbvc_stream_if_t *);

/* read I/O functions */
static int	usbvc_alloc_read_bufs(usbvc_state_t *, usbvc_stream_if_t *);
static int	usbvc_read_buf(usbvc_state_t *, struct buf *);
static void	usbvc_free_read_buf(usbvc_buf_t *);
static void	usbvc_free_read_bufs(usbvc_state_t *, usbvc_stream_if_t *);
static void	usbvc_close_isoc_pipe(usbvc_state_t *, usbvc_stream_if_t *);

/* callbacks */
static void	usbvc_isoc_cb(usb_pipe_handle_t, usb_isoc_req_t *);
static void	usbvc_isoc_exc_cb(usb_pipe_handle_t, usb_isoc_req_t *);

/* Others */
static int	usbvc_set_alt(usbvc_state_t *, usbvc_stream_if_t *);
static int	usbvc_decode_stream_header(usbvc_state_t *, usbvc_buf_grp_t *,
		    mblk_t *, int);
static int	usbvc_serialize_access(usbvc_state_t *, boolean_t);
static void	usbvc_release_access(usbvc_state_t *);
static int		usbvc_set_default_stream_fmt(usbvc_state_t *);

static usb_event_t usbvc_events = {
	usbvc_disconnect_event_cb,
	usbvc_reconnect_event_cb,
	NULL, NULL
};

/* module loading stuff */
struct cb_ops usbvc_cb_ops = {
	usbvc_open,		/* open  */
	usbvc_close,		/* close */
	usbvc_strategy,	/* strategy */
	nulldev,		/* print */
	nulldev,		/* dump */
	usbvc_read,		/* read */
	nodev,			/* write */
	usbvc_ioctl,		/* ioctl */
	usbvc_devmap,		/* devmap */
	nodev,			/* mmap */
	ddi_devmap_segmap,	/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* cb_prop_op */
	NULL,			/* streamtab  */
	D_MP | D_DEVMAP
};

static struct dev_ops usbvc_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* refcnt  */
	usbvc_info,		/* info */
	nulldev,		/* identify */
	nulldev,		/* probe */
	usbvc_attach,		/* attach */
	usbvc_detach,		/* detach */
	nodev,			/* reset */
	&usbvc_cb_ops,	/* driver operations */
	NULL,			/* bus operations */
	usbvc_power,		/* power */
	ddi_quiesce_not_needed,	/* quiesce */
};

static struct modldrv usbvc_modldrv =	{
	&mod_driverops,
	"USB video class driver",
	&usbvc_ops
};

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

/* Soft state structures */
#define	USBVC_INITIAL_SOFT_SPACE	1
static void *usbvc_statep;


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

	if ((rval = ddi_soft_state_init(&usbvc_statep,
	    sizeof (usbvc_state_t), USBVC_INITIAL_SOFT_SPACE)) != 0) {

		return (rval);
	}

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

	return (rval);
}


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

	if ((rval = mod_remove(&modlinkage)) != 0) {

		return (rval);
	}

	ddi_soft_state_fini(&usbvc_statep);

	return (rval);
}


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


/*
 * usbvc_info:
 *	Get minor number, soft state structure, etc.
 */
/*ARGSUSED*/
static int
usbvc_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
			void *arg, void **result)
{
	usbvc_state_t	*usbvcp;
	int error = DDI_FAILURE;

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if ((usbvcp = ddi_get_soft_state(usbvc_statep,
		    getminor((dev_t)arg))) != NULL) {
			*result = usbvcp->usbvc_dip;
			if (*result != NULL) {
				error = DDI_SUCCESS;
			}
		} else {
			*result = NULL;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)(uintptr_t)getminor((dev_t)arg);
		error = DDI_SUCCESS;
		break;
	default:
		break;
	}

	return (error);
}


/*
 * Entry functions.
 */

/*
 * usbvc_attach:
 *	Attach or resume.
 *
 *	For attach, initialize state and device, including:
 *		state variables, locks, device node
 *		device registration with system
 *		power management, hotplugging
 *	For resume, restore device and state
 */
static int
usbvc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int			instance = ddi_get_instance(dip);
	usbvc_state_t		*usbvcp = NULL;
	int			status;

	switch (cmd) {
	case DDI_ATTACH:

		break;
	case DDI_RESUME:
		usbvc_cpr_resume(dip);

		return (DDI_SUCCESS);
	default:

		return (DDI_FAILURE);
	}

	if (ddi_soft_state_zalloc(usbvc_statep, instance) == DDI_SUCCESS) {
		usbvcp = ddi_get_soft_state(usbvc_statep, instance);
	}
	if (usbvcp == NULL)  {

		return (DDI_FAILURE);
	}

	usbvcp->usbvc_dip = dip;

	usbvcp->usbvc_log_handle = usb_alloc_log_hdl(dip,
	    "usbvc", &usbvc_errlevel,
	    &usbvc_errmask, &usbvc_instance_debug, 0);

	USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_attach: enter");

	if ((status = usb_client_attach(dip, USBDRV_VERSION, 0)) !=
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_attach: usb_client_attach failed, error code:%d",
		    status);

		goto fail;
	}

	if ((status = usb_get_dev_data(dip, &usbvcp->usbvc_reg,
	    USB_PARSE_LVL_ALL, 0)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_attach: usb_get_dev_data failed, error code:%d",
		    status);

		goto fail;
	}
	usbvc_init_sync_objs(usbvcp);

	/* create minor node */
	if ((status = ddi_create_minor_node(dip, name, S_IFCHR, instance,
	    "usb_video", 0)) != DDI_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_attach: Error creating minor node, error code:%d",
		    status);

		goto fail;
	}

	mutex_enter(&usbvcp->usbvc_mutex);
	usbvc_init_lists(usbvcp);

	usbvcp->usbvc_default_ph = usbvcp->usbvc_reg->dev_default_ph;

	/* Put online before PM init as can get power managed afterward. */
	usbvcp->usbvc_dev_state = USB_DEV_ONLINE;
	mutex_exit(&usbvcp->usbvc_mutex);

	/* initialize power management */
	usbvc_init_power_mgmt(usbvcp);

	if ((status = usbvc_parse_ctrl_if(usbvcp)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_attach: parse ctrl interface fail, error code:%d",
		    status);

		goto fail;
	}
	if ((status = usbvc_parse_stream_ifs(usbvcp)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_attach: parse stream interfaces fail, error code:%d",
		    status);

		goto fail;
	}
	(void) usbvc_set_default_stream_fmt(usbvcp);

	/* Register for events */
	if ((status = usb_register_event_cbs(dip, &usbvc_events, 0)) !=
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_attach: register_event_cbs failed, error code:%d",
		    status);

		goto fail;
	}

	/* Report device */
	ddi_report_dev(dip);

	return (DDI_SUCCESS);

fail:
	if (usbvcp) {
		usbvc_cleanup(dip, usbvcp);
	}

	return (DDI_FAILURE);
}


/*
 * usbvc_detach:
 *	detach or suspend driver instance
 *
 * Note: in detach, only contention threads is from pm and disconnnect.
 */
static int
usbvc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int		instance = ddi_get_instance(dip);
	usbvc_state_t	*usbvcp = ddi_get_soft_state(usbvc_statep, instance);
	int		rval = USB_FAILURE;

	switch (cmd) {
	case DDI_DETACH:
		mutex_enter(&usbvcp->usbvc_mutex);
		ASSERT((usbvcp->usbvc_drv_state & USBVC_OPEN) == 0);
		mutex_exit(&usbvcp->usbvc_mutex);

		USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_detach: enter for detach");

		usbvc_cleanup(dip, usbvcp);
		rval = USB_SUCCESS;

		break;
	case DDI_SUSPEND:
		USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_detach: enter for suspend");

		usbvc_cpr_suspend(dip);
		rval = USB_SUCCESS;

		break;
	default:

		break;
	}

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


/*
 * usbvc_cleanup:
 *	clean up the driver state for detach
 */
static void
usbvc_cleanup(dev_info_t *dip, usbvc_state_t *usbvcp)
{
	USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "Cleanup: enter");

	if (usbvcp->usbvc_locks_initialized) {

		/* This must be done 1st to prevent more events from coming. */
		usb_unregister_event_cbs(dip, &usbvc_events);

		/*
		 * 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.
		 *
		 * The following ensures that all driver activity has drained.
		 */
		mutex_enter(&usbvcp->usbvc_mutex);
		(void) usbvc_serialize_access(usbvcp, USBVC_SER_NOSIG);
		usbvc_release_access(usbvcp);
		mutex_exit(&usbvcp->usbvc_mutex);

		/* All device activity has died down. */
		usbvc_destroy_power_mgmt(usbvcp);
		mutex_enter(&usbvcp->usbvc_mutex);
		usbvc_fini_lists(usbvcp);
		mutex_exit(&usbvcp->usbvc_mutex);

		ddi_remove_minor_node(dip, NULL);
		usbvc_fini_sync_objs(usbvcp);
	}

	usb_client_detach(dip, usbvcp->usbvc_reg);
	usb_free_log_hdl(usbvcp->usbvc_log_handle);
	ddi_soft_state_free(usbvc_statep, ddi_get_instance(dip));
	ddi_prop_remove_all(dip);
}


/*ARGSUSED*/
static int
usbvc_open(dev_t *devp, int flag, int otyp, cred_t *cred_p)
{
	usbvc_state_t	*usbvcp =
	    ddi_get_soft_state(usbvc_statep, getminor(*devp));

	if (usbvcp == NULL) {

		return (ENXIO);
	}

	/*
	 * Keep it simple: one client at a time.
	 * Exclusive open only
	 */
	mutex_enter(&usbvcp->usbvc_mutex);
	USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_open: enter, dev_stat=%d", usbvcp->usbvc_dev_state);

	if (usbvcp->usbvc_dev_state == USB_DEV_DISCONNECTED) {
		mutex_exit(&usbvcp->usbvc_mutex);

		return (ENODEV);
	}
	if (usbvcp->usbvc_dev_state == USB_DEV_SUSPENDED) {
		mutex_exit(&usbvcp->usbvc_mutex);

		return (EIO);
	}
	if ((usbvcp->usbvc_drv_state & USBVC_OPEN) != 0) {
		mutex_exit(&usbvcp->usbvc_mutex);

		return (EBUSY);
	}
	usbvcp->usbvc_drv_state |= USBVC_OPEN;

	if (usbvc_serialize_access(usbvcp, USBVC_SER_SIG) == 0) {
		usbvcp->usbvc_drv_state &= ~USBVC_OPEN;
		usbvcp->usbvc_serial_inuse = B_FALSE;
		mutex_exit(&usbvcp->usbvc_mutex);

		return (EINTR);
	}

	/* raise power */
	usbvc_pm_busy_component(usbvcp);
	if (usbvcp->usbvc_pm->usbvc_current_power != USB_DEV_OS_FULL_PWR) {
		usbvcp->usbvc_pm->usbvc_raise_power = B_TRUE;
		mutex_exit(&usbvcp->usbvc_mutex);
		(void) pm_raise_power(usbvcp->usbvc_dip,
		    0, USB_DEV_OS_FULL_PWR);
		mutex_enter(&usbvcp->usbvc_mutex);
		usbvcp->usbvc_pm->usbvc_raise_power = B_FALSE;
	}

	/* Device is idle until it is used. */
	usbvc_release_access(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_open: end.");

	return (0);
}


/*ARGSUSED*/
static int
usbvc_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
	usbvc_stream_if_t *strm_if;
	int		if_num;
	usbvc_state_t	*usbvcp =
	    ddi_get_soft_state(usbvc_statep, getminor(dev));

	USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
	    "close: enter");

	mutex_enter(&usbvcp->usbvc_mutex);
	(void) usbvc_serialize_access(usbvcp, USBVC_SER_NOSIG);
	mutex_exit(&usbvcp->usbvc_mutex);

	/* Perform device session cleanup here. */

	USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
	    "close: cleaning up...");

	/*
	 * USBA automatically flushes/resets active non-default pipes
	 * when they are closed.  We can't reset default pipe, but we
	 * can wait for all requests on it from this dip to drain.
	 */
	(void) usb_pipe_drain_reqs(usbvcp->usbvc_dip,
	    usbvcp->usbvc_reg->dev_default_ph, 0,
	    USB_FLAGS_SLEEP, NULL, 0);

	mutex_enter(&usbvcp->usbvc_mutex);
	strm_if = usbvcp->usbvc_curr_strm;
	if (strm_if->start_polling == 1) {
		mutex_exit(&usbvcp->usbvc_mutex);
		usb_pipe_stop_isoc_polling(strm_if->datain_ph, USB_FLAGS_SLEEP);
		mutex_enter(&usbvcp->usbvc_mutex);
		strm_if->start_polling = 0;
	}
	strm_if->stream_on = 0;

	usbvc_close_isoc_pipe(usbvcp, strm_if);
	if_num = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber;
	mutex_exit(&usbvcp->usbvc_mutex);

	/* reset alternate to the default one. */
	(void) usb_set_alt_if(usbvcp->usbvc_dip, if_num, 0,
	    USB_FLAGS_SLEEP, NULL, NULL);
	mutex_enter(&usbvcp->usbvc_mutex);

	usbvc_free_read_bufs(usbvcp, strm_if);

	/* reset the desired read buf number to the default value on close */
	strm_if->buf_read_num = USBVC_DEFAULT_READ_BUF_NUM;

	usbvc_free_map_bufs(usbvcp, strm_if);
	usbvcp->usbvc_drv_state &= ~USBVC_OPEN;

	usbvc_release_access(usbvcp);
	usbvc_pm_idle_component(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	return (0);
}


/*ARGSUSED*/
/* Read isoc data from usb video devices */
static int
usbvc_read(dev_t dev, struct uio *uio_p, cred_t *cred_p)
{
	int			rval;
	usbvc_stream_if_t	*strm_if;
	usbvc_state_t	*usbvcp =
	    ddi_get_soft_state(usbvc_statep, getminor(dev));

	USB_DPRINTF_L4(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
	    "usbvc_read: enter");
	mutex_enter(&usbvcp->usbvc_mutex);
	if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) {
		USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
		    "usbvc_read: Device is not available,"
		    " dev_stat=%d", usbvcp->usbvc_dev_state);
		mutex_exit(&usbvcp->usbvc_mutex);

		return (EFAULT);
	}
	if ((uio_p->uio_fmode & (FNDELAY|FNONBLOCK)) &&
	    (usbvcp->usbvc_serial_inuse != B_FALSE)) {
		USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
		    "usbvc_read: non-blocking read, return fail.");
		mutex_exit(&usbvcp->usbvc_mutex);

		return (EAGAIN);
	}
	if (usbvc_serialize_access(usbvcp, USBVC_SER_SIG) <= 0) {
		USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
		    "usbvc_read: serialize_access failed.");
		rval = EFAULT;

		goto fail;
	}

	/* Get the first stream interface */
	strm_if = usbvcp->usbvc_curr_strm;
	if (!strm_if) {
		USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
		    "usbvc_read: no stream interfaces");
		rval = EFAULT;

		goto fail;
	}

	/*
	 * If it is the first read, open isoc pipe and allocate bufs for
	 * read I/O method.
	 */
	if (strm_if->datain_ph == NULL) {
		if (usbvc_open_isoc_pipe(usbvcp, strm_if) != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_READ,
			    usbvcp->usbvc_log_handle,
			    "usbvc_read: first read, open pipe fail");
			rval = EFAULT;

			goto fail;
		}
		if (usbvc_alloc_read_bufs(usbvcp, strm_if) != USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_READ,
			    usbvcp->usbvc_log_handle,
			    "usbvc_read: allocate rw bufs fail");
			rval = EFAULT;

			goto fail;
		}
	}

	/* start polling if it is not started yet */
	if (strm_if->start_polling != 1) {
		if (usbvc_start_isoc_polling(usbvcp, strm_if, NULL) !=
		    USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_READ,
			    usbvcp->usbvc_log_handle,
			    "usbvc_read: usbvc_start_isoc_polling fail");
			rval = EFAULT;

			goto fail;
		}
		strm_if->start_polling = 1;
	}

	if (list_is_empty(&strm_if->buf_read.uv_buf_done)) {
		USB_DPRINTF_L3(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
		    "usbvc_read: full buf list is empty.");

		if (uio_p->uio_fmode & (FNDELAY | FNONBLOCK)) {
			USB_DPRINTF_L2(PRINT_MASK_READ,
			    usbvcp->usbvc_log_handle, "usbvc_read: fail, "
			    "non-blocking read, done buf is empty.");
			rval = EAGAIN;

			goto fail;
		}

		/* no available buffers, block here */
		while (list_is_empty(&strm_if->buf_read.uv_buf_done)) {
			USB_DPRINTF_L3(PRINT_MASK_READ,
			    usbvcp->usbvc_log_handle,
			    "usbvc_read: wait for done buf");
			if (cv_wait_sig(&usbvcp->usbvc_read_cv,
			    &usbvcp->usbvc_mutex) <= 0) {
				/* no done buf and cv is signaled */
				rval = EINTR;

				goto fail;
			}
			if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) {

				/* Device is disconnected. */
				rval = EINTR;

				goto fail;
			}
		}

	}

	mutex_exit(&usbvcp->usbvc_mutex);
	rval = physio(usbvc_strategy, NULL, dev, B_READ,
	    usbvc_minphys, uio_p);

	mutex_enter(&usbvcp->usbvc_mutex);
	usbvc_release_access(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	return (rval);

fail:
	usbvc_release_access(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	return (rval);
}


/*
 * strategy:
 *	Called through physio to setup and start the transfer.
 */
static int
usbvc_strategy(struct buf *bp)
{
	usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep,
	    getminor(bp->b_edev));

	USB_DPRINTF_L4(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
	    "usbvc_strategy: enter");

	/*
	 * Initialize residual count here in case transfer doesn't even get
	 * started.
	 */
	bp->b_resid = bp->b_bcount;

	/* Needed as this is a character driver. */
	if (bp->b_flags & (B_PHYS | B_PAGEIO)) {
		bp_mapin(bp);
	}

	mutex_enter(&usbvcp->usbvc_mutex);

	/* Make sure device has not been disconnected. */
	if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) {
		USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
		    "usbvc_strategy: device can't be accessed");
		mutex_exit(&usbvcp->usbvc_mutex);

		goto fail;
	}

	/* read data from uv_buf_done list */
	if (usbvc_read_buf(usbvcp, bp) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
		    "usbvc_strategy: read full buf list fail");
		mutex_exit(&usbvcp->usbvc_mutex);

		goto fail;
	}

	mutex_exit(&usbvcp->usbvc_mutex);

	biodone(bp);

	return (0);

fail:
	USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
	    "usbvc_strategy: strategy fail");
	bp->b_private = NULL;

	bioerror(bp, EIO);
	biodone(bp);

	return (0);
}


static void
usbvc_minphys(struct buf *bp)
{
	dev_t			dev = bp->b_edev;
	usbvc_stream_if_t	*strm_if;
	uint32_t		maxsize;
	usbvc_state_t		*usbvcp =
	    ddi_get_soft_state(usbvc_statep, getminor(dev));

	mutex_enter(&usbvcp->usbvc_mutex);
	strm_if = usbvcp->usbvc_curr_strm;
	LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, maxsize);
	USB_DPRINTF_L3(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
	    "usbvc_minphys: max read size=%d", maxsize);

	if (bp->b_bcount > maxsize) {
		bp->b_bcount = maxsize;
	}
	mutex_exit(&usbvcp->usbvc_mutex);
}


/*
 * ioctl entry.
 */
/*ARGSUSED*/
static int
usbvc_ioctl(dev_t dev, int cmd, intptr_t arg,
		int mode, cred_t *cred_p, int *rval_p)
{
	int		rv = 0;
	usbvc_state_t	*usbvcp =
	    ddi_get_soft_state(usbvc_statep, getminor(dev));

	if (usbvcp == NULL) {

		return (ENXIO);
	}
	USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
	    "ioctl enter, cmd=%x", cmd);
	mutex_enter(&usbvcp->usbvc_mutex);
	if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) {
		USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
		    "ioctl: Device is not online,"
		    " dev_stat=%d", usbvcp->usbvc_dev_state);
		mutex_exit(&usbvcp->usbvc_mutex);

		return (EFAULT);
	}
	if (usbvc_serialize_access(usbvcp, USBVC_SER_SIG) <= 0) {
		usbvcp->usbvc_serial_inuse = B_FALSE;
		mutex_exit(&usbvcp->usbvc_mutex);
		USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
		    "serialize_access failed.");

		return (EFAULT);
	}
	mutex_exit(&usbvcp->usbvc_mutex);

	rv = usbvc_v4l2_ioctl(usbvcp, cmd, arg, mode);

	mutex_enter(&usbvcp->usbvc_mutex);
	usbvc_release_access(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
	    "usbvc_ioctl exit");

	return (rv);
}


/* Entry for mmap system call */
static int
usbvc_devmap(dev_t dev, devmap_cookie_t handle, offset_t off,
	size_t len, size_t *maplen, uint_t model)
{
	usbvc_state_t		*usbvcp;
	int			error, i;
	usbvc_buf_t		*buf = NULL;
	usbvc_stream_if_t	*strm_if;
	usbvc_buf_grp_t		*bufgrp;

	usbvcp = ddi_get_soft_state(usbvc_statep, getminor(dev));
	if (usbvcp == NULL) {
		USB_DPRINTF_L2(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle,
		    "usbvc_devmap: usbvcp == NULL");

		return (ENXIO);
	}

	USB_DPRINTF_L3(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle,
	    "devmap: memory map for instance(%d), off=%llx,"
	    "len=%ld, maplen=%ld, model=%d", getminor(dev), off,
	    len, *maplen, model);

	mutex_enter(&usbvcp->usbvc_mutex);
	(void) usbvc_serialize_access(usbvcp, USBVC_SER_NOSIG);
	strm_if = usbvcp->usbvc_curr_strm;
	if (!strm_if) {
		USB_DPRINTF_L2(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle,
		    "usbvc_devmap: No current strm if");
		mutex_exit(&usbvcp->usbvc_mutex);

		return (ENXIO);
	}
	bufgrp = &strm_if->buf_map;
	for (i = 0; i < bufgrp->buf_cnt; i++) {
		if (bufgrp->buf_head[i].v4l2_buf.m.offset == off) {
			buf = &bufgrp->buf_head[i];

			break;
		}
	}
	USB_DPRINTF_L3(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle,
	    "usbvc_devmap: idx=%d", i);
	if (buf == NULL) {
		mutex_exit(&usbvcp->usbvc_mutex);

		return (ENXIO);
	}
	/*
	 * round up len to a multiple of a page size, according to chapter
	 * 10 of "writing device drivers"
	 */
	len = ptob(btopr(len));
	if (len > ptob(btopr(buf->len))) {
		USB_DPRINTF_L2(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle,
		    "usbvc_devmap: len=0x%lx", len);
		mutex_exit(&usbvcp->usbvc_mutex);

		return (ENXIO);
	}
	mutex_exit(&usbvcp->usbvc_mutex);

	error = devmap_umem_setup(handle, usbvcp->usbvc_dip, NULL,
	    buf->umem_cookie, off, len, PROT_ALL, DEVMAP_DEFAULTS, NULL);
	mutex_enter(&usbvcp->usbvc_mutex);
	*maplen = len;
	if (error == 0 && buf->status == USBVC_BUF_INIT) {
		buf->status = USBVC_BUF_MAPPED;
	} else {
		USB_DPRINTF_L3(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle,
		    "usbvc_devmap: devmap_umem_setup, err=%d", error);
	}

	(void) usbvc_release_access(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	return (error);
}

/*
 * pm and cpr
 */

/*
 *  usbvc_power :
 *	Power entry point, the workhorse behind pm_raise_power, pm_lower_power,
 *	usb_req_raise_power and usb_req_lower_power.
 */
/* ARGSUSED */
static int
usbvc_power(dev_info_t *dip, int comp, int level)
{
	usbvc_state_t	*usbvcp;
	usbvc_power_t	*pm;
	int		rval = USB_FAILURE;

	usbvcp = ddi_get_soft_state(usbvc_statep, ddi_get_instance(dip));
	mutex_enter(&usbvcp->usbvc_mutex);
	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_power: enter: level = %d, dev_state: %x",
	    level, usbvcp->usbvc_dev_state);

	if (usbvcp->usbvc_pm == NULL) {

		goto done;
	}

	pm = usbvcp->usbvc_pm;

	/* Check if we are transitioning to a legal power level */
	if (USB_DEV_PWRSTATE_OK(pm->usbvc_pwr_states, level)) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_power: illegal power level = %d "
		    "pwr_states: %x", level, pm->usbvc_pwr_states);

		goto done;
	}
	/*
	 * if we are about to raise power and asked to lower power, fail
	 */
	if (pm->usbvc_raise_power && (level < (int)pm->usbvc_current_power)) {

		goto done;
	}
	switch (level) {
	case USB_DEV_OS_PWR_OFF :
		rval = usbvc_pwrlvl0(usbvcp);

		break;
	case USB_DEV_OS_PWR_1 :
		rval = usbvc_pwrlvl1(usbvcp);

		break;
	case USB_DEV_OS_PWR_2 :
		rval = usbvc_pwrlvl2(usbvcp);

		break;
	case USB_DEV_OS_FULL_PWR :
		rval = usbvc_pwrlvl3(usbvcp);

		break;
	}

done:
	mutex_exit(&usbvcp->usbvc_mutex);

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


/*
 * usbvc_init_power_mgmt:
 *	Initialize power management and remote wakeup functionality.
 *	No mutex is necessary in this function as it's called only by attach.
 */
static void
usbvc_init_power_mgmt(usbvc_state_t *usbvcp)
{
	usbvc_power_t	*usbvcpm;
	uint_t		pwr_states;

	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "init_power_mgmt enter");

	/* Allocate the state structure */
	usbvcpm = kmem_zalloc(sizeof (usbvc_power_t), KM_SLEEP);
	mutex_enter(&usbvcp->usbvc_mutex);
	usbvcp->usbvc_pm = usbvcpm;
	usbvcpm->usbvc_state = usbvcp;
	usbvcpm->usbvc_pm_capabilities = 0;
	usbvcpm->usbvc_current_power = USB_DEV_OS_FULL_PWR;
	mutex_exit(&usbvcp->usbvc_mutex);

	if (usb_create_pm_components(usbvcp->usbvc_dip, &pwr_states) ==
	    USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
		    "usbvc_init_power_mgmt: created PM components");

		if (usb_handle_remote_wakeup(usbvcp->usbvc_dip,
		    USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) {
			usbvcpm->usbvc_wakeup_enabled = 1;
		} else {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_init_power_mgmt:"
			    " remote wakeup not supported");
		}

		mutex_enter(&usbvcp->usbvc_mutex);
		usbvcpm->usbvc_pwr_states = (uint8_t)pwr_states;
		usbvc_pm_busy_component(usbvcp);
		usbvcpm->usbvc_raise_power = B_TRUE;
		mutex_exit(&usbvcp->usbvc_mutex);

		(void) pm_raise_power(
		    usbvcp->usbvc_dip, 0, USB_DEV_OS_FULL_PWR);

		mutex_enter(&usbvcp->usbvc_mutex);
		usbvcpm->usbvc_raise_power = B_FALSE;
		usbvc_pm_idle_component(usbvcp);
		mutex_exit(&usbvcp->usbvc_mutex);

	}
	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_init_power_mgmt: end");
}


/*
 *  usbvc_destroy_power_mgmt:
 *	Shut down and destroy power management and remote wakeup functionality.
 */
static void
usbvc_destroy_power_mgmt(usbvc_state_t *usbvcp)
{
	usbvc_power_t	*pm;
	int		rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "destroy_power_mgmt enter");
	mutex_enter(&usbvcp->usbvc_mutex);
	pm = usbvcp->usbvc_pm;
	if (pm && (usbvcp->usbvc_dev_state != USB_DEV_DISCONNECTED)) {

		usbvc_pm_busy_component(usbvcp);
		if (pm->usbvc_wakeup_enabled) {
			pm->usbvc_raise_power = B_TRUE;
			mutex_exit(&usbvcp->usbvc_mutex);

			/* First bring the device to full power */
			(void) pm_raise_power(usbvcp->usbvc_dip, 0,
			    USB_DEV_OS_FULL_PWR);
			if ((rval = usb_handle_remote_wakeup(
			    usbvcp->usbvc_dip,
			    USB_REMOTE_WAKEUP_DISABLE)) !=
			    USB_SUCCESS) {
				USB_DPRINTF_L2(PRINT_MASK_ATTA,
				    usbvcp->usbvc_log_handle,
				    "usbvc_destroy_power_mgmt: "
				    "Error disabling rmt wakeup: rval = %d",
				    rval);
			}
			mutex_enter(&usbvcp->usbvc_mutex);
			pm->usbvc_raise_power = B_FALSE;

		}
		mutex_exit(&usbvcp->usbvc_mutex);

		/*
		 * Since remote wakeup is disabled now,
		 * no one can raise power
		 * and get to device once power is lowered here.
		 */
		(void) pm_lower_power(usbvcp->usbvc_dip, 0, USB_DEV_OS_PWR_OFF);
		mutex_enter(&usbvcp->usbvc_mutex);
		usbvc_pm_idle_component(usbvcp);
	}

	if (pm) {
		kmem_free(pm, sizeof (usbvc_power_t));
		usbvcp->usbvc_pm = NULL;
	}
	mutex_exit(&usbvcp->usbvc_mutex);
}


static void
usbvc_pm_busy_component(usbvc_state_t *usbvcp)
{
	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_pm_busy_component: enter");

	usbvcp->usbvc_pm->usbvc_pm_busy++;
	mutex_exit(&usbvcp->usbvc_mutex);

	if (pm_busy_component(usbvcp->usbvc_dip, 0) !=
	    DDI_SUCCESS) {
		mutex_enter(&usbvcp->usbvc_mutex);
		USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
		    "usbvc_pm_busy_component: pm busy fail, usbvc_pm_busy=%d",
		    usbvcp->usbvc_pm->usbvc_pm_busy);

		usbvcp->usbvc_pm->usbvc_pm_busy--;
		mutex_exit(&usbvcp->usbvc_mutex);
	}
	mutex_enter(&usbvcp->usbvc_mutex);
	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_pm_busy_component: exit");
}


static void
usbvc_pm_idle_component(usbvc_state_t *usbvcp)
{
	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_pm_idle_component: enter");

	if (usbvcp->usbvc_pm != NULL) {
		mutex_exit(&usbvcp->usbvc_mutex);
		if (pm_idle_component(usbvcp->usbvc_dip, 0) ==
		    DDI_SUCCESS) {
			mutex_enter(&usbvcp->usbvc_mutex);
			ASSERT(usbvcp->usbvc_pm->usbvc_pm_busy > 0);
			usbvcp->usbvc_pm->usbvc_pm_busy--;
			mutex_exit(&usbvcp->usbvc_mutex);
		}
		mutex_enter(&usbvcp->usbvc_mutex);
		USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
		    "usbvc_pm_idle_component: %d",
		    usbvcp->usbvc_pm->usbvc_pm_busy);
	}
}


/*
 * usbvc_pwrlvl0:
 * Functions to handle power transition for OS levels 0 -> 3
 */
static int
usbvc_pwrlvl0(usbvc_state_t *usbvcp)
{
	int rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_pwrlvl0, dev_state: %x", usbvcp->usbvc_dev_state);

	switch (usbvcp->usbvc_dev_state) {
	case USB_DEV_ONLINE:
		/* Deny the powerdown request if the device is busy */
		if (usbvcp->usbvc_pm->usbvc_pm_busy != 0) {
			USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
			    "usbvc_pwrlvl0: usbvc_pm_busy");

			return (USB_FAILURE);
		}

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

		usbvcp->usbvc_dev_state = USB_DEV_PWRED_DOWN;
		usbvcp->usbvc_pm->usbvc_current_power = USB_DEV_OS_PWR_OFF;

		/* FALLTHRU */
	case USB_DEV_DISCONNECTED:
	case USB_DEV_SUSPENDED:
		/* allow a disconnect/cpr'ed device to go to lower power */

		return (USB_SUCCESS);
	case USB_DEV_PWRED_DOWN:
	default:
		USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
		    "usbvc_pwrlvl0: illegal dev state");

		return (USB_FAILURE);
	}
}


/*
 * usbvc_pwrlvl1:
 *	Functions to handle power transition to OS levels -> 2
 */
static int
usbvc_pwrlvl1(usbvc_state_t *usbvcp)
{
	int	rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_pwrlvl1");

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

	return (USB_FAILURE);
}


/*
 * usbvc_pwrlvl2:
 *	Functions to handle power transition to OS levels -> 1
 */
static int
usbvc_pwrlvl2(usbvc_state_t *usbvcp)
{
	int	rval;

	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_pwrlvl2");

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

	return (USB_FAILURE);
}


/*
 * usbvc_pwrlvl3:
 *	Functions to handle power transition to OS level -> 0
 */
static int
usbvc_pwrlvl3(usbvc_state_t *usbvcp)
{
	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_pwrlvl3, dev_stat=%d", usbvcp->usbvc_dev_state);

	switch (usbvcp->usbvc_dev_state) {
	case USB_DEV_PWRED_DOWN:
		/* Issue USB D0 command to the device here */
		(void) usb_set_device_pwrlvl0(usbvcp->usbvc_dip);

		usbvcp->usbvc_dev_state = USB_DEV_ONLINE;
		usbvcp->usbvc_pm->usbvc_current_power =
		    USB_DEV_OS_FULL_PWR;

		/* FALLTHRU */
	case USB_DEV_ONLINE:
		/* we are already in full power */
		/* FALLTHRU */
	case USB_DEV_DISCONNECTED:
	case USB_DEV_SUSPENDED:
		/*
		 * PM framework tries to put us in full power
		 * during system shutdown. If we are disconnected/cpr'ed
		 * return success anyways
		 */

		return (USB_SUCCESS);
	default:
		USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
		    "usbvc_pwrlvl3: illegal dev state");

		return (USB_FAILURE);
	}
}


/*
 * usbvc_cpr_suspend:
 *	Clean up device.
 *	Wait for any IO to finish, then close pipes.
 *	Quiesce device.
 */
static void
usbvc_cpr_suspend(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	usbvc_state_t	*usbvcp = ddi_get_soft_state(usbvc_statep, instance);

	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_cpr_suspend enter");

	mutex_enter(&usbvcp->usbvc_mutex);

	/*
	 * Set dev_state to suspended so other driver threads don't start any
	 * new I/O.
	 */
	usbvcp->usbvc_dev_state = USB_DEV_SUSPENDED;

	mutex_exit(&usbvcp->usbvc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_cpr_suspend: return");
}


/*
 * If the polling has been stopped due to some exceptional errors,
 * we reconfigure the device and start polling again. Only for S/R
 * resume or hotplug reconnect operations.
 */
static int
usbvc_resume_operation(usbvc_state_t *usbvcp)
{
	usbvc_stream_if_t	*strm_if;
	int rv = USB_SUCCESS;

	USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
	    "usbvc_resume_operation: enter");

	mutex_enter(&usbvcp->usbvc_mutex);
	strm_if = usbvcp->usbvc_curr_strm;
	if (!strm_if) {
		mutex_exit(&usbvcp->usbvc_mutex);
		rv = USB_FAILURE;

		return (rv);
	}

	/*
	 * 1) if application has not started STREAMON ioctl yet,
	 *    just return
	 * 2) if application use READ mode, return immediately
	 */
	if (strm_if->stream_on == 0) {
		mutex_exit(&usbvcp->usbvc_mutex);

		return (rv);
	}

	/* isoc pipe is expected to be opened already if (stream_on==1) */
	if (!strm_if->datain_ph) {
		mutex_exit(&usbvcp->usbvc_mutex);
		rv = USB_FAILURE;

		return (rv);
	}

	mutex_exit(&usbvcp->usbvc_mutex);

	/* first commit the parameters negotiated and saved during S_FMT */
	if ((rv = usbvc_vs_set_probe_commit(usbvcp, strm_if,
	    &strm_if->ctrl_pc, VS_COMMIT_CONTROL)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_IOCTL,
		    usbvcp->usbvc_log_handle,
		    "usbvc_resume_operation: set probe failed, rv=%d", rv);

		return (rv);
	}

	mutex_enter(&usbvcp->usbvc_mutex);

	/* Set alt interfaces, must be after probe_commit according to spec */
	if ((rv = usbvc_set_alt(usbvcp, strm_if)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_IOCTL,
		    usbvcp->usbvc_log_handle,
		    "usbvc_resume_operation: set alt failed");
		mutex_exit(&usbvcp->usbvc_mutex);

		return (rv);
	}

	/*
	 * The isoc polling could be stopped by isoc_exc_cb
	 * during suspend or hotplug. Restart it.
	 */
	if (usbvc_start_isoc_polling(usbvcp, strm_if, V4L2_MEMORY_MMAP)
	    != USB_SUCCESS) {
		rv = USB_FAILURE;
		mutex_exit(&usbvcp->usbvc_mutex);

		return (rv);
	}

	strm_if->start_polling = 1;

	mutex_exit(&usbvcp->usbvc_mutex);

	return (rv);
}

/*
 * usbvc_cpr_resume:
 *
 *	usbvc_restore_device_state marks success by putting device back online
 */
static void
usbvc_cpr_resume(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	usbvc_state_t	*usbvcp = ddi_get_soft_state(usbvc_statep, instance);

	USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "resume: enter");

	/*
	 * NOTE: A pm_raise_power in usbvc_restore_device_state will bring
	 * the power-up state of device into synch with the system.
	 */
	mutex_enter(&usbvcp->usbvc_mutex);
	usbvc_restore_device_state(dip, usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);
}


/*
 *  usbvc_restore_device_state:
 *	Called during hotplug-reconnect and resume.
 *		reenable power management
 *		Verify the device is the same as before the disconnect/suspend.
 *		Restore device state
 *		Thaw any IO which was frozen.
 *		Quiesce device.  (Other routines will activate if thawed IO.)
 *		Set device online.
 *		Leave device disconnected if there are problems.
 */
static void
usbvc_restore_device_state(dev_info_t *dip, usbvc_state_t *usbvcp)
{
	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_restore_device_state: enter");

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));

	ASSERT((usbvcp->usbvc_dev_state == USB_DEV_DISCONNECTED) ||
	    (usbvcp->usbvc_dev_state == USB_DEV_SUSPENDED));

	usbvc_pm_busy_component(usbvcp);
	usbvcp->usbvc_pm->usbvc_raise_power = B_TRUE;
	mutex_exit(&usbvcp->usbvc_mutex);
	(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, usbvcp->usbvc_log_handle,
	    USB_LOG_L0, PRINT_MASK_ALL,
	    USB_CHK_BASIC|USB_CHK_CFG, NULL) != USB_SUCCESS) {

		goto fail;
	}

	mutex_enter(&usbvcp->usbvc_mutex);
	usbvcp->usbvc_pm->usbvc_raise_power = B_FALSE;
	usbvcp->usbvc_dev_state = USB_DEV_ONLINE;
	mutex_exit(&usbvcp->usbvc_mutex);

	if (usbvcp->usbvc_pm->usbvc_wakeup_enabled) {

		/* Failure here means device disappeared again. */
		if (usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_ENABLE) !=
		    USB_SUCCESS) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle,
			    "device may or may not be accessible. "
			    "Please verify reconnection");
		}
	}

	if (usbvc_resume_operation(usbvcp) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
		    "usbvc_restore_device_state: can't resume operation");

		goto fail;
	}

	mutex_enter(&usbvcp->usbvc_mutex);

	usbvc_pm_idle_component(usbvcp);

	USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle,
	    "usbvc_restore_device_state: end");

	return;

fail:
	/* change the device state from suspended to disconnected */
	mutex_enter(&usbvcp->usbvc_mutex);
	usbvcp->usbvc_dev_state = USB_DEV_DISCONNECTED;
	usbvc_pm_idle_component(usbvcp);
}


/* Events */

/*
 * usbvc_disconnect_event_cb:
 *	Called when device hotplug-removed.
 *		Close pipes. (This does not attempt to contact device.)
 *		Set state to DISCONNECTED
 */
static int
usbvc_disconnect_event_cb(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	usbvc_state_t	*usbvcp = ddi_get_soft_state(usbvc_statep, instance);

	USB_DPRINTF_L4(PRINT_MASK_HOTPLUG, usbvcp->usbvc_log_handle,
	    "disconnect: enter");

	mutex_enter(&usbvcp->usbvc_mutex);
	/*
	 * Save any state of device or IO in progress required by
	 * usbvc_restore_device_state for proper device "thawing" later.
	 */
	usbvcp->usbvc_dev_state = USB_DEV_DISCONNECTED;

	/*
	 * wake up the read threads in case there are any threads are blocking,
	 * after being waked up, those threads will quit fail immediately since
	 * we have changed the dev_stat.
	 */
	if (usbvcp->usbvc_io_type == V4L2_MEMORY_MMAP) {
		cv_broadcast(&usbvcp->usbvc_mapio_cv);
	} else {
		cv_broadcast(&usbvcp->usbvc_read_cv);
	}
	/* Wait for the other threads to quit */
	(void) usbvc_serialize_access(usbvcp, USBVC_SER_SIG);
	usbvc_release_access(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	return (USB_SUCCESS);
}


/*
 * usbvc_reconnect_event_cb:
 *	Called with device hotplug-inserted
 *		Restore state
 */
static int
usbvc_reconnect_event_cb(dev_info_t *dip)
{
	int		instance = ddi_get_instance(dip);
	usbvc_state_t	*usbvcp = ddi_get_soft_state(usbvc_statep, instance);

	USB_DPRINTF_L4(PRINT_MASK_HOTPLUG, usbvcp->usbvc_log_handle,
	    "reconnect: enter");

	mutex_enter(&usbvcp->usbvc_mutex);
	(void) usbvc_serialize_access(usbvcp, USBVC_SER_SIG);
	usbvc_restore_device_state(dip, usbvcp);
	usbvc_release_access(usbvcp);
	mutex_exit(&usbvcp->usbvc_mutex);

	return (USB_SUCCESS);
}

/* Sync objs and lists */

/*
 * init/fini sync objects during attach
 */
static void
usbvc_init_sync_objs(usbvc_state_t *usbvcp)
{
	mutex_init(&usbvcp->usbvc_mutex, NULL, MUTEX_DRIVER,
	    usbvcp->usbvc_reg->dev_iblock_cookie);

	cv_init(&usbvcp->usbvc_serial_cv, NULL, CV_DRIVER, NULL);
	cv_init(&usbvcp->usbvc_read_cv, NULL, CV_DRIVER, NULL);
	cv_init(&usbvcp->usbvc_mapio_cv, NULL, CV_DRIVER, NULL);

	usbvcp->usbvc_serial_inuse = B_FALSE;

	usbvcp->usbvc_locks_initialized = B_TRUE;
}


static void
usbvc_fini_sync_objs(usbvc_state_t *usbvcp)
{
	cv_destroy(&usbvcp->usbvc_serial_cv);
	cv_destroy(&usbvcp->usbvc_read_cv);
	cv_destroy(&usbvcp->usbvc_mapio_cv);

	mutex_destroy(&usbvcp->usbvc_mutex);
}


static void
usbvc_init_lists(usbvc_state_t *usbvcp)
{
	/* video terminals */
	list_create(&(usbvcp->usbvc_term_list), sizeof (usbvc_terms_t),
	    offsetof(usbvc_terms_t, term_node));

	/* video units */
	list_create(&(usbvcp->usbvc_unit_list), sizeof (usbvc_units_t),
	    offsetof(usbvc_units_t, unit_node));

	/* stream interfaces */
	list_create(&(usbvcp->usbvc_stream_list), sizeof (usbvc_stream_if_t),
	    offsetof(usbvc_stream_if_t, stream_if_node));
}


/*
 * Free all the data structures allocated when parsing descriptors of ctrl
 * and stream interfaces. It is safe to call this function because it always
 * checks the pointer before free mem.
 */
static void
usbvc_fini_lists(usbvc_state_t *usbvcp)
{
	USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
	    "usbvc_fini_lists: enter");

	usbvc_free_ctrl_descr(usbvcp);

	/* Free all video stream structure and the sub-structures */
	usbvc_free_stream_descr(usbvcp);

	USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
	    "usbvc_fini_lists: end");
}


/*
 * Free all the data structures allocated when parsing descriptors of ctrl
 * interface.
 */
static void
usbvc_free_ctrl_descr(usbvc_state_t *usbvcp)
{
	usbvc_terms_t	*term;
	usbvc_units_t	*unit;

	USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
	    "usbvc_free_ctrl_descr: enter");

	if (usbvcp->usbvc_vc_header) {
		kmem_free(usbvcp->usbvc_vc_header, sizeof (usbvc_vc_header_t));
	}

	/* Free all video terminal structure */
	while (!list_is_empty(&usbvcp->usbvc_term_list)) {
			term = list_head(&usbvcp->usbvc_term_list);
			if (term != NULL) {
				list_remove(&(usbvcp->usbvc_term_list), term);
				kmem_free(term, sizeof (usbvc_terms_t));
			}
	}

	/* Free all video unit structure */
	while (!list_is_empty(&usbvcp->usbvc_unit_list)) {
			unit = list_head(&usbvcp->usbvc_unit_list);
			if (unit != NULL) {
				list_remove(&(usbvcp->usbvc_unit_list), unit);
				kmem_free(unit, sizeof (usbvc_units_t));
			}
	}
}


/*
 * Free all the data structures allocated when parsing descriptors of stream
 * interfaces.
 */
static void
usbvc_free_stream_descr(usbvc_state_t *usbvcp)
{
	usbvc_stream_if_t	*strm;
	usbvc_input_header_t	*in_hdr;
	usbvc_output_header_t	*out_hdr;
	uint8_t			fmt_cnt, frm_cnt;

	while (!list_is_empty(&usbvcp->usbvc_stream_list)) {
		USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
		    "usbvc_fini_lists: stream list not empty.");

		strm = list_head(&usbvcp->usbvc_stream_list);
		if (strm != NULL) {

			/* unlink this stream's data structure from the list */
			list_remove(&(usbvcp->usbvc_stream_list), strm);
		} else {

			/* No real stream data structure in the list */
			return;
		}

		in_hdr = strm->input_header;
		out_hdr = strm->output_header;

		if (in_hdr) {
			fmt_cnt = in_hdr->descr->bNumFormats;
		} else if (out_hdr) {
			fmt_cnt = out_hdr->descr->bNumFormats;
		}

		USB_DPRINTF_L3(PRINT_MASK_CLOSE,
		    usbvcp->usbvc_log_handle, "usbvc_fini_lists:"
		    " fmtgrp cnt=%d", fmt_cnt);

		/* Free headers */
		if (in_hdr) {
			kmem_free(in_hdr, sizeof (usbvc_input_header_t));
		}
		if (out_hdr) {
			kmem_free(out_hdr, sizeof (usbvc_output_header_t));
		}

		/* Free format descriptors */
		if (strm->format_group) {
			int i;
			usbvc_format_group_t *fmtgrp;

			for (i = 0; i < fmt_cnt; i++) {
				fmtgrp = &strm->format_group[i];
				if (fmtgrp->format == NULL) {

					break;
				}
				if (fmtgrp->still) {
					kmem_free(fmtgrp->still,
					    sizeof (usbvc_still_image_frame_t));
				}
				frm_cnt = fmtgrp->format->bNumFrameDescriptors;

				USB_DPRINTF_L3(PRINT_MASK_CLOSE,
				    usbvcp->usbvc_log_handle,
				    "usbvc_fini_lists:"
				    " frame cnt=%d", frm_cnt);

				if (fmtgrp->frames) {
					kmem_free(fmtgrp->frames,
					    sizeof (usbvc_frames_t) * frm_cnt);
				}
			}
			kmem_free(strm->format_group,
			    sizeof (usbvc_format_group_t) * fmt_cnt);
		}
		USB_DPRINTF_L3(PRINT_MASK_CLOSE,
		    usbvcp->usbvc_log_handle, "usbvc_fini_lists:"
		    " free stream_if_t");

		kmem_free(strm, sizeof (usbvc_stream_if_t));
	}
}

/*
 * Parse class specific descriptors of the video device
 */

/*
 * Check the length of a class specific descriptor. Make sure cvs_buf_len is
 * not less than the length expected according to uvc spec.
 *
 * Args:
 * - off_num: the cvs_buf offset of the descriptor element that
 *   indicates the number of variable descriptor elements;
 * - size: the size of each variable descriptor element, if zero, then the
 *   size value is offered by off_size;
 * - off_size: the cvs_buf offset of the descriptor element that indicates
 *   the size of each variable descriptor element;
 */
static int
usbvc_chk_descr_len(uint8_t off_num, uint8_t size, uint8_t off_size,
    usb_cvs_data_t *cvs_data)
{
	uchar_t			*cvs_buf;
	uint_t			cvs_buf_len;

	cvs_buf = cvs_data->cvs_buf;
	cvs_buf_len = cvs_data->cvs_buf_len;

	if (size == 0) {
		if (cvs_buf_len > off_size) {
			size = cvs_buf[off_size];
		} else {

			return (USB_FAILURE);
		}
	}
	if (cvs_buf_len < (off_num + 1)) {

		return (USB_FAILURE);
	}

	if (cvs_buf_len < (cvs_buf[off_num] * size + off_num +1)) {

		return (USB_FAILURE);
	}

	return (USB_SUCCESS);
}


/* Parse the descriptors of control interface */
static int
usbvc_parse_ctrl_if(usbvc_state_t *usbvcp)
{
	int			if_num;
	int			cvs_num;
	usb_alt_if_data_t	*if_alt_data;
	usb_cvs_data_t		*cvs_data;
	uchar_t			*cvs_buf;
	uint_t			cvs_buf_len;
	uint16_t		version;

	if_num = usbvcp->usbvc_reg->dev_curr_if;
	if_alt_data = usbvcp->usbvc_reg->dev_curr_cfg->cfg_if[if_num].if_alt;
	cvs_data = if_alt_data->altif_cvs;

	for (cvs_num = 0; cvs_num < if_alt_data->altif_n_cvs; cvs_num++) {
		cvs_buf = cvs_data[cvs_num].cvs_buf;
		cvs_buf_len = cvs_data[cvs_num].cvs_buf_len;
		USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_ctrl_if: cvs_num= %d, cvs_buf_len=%d",
		    cvs_num, cvs_buf_len);

		/*
		 * parse interface cvs descriptors here; by checking
		 * bDescriptorType (cvs_buf[1])
		 */
		if (cvs_buf[1] != CS_INTERFACE) {

			continue;
		}

		/*
		 * Different descriptors in VC interface; according to
		 * bDescriptorSubType (cvs_buf[2])
		 */
		switch (cvs_buf[2]) {
		case VC_HEADER:

			/*
			 * According to uvc spec, there must be one and only
			 * be one header. If more than one, return failure.
			 */
			if (usbvcp->usbvc_vc_header) {

				return (USB_FAILURE);
			}
			/*
			 * Check if it is a valid HEADER descriptor in case of
			 * a device not compliant to uvc spec. This descriptor
			 * is critical, return failure if not a valid one.
			 */
			if (usbvc_chk_descr_len(11, 1, 0, cvs_data) !=
			    USB_SUCCESS) {

				return (USB_FAILURE);
			}
			usbvcp->usbvc_vc_header =
			    (usbvc_vc_header_t *)kmem_zalloc(
			    sizeof (usbvc_vc_header_t), KM_SLEEP);
			usbvcp->usbvc_vc_header->descr =
			    (usbvc_vc_header_descr_t *)&cvs_buf[0];

			LE_TO_UINT16(usbvcp->usbvc_vc_header->descr->bcdUVC,
			    0, version);
			USB_DPRINTF_L3(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if:"
			    " VC header, bcdUVC=%x", version);
			if (usbvcp->usbvc_vc_header->descr->bInCollection ==
			    0) {
				USB_DPRINTF_L3(PRINT_MASK_ATTA,
				    usbvcp->usbvc_log_handle,
				    "usbvc_parse_ctrl_if: no strm interfaces");

				break;
			}

			/* stream interface numbers */
			usbvcp->usbvc_vc_header->baInterfaceNr = &cvs_buf[12];

			break;
		case VC_INPUT_TERMINAL:
		{
			usbvc_terms_t *term;

			/*
			 * Check if it is a valid descriptor in case of a
			 * device not compliant to uvc spec
			 */
			if (cvs_buf_len < USBVC_I_TERM_LEN_MIN) {

				break;
			}
			term = (usbvc_terms_t *)
			    kmem_zalloc(sizeof (usbvc_terms_t), KM_SLEEP);
			term->descr = (usbvc_term_descr_t *)cvs_buf;

			USB_DPRINTF_L3(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: "
			    "input term type=%x", term->descr->wTerminalType);
			if (term->descr->wTerminalType == ITT_CAMERA) {
				if (usbvc_chk_descr_len(14, 1, 0, cvs_data) !=
				    USB_SUCCESS) {
					kmem_free(term, sizeof (usbvc_terms_t));

					break;
				}
				term->bmControls = &cvs_buf[15];
			} else if (cvs_buf_len > 8) { /* other input terms */
				term->bSpecific = &cvs_buf[8];
			}
			list_insert_tail(&(usbvcp->usbvc_term_list), term);

			break;
		}
		case VC_OUTPUT_TERMINAL:
		{
			usbvc_terms_t *term;

			if (cvs_buf_len < USBVC_O_TERM_LEN_MIN) {

				break;
			}
			term = (usbvc_terms_t *)
			    kmem_zalloc(sizeof (usbvc_terms_t), KM_SLEEP);
			term->descr = (usbvc_term_descr_t *)cvs_buf;

			USB_DPRINTF_L3(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if:"
			    " output term id= %x", term->descr->bTerminalID);
			if (cvs_buf_len > 9) {
				term->bSpecific = &cvs_buf[9];
			}
			list_insert_tail(&(usbvcp->usbvc_term_list), term);

			break;
		}
		case VC_PROCESSING_UNIT:
		{
			uint8_t sz;
			usbvc_units_t *unit;

			if (usbvc_chk_descr_len(7, 1, 0, cvs_data) !=
			    USB_SUCCESS) {

				break;
			}

			/* bControlSize */
			sz = cvs_buf[7];

			if ((sz + 8) >= cvs_buf_len) {

				break;
			}
			unit = (usbvc_units_t *)
			    kmem_zalloc(sizeof (usbvc_units_t), KM_SLEEP);

			unit->descr = (usbvc_unit_descr_t *)cvs_buf;

			USB_DPRINTF_L3(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: "
			    "unit type=%x", unit->descr->bDescriptorSubType);

			if (sz != 0) {
				unit->bmControls = &cvs_buf[8];
			}
			unit->iProcessing = cvs_buf[8 + sz];

			/*
			 * video class 1.1 version add one element
			 * (bmVideoStandards) to processing unit descriptor
			 */
			if (cvs_buf_len > (9 + sz)) {
				unit->bmVideoStandards = cvs_buf[9 + sz];
			}
			list_insert_tail(&(usbvcp->usbvc_unit_list), unit);

			break;
		}
		case VC_SELECTOR_UNIT:
		{
			uint8_t  pins;
			usbvc_units_t *unit;

			if (usbvc_chk_descr_len(4, 1, 0, cvs_data) !=
			    USB_SUCCESS) {

				break;
			}
			pins = cvs_buf[4];
			if ((pins + 5) >= cvs_buf_len) {

				break;
			}
			unit = (usbvc_units_t *)
			    kmem_zalloc(sizeof (usbvc_units_t), KM_SLEEP);

			unit->descr = (usbvc_unit_descr_t *)cvs_buf;

			USB_DPRINTF_L3(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: "
			    "unit type=%x", unit->descr->bDescriptorSubType);
			if (pins > 0) {
				unit->baSourceID = &cvs_buf[5];
			}
			unit->iSelector = cvs_buf[5 + pins];

			list_insert_tail(&(usbvcp->usbvc_unit_list), unit);

			break;
		}
		case VC_EXTENSION_UNIT:
		{
			uint8_t  pins, n;
			usbvc_units_t *unit;

			if (usbvc_chk_descr_len(21, 1, 0, cvs_data) !=
			    USB_SUCCESS) {

				break;
			}
			pins = cvs_buf[21];
			if ((pins + 22) >= cvs_buf_len) {

				break;
			}

			/* Size of bmControls */
			n = cvs_buf[pins + 22];

			if (usbvc_chk_descr_len(pins + 22, 1, 0, cvs_data) !=
			    USB_SUCCESS) {

				break;
			}
			if ((23 + pins + n) >= cvs_buf_len) {

				break;
			}
			unit = (usbvc_units_t *)
			    kmem_zalloc(sizeof (usbvc_units_t), KM_SLEEP);

			unit->descr = (usbvc_unit_descr_t *)cvs_buf;

			USB_DPRINTF_L3(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: "
			    "unit type=%x", unit->descr->bDescriptorSubType);
			if (pins != 0) {
				unit->baSourceID = &cvs_buf[22];
			}
			unit->bControlSize = cvs_buf[22 + pins];

			if (unit->bControlSize != 0) {
				unit->bmControls = &cvs_buf[23 + pins];
			}
			unit->iExtension = cvs_buf[23 + pins + n];

			list_insert_tail(&(usbvcp->usbvc_unit_list), unit);

			break;
		}
		default:

			break;
		}
	}

	/*
	 * For webcam which is not compliant to video class specification
	 * and no header descriptor in VC interface, return USB_FAILURE.
	 */
	if (!usbvcp->usbvc_vc_header) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_ctrl_if: no header descriptor");

		return (USB_FAILURE);
	}

	return (USB_SUCCESS);
}


/* Parse all the cvs descriptors in one stream interface. */
usbvc_stream_if_t *
usbvc_parse_stream_if(usbvc_state_t *usbvcp, int if_num)
{
	usb_alt_if_data_t	*if_alt_data;
	uint_t			i, j;
	usbvc_stream_if_t	*strm_if;
	uint16_t		pktsize;
	uint8_t			ep_adr;

	strm_if = (usbvc_stream_if_t *)kmem_zalloc(sizeof (usbvc_stream_if_t),
	    KM_SLEEP);
	strm_if->if_descr = &usbvcp->usbvc_reg->dev_curr_cfg->cfg_if[if_num];
	if_alt_data = strm_if->if_descr->if_alt;
	if (usbvc_parse_stream_header(usbvcp, strm_if) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_stream_if: parse header fail");
		kmem_free(strm_if, sizeof (usbvc_stream_if_t));

		return (NULL);
	}
	if (usbvc_parse_format_groups(usbvcp, strm_if) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_stream_if: parse groups fail");
		kmem_free(strm_if, sizeof (usbvc_stream_if_t));

		return (NULL);
	}

	/* Parse the alternate settings to find the maximum bandwidth. */
	for (i = 0; i < strm_if->if_descr->if_n_alt; i++) {
		if_alt_data = &strm_if->if_descr->if_alt[i];
		for (j = 0; j < if_alt_data->altif_n_ep; j++) {
			ep_adr =
			    if_alt_data->altif_ep[j].ep_descr.bEndpointAddress;
			if (strm_if->input_header != NULL &&
			    ep_adr !=
			    strm_if->input_header->descr->bEndpointAddress) {

				continue;
			}
			if (strm_if->output_header != NULL &&
			    ep_adr !=
			    strm_if->output_header->descr->bEndpointAddress) {

				continue;
			}
			pktsize =
			    if_alt_data->altif_ep[j].ep_descr.wMaxPacketSize;
			pktsize = HS_PKT_SIZE(pktsize);
			if (pktsize > strm_if->max_isoc_payload) {
				strm_if->max_isoc_payload = pktsize;
			}
		}
	}

	/* initialize MJPEC FID toggle */
	strm_if->fid = 0xff;

	/*
	 * initialize desired number of buffers used internally in read() mode
	 */
	strm_if->buf_read_num = USBVC_DEFAULT_READ_BUF_NUM;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_stream_if: return. max_isoc_payload=%x",
	    strm_if->max_isoc_payload);

	return (strm_if);
}


/*
 * Parse all the stream interfaces asociated with the video control interface.
 * This driver will attach to a video control interface on the device,
 * there might be multiple video stream interfaces associated with one video
 * control interface.
 */
static int
usbvc_parse_stream_ifs(usbvc_state_t *usbvcp)
{
	int			i, if_cnt, if_num;
	usbvc_stream_if_t	*strm_if;

	if_cnt = usbvcp->usbvc_vc_header->descr->bInCollection;
	if (if_cnt == 0) {
		ASSERT(list_is_empty(&usbvcp->usbvc_stream_list));
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_stream_ifs: no stream interfaces");

		return (USB_SUCCESS);
	}
	for (i = 0; i < if_cnt; i++) {
		if_num = usbvcp->usbvc_vc_header->baInterfaceNr[i];
		strm_if = usbvc_parse_stream_if(usbvcp, if_num);
		if (strm_if == NULL) {
			USB_DPRINTF_L2(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle, "usbvc_parse_stream_ifs:"
			    " parse stream interface %d failed.", if_num);

			return (USB_FAILURE);
		}
		/* video data buffers */
		list_create(&(strm_if->buf_map.uv_buf_free),
		    sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node));
		list_create(&(strm_if->buf_map.uv_buf_done),
		    sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node));
		list_create(&(strm_if->buf_read.uv_buf_free),
		    sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node));
		list_create(&(strm_if->buf_read.uv_buf_done),
		    sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node));

		list_insert_tail(&(usbvcp->usbvc_stream_list), strm_if);
	}

	/* Make the first stream interface as the default one. */
	usbvcp->usbvc_curr_strm =
	    (usbvc_stream_if_t *)list_head(&usbvcp->usbvc_stream_list);

	return (USB_SUCCESS);
}


/*
 * Parse colorspace descriptor and still image descriptor of a format group.
 * There is only one colorspace or still image descriptor in one format group.
 */
static void
usbvc_parse_color_still(usbvc_state_t *usbvcp, usbvc_format_group_t *fmtgrp,
	usb_cvs_data_t *cvs_data, uint_t cvs_num, uint_t altif_n_cvs)
{
	uint8_t		frame_cnt;
	uint_t		last_frame, i;
	uchar_t		*cvs_buf;
	uint_t			cvs_buf_len;

	frame_cnt = fmtgrp->format->bNumFrameDescriptors;
	last_frame = frame_cnt + cvs_num;

	/*
	 * Find the still image descr and color format descr if there are any.
	 * UVC Spec: only one still image and one color descr is allowed in
	 * one format group.
	 */
	for (i = 1; i <= 2; i++) {
		if ((last_frame + i) >= altif_n_cvs) {

			break;
		}
		cvs_buf = cvs_data[last_frame + i].cvs_buf;
		cvs_buf_len = cvs_data[last_frame + i].cvs_buf_len;

		if (cvs_buf[2] == VS_STILL_IMAGE_FRAME) {
			uint8_t m, n, off;
			usbvc_still_image_frame_t *st;

			if (usbvc_chk_descr_len(4, 4, 0, cvs_data) !=
			    USB_SUCCESS) {

				continue;
			}

			/* Number of Image Size patterns of this format */
			n = cvs_buf[4];

			/* offset of bNumCompressionPattern */
			off = 9 + 4 * n -4;

			if (off >= cvs_buf_len) {

				continue;
			}

			/* Number of compression pattern of this format */
			m = cvs_buf[off];

			if (usbvc_chk_descr_len(m, 1, 0, cvs_data) !=
			    USB_SUCCESS) {

				continue;
			}
			fmtgrp->still = (usbvc_still_image_frame_t *)
			    kmem_zalloc(sizeof (usbvc_still_image_frame_t),
			    KM_SLEEP);
			st = fmtgrp->still;
			st->descr = (usbvc_still_image_frame_descr_t *)cvs_buf;
			n = st->descr->bNumImageSizePatterns;
			if (n > 0) {
				st->width_height =
				    (width_height_t *)&cvs_buf[5];
			}
			st->bNumCompressionPattern = cvs_buf[off];
			if (cvs_buf[off] > 0) {
				st->bCompression = &cvs_buf[off + 1];
			}
		}
		if (cvs_buf[2] == VS_COLORFORMAT) {
			fmtgrp->color = (usbvc_color_matching_descr_t *)cvs_buf;
			fmtgrp->v4l2_color = usbvc_v4l2_colorspace(
			    fmtgrp->color->bColorPrimaries);
		}
	}
	USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_color_still: still=%p, color=%p",
	    (void *)fmtgrp->still, (void *)fmtgrp->color);
}


/*
 * Parse frame descriptors of a format group. There might be multi frame
 * descriptors in one format group.
 */
static void
usbvc_parse_frames(usbvc_state_t *usbvcp, usbvc_format_group_t *fmtgrp,
	usb_cvs_data_t *cvs_data, uint_t cvs_num, uint_t altif_n_cvs)
{
	uint_t		last_frame;
	usbvc_frames_t	*frm;
	usb_cvs_data_t		*cvs;
	uchar_t		*cvs_buf;
	uint_t			cvs_buf_len;
	uint8_t		i;
	uint8_t		frame_cnt = fmtgrp->format->bNumFrameDescriptors;

	USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_format_group: frame_cnt=%d", frame_cnt);

	if (frame_cnt == 0) {
		fmtgrp->frames = NULL;

		return;
	}

	/* All these mem allocated will be freed in cleanup() */
	fmtgrp->frames = (usbvc_frames_t *)
	    kmem_zalloc(sizeof (usbvc_frames_t) * frame_cnt, KM_SLEEP);

	last_frame = frame_cnt + cvs_num;
	cvs_num++;
	i = 0;

	/*
	 * Traverse from the format decr's first frame decr to the the last
	 * frame descr.
	 */
	for (; cvs_num <= last_frame; cvs_num++) {
		USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_frames: cvs_num=%d, i=%d", cvs_num, i);
		if (cvs_num >= altif_n_cvs) {
			USB_DPRINTF_L3(PRINT_MASK_ATTA,
			    usbvcp->usbvc_log_handle,
			    "usbvc_parse_frames: less frames than "
			    "expected, cvs_num=%d, i=%d", cvs_num, i);

			break;
		}
		cvs = &cvs_data[cvs_num];
		cvs_buf = cvs->cvs_buf;
		cvs_buf_len = cvs->cvs_buf_len;
		if (cvs_buf_len < USBVC_FRAME_LEN_MIN) {
			i++;

			continue;
		}
		frm = &fmtgrp->frames[i];
		frm->descr = (usbvc_frame_descr_t *)cvs->cvs_buf;

		/* Descriptor for discrete frame interval */
		if (frm->descr->bFrameIntervalType > 0) {
			if (usbvc_chk_descr_len(25, 4, 0, cvs) != USB_SUCCESS) {
				frm->descr = NULL;
				i++;

				continue;
			}

			frm->dwFrameInterval = (uint8_t *)&cvs_buf[26];
		} else {	/* Continuous interval */
			if (cvs_buf_len < USBVC_FRAME_LEN_CON) {
				frm->descr = NULL;
				i++;

				continue;
			}

			/* Continuous frame intervals */
			LE_TO_UINT32(cvs_buf, 26, frm->dwMinFrameInterval);
			LE_TO_UINT32(cvs_buf, 30, frm->dwMaxFrameInterval);
			LE_TO_UINT32(cvs_buf, 34, frm->dwFrameIntervalStep);
		}

		i++;
	}
	fmtgrp->frame_cnt = i;
	USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_frames: %d frames are actually parsed",
	    fmtgrp->frame_cnt);
}


/* Parse one of the format groups in a stream interface */
static int
usbvc_parse_format_group(usbvc_state_t *usbvcp, usbvc_format_group_t *fmtgrp,
	usb_cvs_data_t *cvs_data, uint_t cvs_num, uint_t altif_n_cvs)
{
	usbvc_format_descr_t *fmt;

	fmt = fmtgrp->format;
	USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_format_group: frame_cnt=%d, cvs_num=%d",
	    fmt->bNumFrameDescriptors, cvs_num);

	switch (fmt->bDescriptorSubType) {
	case VS_FORMAT_UNCOMPRESSED:
		usbvc_parse_color_still(usbvcp, fmtgrp, cvs_data, cvs_num,
		    altif_n_cvs);
		usbvc_parse_frames(usbvcp, fmtgrp, cvs_data, cvs_num,
		    altif_n_cvs);
		fmtgrp->v4l2_bpp = fmt->fmt.uncompressed.bBitsPerPixel / 8;
		fmtgrp->v4l2_pixelformat = usbvc_v4l2_guid2fcc(
		    (uint8_t *)&fmt->fmt.uncompressed.guidFormat);

		break;
	case VS_FORMAT_MJPEG:
		usbvc_parse_color_still(usbvcp, fmtgrp, cvs_data, cvs_num,
		    altif_n_cvs);
		usbvc_parse_frames(usbvcp, fmtgrp, cvs_data, cvs_num,
		    altif_n_cvs);
		fmtgrp->v4l2_bpp = 0;
		fmtgrp->v4l2_pixelformat = V4L2_PIX_FMT_MJPEG;

		break;
	case VS_FORMAT_MPEG2TS:
	case VS_FORMAT_DV:
	case VS_FORMAT_FRAME_BASED:
	case VS_FORMAT_STREAM_BASED:
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_format_group: format not supported yet.");

		return (USB_FAILURE);
	default:
		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_format_group: unknown format.");

		return (USB_FAILURE);
	}

	return (USB_SUCCESS);
}


/* Parse the descriptors belong to one format */
static int
usbvc_parse_format_groups(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	usb_alt_if_data_t	*if_alt_data;
	usb_cvs_data_t		*cvs_data;
	uint8_t			fmtgrp_num, fmtgrp_cnt;
	uchar_t			*cvs_buf;
	uint_t			cvs_num = 0;
	usbvc_format_group_t	*fmtgrp;

	fmtgrp_cnt = 0;
	/*
	 * bNumFormats indicates the number of formats in this stream
	 * interface. On some devices, we see this number is larger than
	 * the truth.
	 */
	if (strm_if->input_header) {
		fmtgrp_cnt = strm_if->input_header->descr->bNumFormats;
	} else if (strm_if->output_header) {
		fmtgrp_cnt = strm_if->output_header->descr->bNumFormats;
	}
	if (!fmtgrp_cnt) {

		return (USB_FAILURE);
	}
	USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_format_groups: fmtgrp_cnt=%d", fmtgrp_cnt);

	fmtgrp = (usbvc_format_group_t *)
	    kmem_zalloc(sizeof (usbvc_format_group_t) * fmtgrp_cnt, KM_SLEEP);

	if_alt_data = strm_if->if_descr->if_alt;
	cvs_data = if_alt_data->altif_cvs;

	for (fmtgrp_num = 0; fmtgrp_num < fmtgrp_cnt &&
	    cvs_num < if_alt_data->altif_n_cvs; cvs_num++) {
		cvs_buf = cvs_data[cvs_num].cvs_buf;
		switch (cvs_buf[2]) {
		case VS_FORMAT_UNCOMPRESSED:
		case VS_FORMAT_MJPEG:
		case VS_FORMAT_MPEG2TS:
		case VS_FORMAT_DV:
		case VS_FORMAT_FRAME_BASED:
		case VS_FORMAT_STREAM_BASED:
			fmtgrp[fmtgrp_num].format =
			    (usbvc_format_descr_t *)cvs_buf;

			/*
			 * Now cvs_data[cvs_num].cvs_buf is format descriptor,
			 * usbvc_parse_format_group will then parse the frame
			 * descriptors following this format descriptor.
			 */
			(void) usbvc_parse_format_group(usbvcp,
			    &fmtgrp[fmtgrp_num], cvs_data, cvs_num,
			    if_alt_data->altif_n_cvs);

			fmtgrp_num++;

			break;
		default:
			break;
		}
	}

	/* Save the number of parsed format groups. */
	strm_if->fmtgrp_cnt = fmtgrp_num;
	USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_format_groups: acctually %d formats parsed",
	    fmtgrp_num);

	/*
	 * If can't find any formats, then free all allocated
	 * usbvc_format_group_t, return failure.
	 */
	if (!(fmtgrp[0].format)) {
		kmem_free(fmtgrp, sizeof (usbvc_format_group_t) * fmtgrp_cnt);
		strm_if->format_group = NULL;

		USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_format_groups: can't find any formats");

		return (USB_FAILURE);
	}
	strm_if->format_group = fmtgrp;
	USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_format_groups: %d format groups parsed", fmtgrp_num);

	return (USB_SUCCESS);
}


/*
 * Parse the input/output header in one stream interface.
 * UVC Spec: there must be one and only one header in one stream interface.
 */
int
usbvc_parse_stream_header(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	usb_alt_if_data_t	*if_alt_data;
	usb_cvs_data_t		*cvs_data;
	int			cvs_num;
	uchar_t			*cvs_buf;
	usbvc_input_header_t	*in_hdr;
	usbvc_output_header_t	*out_hdr;

	if_alt_data = strm_if->if_descr->if_alt;
	cvs_data = if_alt_data->altif_cvs;
	for (cvs_num = 0; cvs_num < if_alt_data->altif_n_cvs; cvs_num++) {
		cvs_buf = cvs_data[cvs_num].cvs_buf;
		USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
		    "usbvc_parse_stream_header: cvs_num= %d", cvs_num);

		/*
		 * parse interface cvs descriptors here; by checking
		 * bDescriptorType (cvs_buf[1])
		 */
		if (cvs_buf[1] != CS_INTERFACE) {

			continue;
		}

		if (cvs_buf[2] == VS_INPUT_HEADER) {
			if (usbvc_chk_descr_len(3, 0, 12, cvs_data) !=
			    USB_SUCCESS) {

				continue;
			}

			strm_if->input_header =
			    (usbvc_input_header_t *)
			    kmem_zalloc(sizeof (usbvc_input_header_t),
			    KM_SLEEP);
			in_hdr = strm_if->input_header;
			in_hdr->descr = (usbvc_input_header_descr_t *)cvs_buf;
			if (in_hdr->descr->bNumFormats > 0) {
				in_hdr->bmaControls = &cvs_buf[13];
			}

			return (USB_SUCCESS);
		} else if (cvs_buf[2] == VS_OUTPUT_HEADER) {
			if (usbvc_chk_descr_len(3, 0, 8, cvs_data) !=
			    USB_SUCCESS) {

				continue;
			}
			strm_if->output_header =
			    (usbvc_output_header_t *)
			    kmem_zalloc(sizeof (usbvc_output_header_t),
			    KM_SLEEP);
			out_hdr = strm_if->output_header;
			out_hdr->descr =
			    (usbvc_output_header_descr_t *)cvs_buf;
			if (out_hdr->descr->bNumFormats > 0) {
				out_hdr->bmaControls = &cvs_buf[13];
			}

			return (USB_SUCCESS);
		} else {

			continue;
		}
	}
	/* Didn't find one header descriptor. */
	USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle,
	    "usbvc_parse_stream_header: FAIL");

	return (USB_FAILURE);
}

/* read I/O functions */

/* Allocate bufs for read I/O method */
static int
usbvc_alloc_read_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	usbvc_buf_t	*buf;
	uchar_t		*data;
	int		i;
	uint32_t	len;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));

	LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, len);
	if (!len) {

		return (USB_FAILURE);
	}
	for (i = 0; i < strm_if->buf_read_num; i++) {
		mutex_exit(&usbvcp->usbvc_mutex);
		buf = (usbvc_buf_t *)kmem_zalloc(sizeof (usbvc_buf_t),
		    KM_SLEEP);
		data = (uchar_t *)kmem_zalloc(len, KM_SLEEP);
		mutex_enter(&usbvcp->usbvc_mutex);
		buf->data = data;
		buf->len = len;
		list_insert_tail(&(strm_if->buf_read.uv_buf_free), buf);
	}
	strm_if->buf_read.buf_cnt = strm_if->buf_read_num;
	USB_DPRINTF_L4(PRINT_MASK_READ, usbvcp->usbvc_log_handle,
	    "read_bufs: %d bufs allocated", strm_if->buf_read.buf_cnt);

	return (USB_SUCCESS);
}


/* Read a done buf, copy data to bp. This function is for read I/O method */
static int
usbvc_read_buf(usbvc_state_t *usbvcp, struct buf *bp)
{
	usbvc_buf_t	*buf;
	int		buf_residue;
	int		len_to_copy;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));

	if (list_is_empty(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_done)) {
		USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
		    "usbvc_read_buf: empty list(uv_buf_done)!");

		return (USB_FAILURE);
	}

	/* read a buf from full list and then put it to free list */
	buf = list_head(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_done);

	USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_read_buf: buf=%p, buf->filled=%d, buf->len=%d,"
	    " buf->len_read=%d bp->b_bcount=%ld, bp->b_resid=%lu",
	    (void *)buf, buf->filled, buf->len, buf->len_read,
	    bp->b_bcount, bp->b_resid);

	ASSERT(buf->len_read <= buf->filled);

	buf_residue = buf->filled - buf->len_read;
	len_to_copy = min(bp->b_bcount, buf_residue);

	bcopy(buf->data + buf->len_read, bp->b_un.b_addr, len_to_copy);
	bp->b_private = NULL;
	buf->len_read += len_to_copy;
	bp->b_resid = bp->b_bcount - len_to_copy;

	if (len_to_copy == buf_residue) {
		/*
		 * the bp can accommodate all the remaining bytes of
		 * the buf. Then we can reuse this buf.
		 */
		buf->len_read = 0;
		list_remove(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_done,
		    buf);
		list_insert_tail(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_free,
		    buf);
	}

	return (USB_SUCCESS);
}


/* Free one buf which is for read/write IO style */
static void
usbvc_free_read_buf(usbvc_buf_t *buf)
{
	if (buf != NULL) {
		if (buf->data) {
			kmem_free(buf->data, buf->len);
		}
		kmem_free(buf, sizeof (usbvc_buf_t));
	}
}


/* Free all bufs which are for read/write IO style */
static void
usbvc_free_read_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	usbvc_buf_t	*buf;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));

	if (!strm_if) {

		return;
	}
	buf = strm_if->buf_read.buf_filling;
	usbvc_free_read_buf(buf);
	strm_if->buf_read.buf_filling = NULL;

	while (!list_is_empty(&strm_if->buf_read.uv_buf_free)) {
		buf = list_head(&strm_if->buf_read.uv_buf_free);
		if (buf != NULL) {
			list_remove(&(strm_if->buf_read.uv_buf_free), buf);
			usbvc_free_read_buf(buf);
		}
	}
	while (!list_is_empty(&strm_if->buf_read.uv_buf_done)) {
		buf = list_head(&strm_if->buf_read.uv_buf_done);
		if (buf != NULL) {
			list_remove(&(strm_if->buf_read.uv_buf_done), buf);
			usbvc_free_read_buf(buf);
		}
	}
	strm_if->buf_read.buf_cnt = 0;
	USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
	    "usbvc_free_read_bufs: return");
}


/*
 * Allocate bufs for mapped I/O , return the number of allocated bufs
 * if success, return 0 if fail.
 */
int
usbvc_alloc_map_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if,
	int buf_cnt, int buf_len)
{
	int		i = 0;
	usbvc_buf_t	*bufs;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_alloc_map_bufs: bufcnt=%d, buflen=%d", buf_cnt, buf_len);
	if (buf_len <= 0 || buf_cnt <= 0) {
		USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
		    "usbvc_alloc_map_bufs: len<=0, cnt<=0");

		return (0);
	}
	mutex_exit(&usbvcp->usbvc_mutex);

	bufs = (usbvc_buf_t *) kmem_zalloc(sizeof (usbvc_buf_t) * buf_cnt,
	    KM_SLEEP);

	mutex_enter(&usbvcp->usbvc_mutex);
	strm_if->buf_map.buf_head = bufs;
	buf_len = ptob(btopr(buf_len));

	mutex_exit(&usbvcp->usbvc_mutex);
	bufs[0].data = ddi_umem_alloc(buf_len * buf_cnt, DDI_UMEM_SLEEP,
	    &bufs[0].umem_cookie);
	mutex_enter(&usbvcp->usbvc_mutex);

	for (i = 0; i < buf_cnt; i++) {
		bufs[i].len = buf_len;
		bufs[i].data = bufs[0].data + (buf_len * i);
		bufs[i].umem_cookie = bufs[0].umem_cookie;
		bufs[i].status = USBVC_BUF_INIT;

		bufs[i].v4l2_buf.index = i;
		bufs[i].v4l2_buf.m.offset = i * bufs[i].len;
		bufs[i].v4l2_buf.length = bufs[i].len;
		bufs[i].v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		bufs[i].v4l2_buf.sequence = 0;
		bufs[i].v4l2_buf.field = V4L2_FIELD_NONE;
		bufs[i].v4l2_buf.memory = V4L2_MEMORY_MMAP;
		bufs[i].v4l2_buf.flags = V4L2_MEMORY_MMAP;

		list_insert_tail(&strm_if->buf_map.uv_buf_free, &bufs[i]);
		USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
		    "usbvc_alloc_map_bufs: prepare %d buffers of %d bytes",
		    buf_cnt, bufs[i].len);
	}
	strm_if->buf_map.buf_cnt = buf_cnt;
	strm_if->buf_map.buf_filling = NULL;

	return (buf_cnt);
}


/* Free all bufs which are for memory map IO style */
void
usbvc_free_map_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	usbvc_buf_t	*buf;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	if (!strm_if) {

		return;
	}
	strm_if->buf_map.buf_filling = NULL;
	while (!list_is_empty(&strm_if->buf_map.uv_buf_free)) {
		buf = (usbvc_buf_t *)list_head(&strm_if->buf_map.uv_buf_free);
		list_remove(&(strm_if->buf_map.uv_buf_free), buf);
	}
	while (!list_is_empty(&strm_if->buf_map.uv_buf_done)) {
		buf = (usbvc_buf_t *)list_head(&strm_if->buf_map.uv_buf_done);
		list_remove(&(strm_if->buf_map.uv_buf_done), buf);
	}
	buf = strm_if->buf_map.buf_head;
	if (!buf) {
		USB_DPRINTF_L2(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
		    "usbvc_free_map_bufs: no data buf need be freed, return");

		return;
	}
	if (buf->umem_cookie) {
		ddi_umem_free(buf->umem_cookie);
	}
	kmem_free(buf, sizeof (usbvc_buf_t) * strm_if->buf_map.buf_cnt);
	strm_if->buf_map.buf_cnt = 0;
	strm_if->buf_map.buf_head = NULL;

	USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
	    "usbvc_free_map_bufs: return");
}


/*
 * Open the isoc pipe, this pipe is for video data transfer
 */
int
usbvc_open_isoc_pipe(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	usb_pipe_policy_t policy;
	int	rval = USB_SUCCESS;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));

	if ((rval = usbvc_set_alt(usbvcp, strm_if)) != USB_SUCCESS) {

		return (rval);
	}
	bzero(&policy, sizeof (usb_pipe_policy_t));
	policy.pp_max_async_reqs = 2;
	mutex_exit(&usbvcp->usbvc_mutex);
	if ((rval = usb_pipe_open(usbvcp->usbvc_dip, strm_if->curr_ep, &policy,
	    USB_FLAGS_SLEEP, &strm_if->datain_ph)) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
		    "usbvc_open_isoc_pipe: open pipe fail");
		mutex_enter(&usbvcp->usbvc_mutex);

		return (rval);
	}
	mutex_enter(&usbvcp->usbvc_mutex);
	strm_if->start_polling = 0;

	strm_if->stream_on = 0;

	USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_open_isoc_pipe: success, datain_ph=%p",
	    (void *)strm_if->datain_ph);

	return (rval);
}


/*
 * Open the isoc pipe
 */
static void
usbvc_close_isoc_pipe(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	if (!strm_if) {
		USB_DPRINTF_L2(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle,
		    "usbvc_close_isoc_pipe: stream interface is NULL");

		return;
	}
	if (strm_if->datain_ph) {
		mutex_exit(&usbvcp->usbvc_mutex);
		usb_pipe_close(usbvcp->usbvc_dip, strm_if->datain_ph,
		    USB_FLAGS_SLEEP, NULL, NULL);
		mutex_enter(&usbvcp->usbvc_mutex);
	}
	strm_if->datain_ph = NULL;
}


/*
 * Start to get video data from isoc pipe in the stream interface,
 * issue isoc req.
 */
int
usbvc_start_isoc_polling(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if,
    uchar_t io_type)
{
	int		rval = USB_SUCCESS;
	uint_t		if_num;
	usb_isoc_req_t	*req;
	ushort_t	pkt_size;
	ushort_t	n_pkt, pkt;
	uint32_t	frame_size;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	pkt_size = HS_PKT_SIZE(strm_if->curr_ep->wMaxPacketSize);
	if_num = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber;
	LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, frame_size);
	n_pkt = (frame_size + (pkt_size) - 1) / (pkt_size);

	USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
	    "usbvc_start_isoc_polling: if_num=%d, alt=%d, n_pkt=%d,"
	    " pkt_size=0x%x, MaxPacketSize=0x%x(Tsac#=%d), frame_size=0x%x",
	    if_num, strm_if->curr_alt, n_pkt, pkt_size,
	    strm_if->curr_ep->wMaxPacketSize,
	    (1 + ((strm_if->curr_ep->wMaxPacketSize>> 11) & 3)),
	    frame_size);

	if (n_pkt > USBVC_MAX_PKTS) {
		n_pkt = USBVC_MAX_PKTS;
	}
	USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
	    "usbvc_start_isoc_polling: n_pkt=%d", n_pkt);

	mutex_exit(&usbvcp->usbvc_mutex);
	if ((req = usb_alloc_isoc_req(usbvcp->usbvc_dip, n_pkt,
	    n_pkt * pkt_size, USB_FLAGS_SLEEP)) != NULL) {
		mutex_enter(&usbvcp->usbvc_mutex);

		/* Initialize the packet descriptor */
		for (pkt = 0; pkt < n_pkt; pkt++) {
			req->isoc_pkt_descr[pkt].isoc_pkt_length = pkt_size;
		}

		req->isoc_pkts_count = n_pkt;

		/*
		 * zero here indicates that HCDs will use
		 * isoc_pkt_descr->isoc_pkt_length to calculate
		 * isoc_pkts_length.
		 */
		req->isoc_pkts_length = 0;
		req->isoc_attributes = USB_ATTRS_ISOC_XFER_ASAP |
		    USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING;
		req->isoc_cb = usbvc_isoc_cb;
		req->isoc_exc_cb = usbvc_isoc_exc_cb;
		usbvcp->usbvc_io_type = io_type;
		req->isoc_client_private = (usb_opaque_t)usbvcp;
		mutex_exit(&usbvcp->usbvc_mutex);
		rval = usb_pipe_isoc_xfer(strm_if->datain_ph, req, 0);
		mutex_enter(&usbvcp->usbvc_mutex);
	} else {
		mutex_enter(&usbvcp->usbvc_mutex);
		USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
		    "usbvc_start_isoc_polling: alloc_isoc_req fail");

		return (USB_FAILURE);
	}

	if (rval != USB_SUCCESS) {
		if (req) {
			usb_free_isoc_req(req);
			req = NULL;
		}
	}
	USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
	    "usbvc_start_isoc_polling: return, rval=%d", rval);

	return (rval);
}

/* callbacks for receiving video data (isco in transfer) */

/*ARGSUSED*/
/* Isoc transfer callback, get video data */
static void
usbvc_isoc_cb(usb_pipe_handle_t ph, usb_isoc_req_t *isoc_req)
{
	usbvc_state_t	*usbvcp =
	    (usbvc_state_t *)isoc_req->isoc_client_private;
	int		i;
	mblk_t		*data = isoc_req->isoc_data;
	usbvc_buf_grp_t	*bufgrp;

	mutex_enter(&usbvcp->usbvc_mutex);

	USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_isoc_cb: rq=0x%p, fno=%" PRId64 ", n_pkts=%u, flag=0x%x,"
	    " data=0x%p, cnt=%d",
	    (void *)isoc_req, isoc_req->isoc_frame_no,
	    isoc_req->isoc_pkts_count, isoc_req->isoc_attributes,
	    (void *)isoc_req->isoc_data, isoc_req->isoc_error_count);

	ASSERT((isoc_req->isoc_cb_flags & USB_CB_INTR_CONTEXT) != 0);
	for (i = 0; i < isoc_req->isoc_pkts_count; i++) {

		USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "\tpkt%d: "
		    "pktsize=%d status=%d resid=%d",
		    i,
		    isoc_req->isoc_pkt_descr[i].isoc_pkt_length,
		    isoc_req->isoc_pkt_descr[i].isoc_pkt_status,
		    isoc_req->isoc_pkt_descr[i].isoc_pkt_actual_length);

		if (isoc_req->isoc_pkt_descr[i].isoc_pkt_status !=
		    USB_CR_OK) {
			USB_DPRINTF_L3(PRINT_MASK_CB,
			    usbvcp->usbvc_log_handle,
			    "record: pkt=%d status=%s", i, usb_str_cr(
			    isoc_req->isoc_pkt_descr[i].isoc_pkt_status));
		}

		if (usbvcp->usbvc_io_type == V4L2_MEMORY_MMAP) {
			bufgrp = &usbvcp->usbvc_curr_strm->buf_map;
		} else {
			bufgrp = &usbvcp->usbvc_curr_strm->buf_read;
		}

		if (isoc_req->isoc_pkt_descr[i].isoc_pkt_actual_length) {
			if (usbvc_decode_stream_header(usbvcp, bufgrp, data,
			    isoc_req->isoc_pkt_descr[i].isoc_pkt_actual_length)
			    != USB_SUCCESS) {
				USB_DPRINTF_L3(PRINT_MASK_CB,
				    usbvcp->usbvc_log_handle, "decode error");
			}
			if (bufgrp->buf_filling &&
			    (bufgrp->buf_filling->status == USBVC_BUF_ERR ||
			    bufgrp->buf_filling->status == USBVC_BUF_DONE)) {

				/* Move the buf to the full list */
				list_insert_tail(&bufgrp->uv_buf_done,
				    bufgrp->buf_filling);

				bufgrp->buf_filling = NULL;

				if (usbvcp->usbvc_io_type == V4L2_MEMORY_MMAP) {
					cv_broadcast(&usbvcp->usbvc_mapio_cv);
				} else {
					cv_broadcast(&usbvcp->usbvc_read_cv);
				}
			}
		}

		data->b_rptr += isoc_req->isoc_pkt_descr[i].isoc_pkt_length;
	}
	mutex_exit(&usbvcp->usbvc_mutex);
	usb_free_isoc_req(isoc_req);
}


/*ARGSUSED*/
static void
usbvc_isoc_exc_cb(usb_pipe_handle_t ph, usb_isoc_req_t *isoc_req)
{
	usbvc_state_t	*usbvcp =
	    (usbvc_state_t *)isoc_req->isoc_client_private;
	usb_cr_t	completion_reason;
	int		rval;
	usbvc_stream_if_t	*strm_if;

	ASSERT(!list_is_empty(&usbvcp->usbvc_stream_list));

	mutex_enter(&usbvcp->usbvc_mutex);

	/* get the first stream interface */
	strm_if = usbvcp->usbvc_curr_strm;

	completion_reason = isoc_req->isoc_completion_reason;

	USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_isoc_exc_cb: ph=0x%p, isoc_req=0x%p, cr=%d",
	    (void *)ph, (void *)isoc_req, completion_reason);

	ASSERT((isoc_req->isoc_cb_flags & USB_CB_INTR_CONTEXT) == 0);

	switch (completion_reason) {
	case USB_CR_STOPPED_POLLING:
	case USB_CR_PIPE_CLOSING:
	case USB_CR_PIPE_RESET:

		break;
	case USB_CR_NO_RESOURCES:
		/*
		 * keep the show going: Since we have the original
		 * request, we just resubmit it
		 */
		rval = usb_pipe_isoc_xfer(strm_if->datain_ph, isoc_req,
		    USB_FLAGS_NOSLEEP);
		USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_isoc_exc_cb: restart capture rval=%d", rval);
		mutex_exit(&usbvcp->usbvc_mutex);

		return;
	default:
		mutex_exit(&usbvcp->usbvc_mutex);
		usb_pipe_stop_isoc_polling(ph, USB_FLAGS_NOSLEEP);
		USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_isoc_exc_cb: stop polling");
		mutex_enter(&usbvcp->usbvc_mutex);
	}
	usb_free_isoc_req(isoc_req);
	strm_if->start_polling = 0;
	USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_isoc_exc_cb: start_polling=%d cr=%d",
	    strm_if->start_polling, completion_reason);
	mutex_exit(&usbvcp->usbvc_mutex);
}

/*
 * Other utility functions
 */

/*
 * Find a proper alternate according to the bandwidth that the current video
 * format need;
 * Set alternate by calling usb_set_alt_if;
 * Called before open pipes in stream interface.
 */
static int
usbvc_set_alt(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if)
{
	usb_alt_if_data_t	*alt;
	uint_t			i, j, if_num;
	uint16_t		pktsize, curr_pktsize;
	uint32_t		bandwidth;
	int			rval = USB_SUCCESS;
	usbvc_input_header_t	*ihd;
	usbvc_output_header_t	*ohd;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));

	LE_TO_UINT32(strm_if->ctrl_pc.dwMaxPayloadTransferSize, 0, bandwidth);
	if (!bandwidth) {
		USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
		    "usbvc_set_alt: bandwidth is not set yet");

		return (USB_FAILURE);
	}
	USB_DPRINTF_L3(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_set_alt: bandwidth=%x", bandwidth);

	strm_if->curr_ep = NULL;
	curr_pktsize = 0xffff;
	ohd = strm_if->output_header;
	ihd = strm_if->input_header;
	/*
	 * Find one alternate setting whose isoc ep's max pktsize is just
	 * enough for the bandwidth.
	 */
	for (i = 0; i < strm_if->if_descr->if_n_alt; i++) {
		alt = &strm_if->if_descr->if_alt[i];

		for (j = 0; j < alt->altif_n_ep; j++) {

			/* if this stream interface is for input */
			if (ihd != NULL &&
			    alt->altif_ep[j].ep_descr.bEndpointAddress !=
			    ihd->descr->bEndpointAddress) {

				continue;
			}
			/*  if this stream interface is for output */
			if (ohd != NULL &&
			    alt->altif_ep[j].ep_descr.bEndpointAddress !=
			    ohd->descr->bEndpointAddress) {

				continue;
			}
			pktsize =
			    alt->altif_ep[j].ep_descr.wMaxPacketSize;
			pktsize = HS_PKT_SIZE(pktsize);
			if (pktsize >= bandwidth && pktsize < curr_pktsize) {
				curr_pktsize = pktsize;
				strm_if->curr_alt = i;
				strm_if->curr_ep = &alt->altif_ep[j].ep_descr;
			}
		}
	}
	if (!strm_if->curr_ep) {
		USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
		    "usbvc_set_alt: can't find a proper ep to satisfy"
		    " the given bandwidth");

		return (USB_FAILURE);
	}
	USB_DPRINTF_L3(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_set_alt: strm_if->curr_alt=%d", strm_if->curr_alt);
	if_num = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber;
	mutex_exit(&usbvcp->usbvc_mutex);
	if ((rval = usb_set_alt_if(usbvcp->usbvc_dip, if_num, strm_if->curr_alt,
	    USB_FLAGS_SLEEP, NULL, NULL)) != USB_SUCCESS) {
		mutex_enter(&usbvcp->usbvc_mutex);
		USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
		    "usbvc_set_alt: usb_set_alt_if fail, if.alt=%d.%d, rval=%d",
		    if_num, strm_if->curr_alt, rval);

		return (rval);
	}
	mutex_enter(&usbvcp->usbvc_mutex);

	USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
	    "usbvc_set_alt: return, if_num=%d, alt=%d",
	    if_num, strm_if->curr_alt);

	return (rval);
}


/*
 * Decode stream header for mjpeg and uncompressed format video data.
 * mjpeg and uncompressed format have the same stream header. See their
 * payload spec, 2.2 and 2.4
 */
static int
usbvc_decode_stream_header(usbvc_state_t *usbvcp, usbvc_buf_grp_t *bufgrp,
	mblk_t *data, int actual_len)
{
	uint32_t len, buf_left, data_len;
	usbvc_stream_if_t *strm_if;
	uchar_t head_flag, head_len;
	usbvc_buf_t *buf_filling;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	USB_DPRINTF_L4(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_decode_stream_header: enter. actual_len=%x", actual_len);

	/* header length check. */
	if (actual_len < 2) {
		USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_decode_stream_header: header is not completed");

		return (USB_FAILURE);
	}
	head_len = data->b_rptr[0];
	head_flag = data->b_rptr[1];

	USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_decode_stream_header: headlen=%x", head_len);

	/* header length check. */
	if (actual_len < head_len) {
		USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_decode_stream_header: actual_len < head_len");

		return (USB_FAILURE);
	}

	/*
	 * If there is no stream data in this packet and this packet is not
	 * used to indicate the end of a frame, then just skip it.
	 */
	if ((actual_len == head_len) && !(head_flag & USBVC_STREAM_EOF)) {
		USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_decode_stream_header: only header, no data");

		return (USB_FAILURE);
	}

	/* Get the first stream interface */
	strm_if = usbvcp->usbvc_curr_strm;

	LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, len);
	USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_decode_stream_header: dwMaxVideoFrameSize=%x, head_flag=%x",
	    len, head_flag);

	/*
	 * if no buf is filling, pick one buf from free list and alloc data
	 * mem for the buf.
	 */
	if (!bufgrp->buf_filling) {
		if (list_is_empty(&bufgrp->uv_buf_free)) {
			strm_if->fid = head_flag & USBVC_STREAM_FID;
			USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
			    "usbvc_decode_stream_header: free list are empty");

			return (USB_FAILURE);

		} else {
			bufgrp->buf_filling =
			    (usbvc_buf_t *)list_head(&bufgrp->uv_buf_free);

			/* unlink from buf free list */
			list_remove(&bufgrp->uv_buf_free, bufgrp->buf_filling);
		}
		bufgrp->buf_filling->filled = 0;
		USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_decode_stream_header: status=%d",
		    bufgrp->buf_filling->status);
		bufgrp->buf_filling->status = USBVC_BUF_EMPTY;
	}
	buf_filling = bufgrp->buf_filling;
	ASSERT(buf_filling->len >= buf_filling->filled);
	buf_left = buf_filling->len - buf_filling->filled;

	/* if no buf room left, then return with a err status */
	if (buf_left == 0) {
		/* buffer full, got an EOF packet(head only, no payload) */
		if ((head_flag & USBVC_STREAM_EOF) &&
		    (actual_len == head_len)) {
			buf_filling->status = USBVC_BUF_DONE;
			USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
			    "usbvc_decode_stream_header: got a EOF packet");

			return (USB_SUCCESS);
		}

		/* Otherwise, mark the buf error and return failure */
		buf_filling->status = USBVC_BUF_ERR;
		USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_decode_stream_header: frame buf full");

		return (USB_FAILURE);
	}

	/* get this sample's data length except header */
	data_len = actual_len - head_len;
	USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_decode_stream_header: fid=%x, len=%x, filled=%x",
	    strm_if->fid, buf_filling->len, buf_filling->filled);

	/* if the first sample for a frame */
	if (buf_filling->filled == 0) {
		/*
		 * Only if it is the frist packet of a frame,
		 * we will begin filling a frame.
		 */
		if (strm_if->fid != 0xff && strm_if->fid ==
		    (head_flag & USBVC_STREAM_FID)) {
			USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
			    "usbvc_decode_stream_header: 1st sample of a frame,"
			    " fid is incorrect.");

			return (USB_FAILURE);
		}
		strm_if->fid = head_flag & USBVC_STREAM_FID;

	/* If in the middle of a frame, fid should be consistent. */
	} else if (strm_if->fid != (head_flag & USBVC_STREAM_FID)) {
		USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
		    "usbvc_decode_stream_header: fid is incorrect.");
		strm_if->fid = head_flag & USBVC_STREAM_FID;
		buf_filling->status = USBVC_BUF_ERR;

		return (USB_FAILURE);
	}
	if (data_len) {
		bcopy((void *)(data->b_rptr + head_len),
		    (void *)(buf_filling->data + buf_filling->filled),
		    min(data_len, buf_left));

		buf_filling->filled += min(data_len, buf_left);
	}

	/* If the last packet for this frame */
	if (head_flag & USBVC_STREAM_EOF) {
		buf_filling->status = USBVC_BUF_DONE;
	}
	if (data_len > buf_left) {
		buf_filling->status = USBVC_BUF_ERR;
	}
	USB_DPRINTF_L4(PRINT_MASK_CB, usbvcp->usbvc_log_handle,
	    "usbvc_decode_stream_header: buf_status=%d", buf_filling->status);

	return (USB_SUCCESS);
}


/*
 * usbvc_serialize_access:
 *    Get the serial synchronization object before returning.
 *
 * Arguments:
 *    usbvcp - Pointer to usbvc state structure
 *    waitsig - Set to:
 *	USBVC_SER_SIG - to wait such that a signal can interrupt
 *	USBVC_SER_NOSIG - to wait such that a signal cannot interrupt
 */
static int
usbvc_serialize_access(usbvc_state_t *usbvcp, boolean_t waitsig)
{
	int rval = 1;

	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));

	while (usbvcp->usbvc_serial_inuse) {
		if (waitsig == USBVC_SER_SIG) {
			rval = cv_wait_sig(&usbvcp->usbvc_serial_cv,
			    &usbvcp->usbvc_mutex);
		} else {
			cv_wait(&usbvcp->usbvc_serial_cv,
			    &usbvcp->usbvc_mutex);
		}
	}
	usbvcp->usbvc_serial_inuse = B_TRUE;

	return (rval);
}


/*
 * usbvc_release_access:
 *    Release the serial synchronization object.
 */
static void
usbvc_release_access(usbvc_state_t *usbvcp)
{
	ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
	usbvcp->usbvc_serial_inuse = B_FALSE;
	cv_broadcast(&usbvcp->usbvc_serial_cv);
}


/* Send req to video control interface to get ctrl */
int
usbvc_vc_get_ctrl(usbvc_state_t *usbvcp, uint8_t req_code, uint8_t entity_id,
    uint16_t cs, uint16_t wlength, mblk_t *data)
{
	usb_cb_flags_t	cb_flags;
	usb_cr_t	cr;
	usb_ctrl_setup_t setup;

	setup.bmRequestType = USBVC_GET_IF;	/* bmRequestType */
	setup.bRequest = req_code;		/* bRequest */
	setup.wValue = cs<<8;
	setup.wIndex = entity_id<<8;
	setup.wLength = wlength;
	setup.attrs = 0;

	if (usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup, &data,
	    &cr, &cb_flags, 0) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_vc_get_ctrl: cmd failed, cr=%d, cb_flags=%x",
		    cr, cb_flags);

		return (USB_FAILURE);
	}

	return (USB_SUCCESS);
}


/* Send req to video control interface to get ctrl */
int
usbvc_vc_set_ctrl(usbvc_state_t *usbvcp, uint8_t req_code,  uint8_t entity_id,
	uint16_t cs, uint16_t wlength, mblk_t *data)
{
	usb_cb_flags_t	cb_flags;
	usb_cr_t	cr;
	usb_ctrl_setup_t setup;

	setup.bmRequestType = USBVC_SET_IF;	/* bmRequestType */
	setup.bRequest = req_code;		/* bRequest */
	setup.wValue = cs<<8;
	setup.wIndex = entity_id<<8;
	setup.wLength = wlength;
	setup.attrs = 0;

	if (usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup, &data,
	    &cr, &cb_flags, 0) != USB_SUCCESS) {
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_vc_set_ctrl: cmd failed, cr=%d, cb_flags=%x",
		    cr, cb_flags);

		return (USB_FAILURE);
	}

	return (USB_SUCCESS);
}


/* Set probe or commit ctrl for video stream interface */
int
usbvc_vs_set_probe_commit(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if,
	usbvc_vs_probe_commit_t *ctrl_pc, uchar_t cs)
{
	mblk_t *data;
	usb_cb_flags_t	cb_flags;
	usb_cr_t	cr;
	usb_ctrl_setup_t setup;
	int rval;

	setup.bmRequestType = USBVC_SET_IF;	/* bmRequestType */
	setup.bRequest = SET_CUR;		/* bRequest */

	/* wValue, VS_PROBE_CONTROL or VS_COMMIT_CONTROL */
	setup.wValue = cs;

	/* UVC Spec: this value must be put to the high byte */
	setup.wValue = setup.wValue << 8;

	setup.wIndex = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber;
	setup.wLength = usbvcp->usbvc_vc_header->descr->bcdUVC[0] ? 34 : 26;
	setup.attrs = 0;

	USB_DPRINTF_L3(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
	    "usbvc_vs_set_probe_commit: wLength=%d", setup.wLength);

	/* Data block */
	if ((data = allocb(setup.wLength, BPRI_HI)) == NULL) {
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_vs_set_probe_commit: allocb failed");

		return (USB_FAILURE);
	}

	bcopy(ctrl_pc, data->b_rptr, setup.wLength);
	data->b_wptr += setup.wLength;

	if ((rval = usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup,
	    &data, &cr, &cb_flags, 0)) != USB_SUCCESS) {
		if (data) {
			freemsg(data);
		}
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_vs_set_probe_commit: fail, rval=%d, cr=%d, "
		    "cb_flags=%x", rval, cr, cb_flags);

		return (rval);
	}
	if (data) {
		freemsg(data);
	}

	return (USB_SUCCESS);
}


/* Get probe ctrl for vodeo stream interface */
int
usbvc_vs_get_probe(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if,
	usbvc_vs_probe_commit_t *ctrl_pc, uchar_t bRequest)
{
	mblk_t *data = NULL;
	usb_cb_flags_t	cb_flags;
	usb_cr_t	cr;
	usb_ctrl_setup_t setup;

	setup.bmRequestType = USBVC_GET_IF;	/* bmRequestType */
	setup.bRequest = bRequest;		/* bRequest */
	setup.wValue = VS_PROBE_CONTROL;	/* wValue, PROBE or COMMIT */
	setup.wValue = setup.wValue << 8;
	setup.wIndex =
	    (uint16_t)strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber;
	setup.wLength = usbvcp->usbvc_vc_header->descr->bcdUVC[0] ? 34 : 26;

	setup.attrs = 0;

	if (usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup, &data,
	    &cr, &cb_flags, 0) != USB_SUCCESS) {
		if (data) {
			freemsg(data);
		}
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_vs_get_probe: cmd failed, cr=%d, cb_flags=%x",
		    cr, cb_flags);

		return (USB_FAILURE);
	}
	bcopy(data->b_rptr, ctrl_pc, setup.wLength);
	if (data) {
		freemsg(data);
	}

	return (USB_SUCCESS);
}


/* Set a default format when open the device */
static int
usbvc_set_default_stream_fmt(usbvc_state_t *usbvcp)
{
	usbvc_vs_probe_commit_t ctrl, ctrl_get;
	usbvc_stream_if_t *strm_if;
	usbvc_format_group_t *curr_fmtgrp;
	uint32_t bandwidth;
	uint8_t  index, i;

	USB_DPRINTF_L4(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
	    "usbvc_set_default_stream_fmt: enter");

	mutex_enter(&usbvcp->usbvc_mutex);
	if (list_is_empty(&usbvcp->usbvc_stream_list)) {
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_set_default_stream_fmt: no stream interface, fail");
		mutex_exit(&usbvcp->usbvc_mutex);

		return (USB_FAILURE);
	}
	bzero((void *)&ctrl, sizeof (usbvc_vs_probe_commit_t));

	/* Get the current stream interface */
	strm_if = usbvcp->usbvc_curr_strm;

	/* Fill the probe commit req data */
	ctrl.bmHint[0] = 0;

	for (i = 0; i < strm_if->fmtgrp_cnt; i++) {
		curr_fmtgrp = &strm_if->format_group[i];

		/*
		 * If v4l2_pixelformat is NULL, then that means there is not
		 * a parsed format in format_group[i].
		 */
		if (!curr_fmtgrp || !curr_fmtgrp->v4l2_pixelformat ||
		    curr_fmtgrp->frame_cnt == 0) {
			USB_DPRINTF_L2(PRINT_MASK_DEVCTRL,
			    usbvcp->usbvc_log_handle,
			    "usbvc_set_default_stream_fmt: no frame, fail");

			continue;
		} else {

			break;
		}
	}
	if (!curr_fmtgrp || curr_fmtgrp->frame_cnt == 0) {
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_set_default_stream_fmt: can't find a fmtgrp"
		    "which has a frame, fail");
		mutex_exit(&usbvcp->usbvc_mutex);

		return (USB_FAILURE);
	}

	ctrl.bFormatIndex = curr_fmtgrp->format->bFormatIndex;

	/* use the first frame descr as default */
	ctrl.bFrameIndex = curr_fmtgrp->frames[0].descr->bFrameIndex;

	/* use bcopy to keep the byte sequence as 32 bit little endian */
	bcopy(&(curr_fmtgrp->frames[0].descr->dwDefaultFrameInterval[0]),
	    &(ctrl.dwFrameInterval[0]), 4);

	mutex_exit(&usbvcp->usbvc_mutex);
	if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl, VS_PROBE_CONTROL)
	    != USB_SUCCESS) {

		return (USB_FAILURE);
	}
	if (usbvc_vs_get_probe(usbvcp, strm_if, &ctrl_get, GET_CUR)
	    != USB_SUCCESS) {

		return (USB_FAILURE);
	}

	mutex_enter(&usbvcp->usbvc_mutex);
	LE_TO_UINT32(strm_if->ctrl_pc.dwMaxPayloadTransferSize, 0, bandwidth);
	USB_DPRINTF_L3(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
	    "usbvc_set_default_stream_fmt: get bandwidth=%x", bandwidth);

	mutex_exit(&usbvcp->usbvc_mutex);
	if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl_get,
	    VS_COMMIT_CONTROL) != USB_SUCCESS) {

		return (USB_FAILURE);
	}

	mutex_enter(&usbvcp->usbvc_mutex);

	/*  it's good to check index here before use it */
	index = ctrl_get.bFormatIndex - curr_fmtgrp->format->bFormatIndex;
	if (index < strm_if->fmtgrp_cnt) {
		strm_if->cur_format_group = &strm_if->format_group[index];
	} else {
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_set_default_stream_fmt: format index out of range");
		mutex_exit(&usbvcp->usbvc_mutex);

		return (USB_FAILURE);
	}

	index = ctrl_get.bFrameIndex -
	    strm_if->cur_format_group->frames[0].descr->bFrameIndex;
	if (index < strm_if->cur_format_group->frame_cnt) {
		strm_if->cur_format_group->cur_frame =
		    &strm_if->cur_format_group->frames[index];
	} else {
		USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle,
		    "usbvc_set_default_stream: frame index out of range");
		mutex_exit(&usbvcp->usbvc_mutex);

		return (USB_FAILURE);
	}

	/*
	 * by now, the video format is set successfully. record the current
	 * setting to strm_if->ctrl_pc
	 */
	bcopy(&ctrl_get, &strm_if->ctrl_pc, sizeof (usbvc_vs_probe_commit_t));

	mutex_exit(&usbvcp->usbvc_mutex);

	return (USB_SUCCESS);
}