usbvc.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* 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;
}
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;
/*
* Wake up the read threads in case there are any threads are blocking.
* After being waked up, those threads will quit immediately since the
* dev_state is not ONLINE
*/
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_NOSIG);
usbvc_release_access(usbvcp);
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
"usbvc_cpr_suspend: return");
}
/*
* 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");
}
}
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;
ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
/* 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_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle,
"usbvc_read_buf: buf=%p, buf->filled=%d, bfu->len=%d,"
" bp->b_bcount=%ld, bp->b_resid=%lu",
(void *)buf, buf->filled, buf->len, bp->b_bcount, bp->b_resid);
list_remove(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_done, buf);
bcopy(buf->data, bp->b_un.b_addr, buf->filled);
bp->b_private = NULL;
bp->b_resid = bp->b_bcount - buf->filled;
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;
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) {
buf_filling->status = USBVC_BUF_ERR;
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);
}