/*
* 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
* 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
*/
/*
*/
/*
* 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:
*
* 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:
* 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
*
* 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
* ---------------
*
* 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.
*
* 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
* 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.
*/
#define DEBUG
#endif
#define USBDRV_MINOR_VER 0
/* Descriptors according to USB video class spec */
/*
* Function Prototypes
*/
/* Entries */
static int usbvc_strategy(struct buf *);
static void usbvc_minphys(struct buf *);
/* 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 *);
/* 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 */
usb_cvs_data_t *);
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 *,
static int usbvc_parse_format_group(usbvc_state_t *,
/* read I/O functions */
static void usbvc_free_read_buf(usbvc_buf_t *);
/* callbacks */
/* Others */
mblk_t *, int);
static void usbvc_release_access(usbvc_state_t *);
static int usbvc_set_default_stream_fmt(usbvc_state_t *);
};
/* module loading stuff */
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 */
};
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 */
};
"USB video class driver",
};
};
/* Soft state structures */
static void *usbvc_statep;
/*
* Module-wide initialization routine.
*/
int
_init(void)
{
int rval;
sizeof (usbvc_state_t), USBVC_INITIAL_SOFT_SPACE)) != 0) {
return (rval);
}
}
return (rval);
}
/*
* Module-wide tear-down routine.
*/
int
_fini(void)
{
int rval;
return (rval);
}
return (rval);
}
int
{
}
/*
* usbvc_info:
* Get minor number, soft state structure, etc.
*/
/*ARGSUSED*/
static int
{
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
}
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
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
{
int status;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
return (DDI_FAILURE);
}
"usbvc", &usbvc_errlevel,
&usbvc_errmask, &usbvc_instance_debug, 0);
"usbvc_attach: enter");
USB_SUCCESS) {
"usbvc_attach: usb_client_attach failed, error code:%d",
status);
goto fail;
}
USB_PARSE_LVL_ALL, 0)) != USB_SUCCESS) {
"usbvc_attach: usb_get_dev_data failed, error code:%d",
status);
goto fail;
}
/* create minor node */
"usb_video", 0)) != DDI_SUCCESS) {
"usbvc_attach: Error creating minor node, error code:%d",
status);
goto fail;
}
/* Put online before PM init as can get power managed afterward. */
/* initialize power management */
"usbvc_attach: parse ctrl interface fail, error code:%d",
status);
goto fail;
}
"usbvc_attach: parse stream interfaces fail, error code:%d",
status);
goto fail;
}
(void) usbvc_set_default_stream_fmt(usbvcp);
/* Register for events */
USB_SUCCESS) {
"usbvc_attach: register_event_cbs failed, error code:%d",
status);
goto fail;
}
/* Report device */
return (DDI_SUCCESS);
fail:
if (usbvcp) {
}
return (DDI_FAILURE);
}
/*
* usbvc_detach:
* detach or suspend driver instance
*
* Note: in detach, only contention threads is from pm and disconnnect.
*/
static int
{
switch (cmd) {
case DDI_DETACH:
"usbvc_detach: enter for detach");
rval = USB_SUCCESS;
break;
case DDI_SUSPEND:
"usbvc_detach: enter for suspend");
rval = USB_SUCCESS;
break;
default:
break;
}
}
/*
* usbvc_cleanup:
* clean up the driver state for detach
*/
static void
{
"Cleanup: enter");
if (usbvcp->usbvc_locks_initialized) {
/* This must be done 1st to prevent more events from coming. */
/*
* 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.
*/
/* All device activity has died down. */
}
}
/*ARGSUSED*/
static int
{
return (ENXIO);
}
/*
* Keep it simple: one client at a time.
* Exclusive open only
*/
return (ENODEV);
}
return (EIO);
}
return (EBUSY);
}
return (EINTR);
}
/* raise power */
0, USB_DEV_OS_FULL_PWR);
}
/* Device is idle until it is used. */
"usbvc_open: end.");
return (0);
}
/*ARGSUSED*/
static int
{
int if_num;
"close: enter");
/* Perform device session cleanup here. */
"close: cleaning up...");
/*
* when they are closed. We can't reset default pipe, but we
* can wait for all requests on it from this dip to drain.
*/
USB_FLAGS_SLEEP, NULL, 0);
strm_if->start_polling = 0;
}
/* reset alternate to the default one. */
/* reset the desired read buf number to the default value on close */
return (0);
}
/*ARGSUSED*/
/* Read isoc data from usb video devices */
static int
{
int rval;
"usbvc_read: enter");
"usbvc_read: Device is not available,"
return (EFAULT);
}
"usbvc_read: non-blocking read, return fail.");
return (EAGAIN);
}
"usbvc_read: serialize_access failed.");
goto fail;
}
/* Get the first stream interface */
if (!strm_if) {
"usbvc_read: no stream interfaces");
goto fail;
}
/*
* If it is the first read, open isoc pipe and allocate bufs for
* read I/O method.
*/
"usbvc_read: first read, open pipe fail");
goto fail;
}
"usbvc_read: allocate rw bufs fail");
goto fail;
}
}
/* start polling if it is not started yet */
USB_SUCCESS) {
"usbvc_read: usbvc_start_isoc_polling fail");
goto fail;
}
}
"usbvc_read: full buf list is empty.");
"non-blocking read, done buf is empty.");
goto fail;
}
/* no available buffers, block here */
"usbvc_read: wait for done buf");
&usbvcp->usbvc_mutex) <= 0) {
/* no done buf and cv is signaled */
goto fail;
}
/* Device is disconnected. */
goto fail;
}
}
}
return (rval);
fail:
return (rval);
}
/*
* strategy:
* Called through physio to setup and start the transfer.
*/
static int
{
"usbvc_strategy: enter");
/*
* Initialize residual count here in case transfer doesn't even get
* started.
*/
/* Needed as this is a character driver. */
}
/* Make sure device has not been disconnected. */
"usbvc_strategy: device can't be accessed");
goto fail;
}
/* read data from uv_buf_done list */
"usbvc_strategy: read full buf list fail");
goto fail;
}
return (0);
fail:
"usbvc_strategy: strategy fail");
return (0);
}
static void
{
"usbvc_minphys: max read size=%d", maxsize);
}
}
/*
* ioctl entry.
*/
/*ARGSUSED*/
static int
{
int rv = 0;
return (ENXIO);
}
"ioctl enter, cmd=%x", cmd);
"ioctl: Device is not online,"
return (EFAULT);
}
"serialize_access failed.");
return (EFAULT);
}
"usbvc_ioctl exit");
return (rv);
}
/* Entry for mmap system call */
static int
{
int error, i;
"usbvc_devmap: usbvcp == NULL");
return (ENXIO);
}
"devmap: memory map for instance(%d), off=%llx,"
if (!strm_if) {
"usbvc_devmap: No current strm if");
return (ENXIO);
}
break;
}
}
"usbvc_devmap: idx=%d", i);
return (ENXIO);
}
/*
* round up len to a multiple of a page size, according to chapter
* 10 of "writing device drivers"
*/
"usbvc_devmap: len=0x%lx", len);
return (ENXIO);
}
} else {
"usbvc_devmap: devmap_umem_setup, err=%d", error);
}
(void) usbvc_release_access(usbvcp);
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: enter: level = %d, dev_state: %x",
goto done;
}
/* Check if we are transitioning to a legal power level */
"usbvc_power: illegal power level = %d "
goto done;
}
/*
* if we are about to raise power and asked to lower power, fail
*/
goto done;
}
switch (level) {
case USB_DEV_OS_PWR_OFF :
break;
case USB_DEV_OS_PWR_1 :
break;
case USB_DEV_OS_PWR_2 :
break;
case USB_DEV_OS_FULL_PWR :
break;
}
done:
}
/*
* 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
{
"init_power_mgmt enter");
/* Allocate the state structure */
usbvcpm->usbvc_pm_capabilities = 0;
USB_SUCCESS) {
"usbvc_init_power_mgmt: created PM components");
} else {
" remote wakeup not supported");
}
(void) pm_raise_power(
}
"usbvc_init_power_mgmt: end");
}
/*
* usbvc_destroy_power_mgmt:
* Shut down and destroy power management and remote wakeup functionality.
*/
static void
{
int rval;
"destroy_power_mgmt enter");
if (pm->usbvc_wakeup_enabled) {
/* First bring the device to full power */
if ((rval = usb_handle_remote_wakeup(
USB_SUCCESS) {
"usbvc_destroy_power_mgmt: "
"Error disabling rmt wakeup: rval = %d",
rval);
}
}
/*
* Since remote wakeup is disabled now,
* no one can raise power
* and get to device once power is lowered here.
*/
}
if (pm) {
}
}
static void
{
"usbvc_pm_busy_component: enter");
DDI_SUCCESS) {
"usbvc_pm_busy_component: pm busy fail, usbvc_pm_busy=%d",
}
"usbvc_pm_busy_component: exit");
}
static void
{
"usbvc_pm_idle_component: enter");
DDI_SUCCESS) {
}
"usbvc_pm_idle_component: %d",
}
}
/*
* usbvc_pwrlvl0:
* Functions to handle power transition for OS levels 0 -> 3
*/
static int
{
int rval;
switch (usbvcp->usbvc_dev_state) {
case USB_DEV_ONLINE:
/* Deny the powerdown request if the device is busy */
"usbvc_pwrlvl0: usbvc_pm_busy");
return (USB_FAILURE);
}
/* Issue USB D3 command to the device here */
/* 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:
"usbvc_pwrlvl0: illegal dev state");
return (USB_FAILURE);
}
}
/*
* usbvc_pwrlvl1:
* Functions to handle power transition to OS levels -> 2
*/
static int
{
int rval;
"usbvc_pwrlvl1");
/* Issue USB D2 command to the device here */
return (USB_FAILURE);
}
/*
* usbvc_pwrlvl2:
* Functions to handle power transition to OS levels -> 1
*/
static int
{
int rval;
"usbvc_pwrlvl2");
/* Issue USB D1 command to the device here */
return (USB_FAILURE);
}
/*
* usbvc_pwrlvl3:
* Functions to handle power transition to OS level -> 0
*/
static int
{
switch (usbvcp->usbvc_dev_state) {
case USB_DEV_PWRED_DOWN:
/* Issue USB D0 command to the device here */
/* 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:
"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 enter");
/*
* Set dev_state to suspended so other driver threads don't start any
* new I/O.
*/
"usbvc_cpr_suspend: return");
}
/*
* If the polling has been stopped due to some exceptional errors,
* we reconfigure the device and start polling again. Only for S/R
* resume or hotplug reconnect operations.
*/
static int
{
"usbvc_resume_operation: enter");
if (!strm_if) {
rv = USB_FAILURE;
return (rv);
}
/*
* 1) if application has not started STREAMON ioctl yet,
* just return
* 2) if application use READ mode, return immediately
*/
return (rv);
}
/* isoc pipe is expected to be opened already if (stream_on==1) */
rv = USB_FAILURE;
return (rv);
}
/* first commit the parameters negotiated and saved during S_FMT */
"usbvc_resume_operation: set probe failed, rv=%d", rv);
return (rv);
}
/* Set alt interfaces, must be after probe_commit according to spec */
"usbvc_resume_operation: set alt failed");
return (rv);
}
/*
* The isoc polling could be stopped by isoc_exc_cb
* during suspend or hotplug. Restart it.
*/
!= USB_SUCCESS) {
rv = USB_FAILURE;
return (rv);
}
return (rv);
}
/*
* usbvc_cpr_resume:
*
* usbvc_restore_device_state marks success by putting device back online
*/
static void
{
"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.
*/
}
/*
* 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: enter");
/* Check if we are talking to the same device */
goto fail;
}
/* Failure here means device disappeared again. */
USB_SUCCESS) {
"device may or may not be accessible. "
"Please verify reconnection");
}
}
"usbvc_restore_device_state: can't resume operation");
goto fail;
}
"usbvc_restore_device_state: end");
return;
fail:
/* change the device state from suspended to disconnected */
}
/* 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
{
"disconnect: enter");
/*
* Save any state of device or IO in progress required by
* usbvc_restore_device_state for proper device "thawing" later.
*/
/*
* 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.
*/
} else {
}
/* Wait for the other threads to quit */
return (USB_SUCCESS);
}
/*
* usbvc_reconnect_event_cb:
* Called with device hotplug-inserted
* Restore state
*/
static int
{
"reconnect: enter");
return (USB_SUCCESS);
}
/* Sync objs and lists */
/*
*/
static void
{
}
static void
{
}
static void
{
/* video terminals */
/* video units */
/* stream interfaces */
}
/*
* 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: enter");
/* Free all video stream structure and the sub-structures */
"usbvc_fini_lists: end");
}
/*
* Free all the data structures allocated when parsing descriptors of ctrl
* interface.
*/
static void
{
"usbvc_free_ctrl_descr: enter");
if (usbvcp->usbvc_vc_header) {
}
/* Free all video terminal structure */
}
}
/* Free all video unit structure */
}
}
}
/*
* Free all the data structures allocated when parsing descriptors of stream
* interfaces.
*/
static void
{
"usbvc_fini_lists: stream list not empty.");
/* unlink this stream's data structure from the list */
} else {
/* No real stream data structure in the list */
return;
}
if (in_hdr) {
} else if (out_hdr) {
}
" fmtgrp cnt=%d", fmt_cnt);
/* Free headers */
if (in_hdr) {
}
if (out_hdr) {
}
/* Free format descriptors */
if (strm->format_group) {
int i;
for (i = 0; i < fmt_cnt; i++) {
break;
}
sizeof (usbvc_still_image_frame_t));
}
"usbvc_fini_lists:"
" frame cnt=%d", frm_cnt);
sizeof (usbvc_frames_t) * frm_cnt);
}
}
sizeof (usbvc_format_group_t) * fmt_cnt);
}
" free 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
{
if (size == 0) {
if (cvs_buf_len > off_size) {
} else {
return (USB_FAILURE);
}
}
return (USB_FAILURE);
}
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/* Parse the descriptors of control interface */
static int
{
int if_num;
int cvs_num;
"usbvc_parse_ctrl_if: cvs_num= %d, cvs_buf_len=%d",
/*
* parse interface cvs descriptors here; by checking
* bDescriptorType (cvs_buf[1])
*/
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.
*/
USB_SUCCESS) {
return (USB_FAILURE);
}
sizeof (usbvc_vc_header_t), KM_SLEEP);
(usbvc_vc_header_descr_t *)&cvs_buf[0];
0, version);
" VC header, bcdUVC=%x", version);
0) {
"usbvc_parse_ctrl_if: no strm interfaces");
break;
}
/* stream interface numbers */
break;
case VC_INPUT_TERMINAL:
{
/*
* 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 *)
USB_SUCCESS) {
break;
}
}
break;
}
case VC_OUTPUT_TERMINAL:
{
if (cvs_buf_len < USBVC_O_TERM_LEN_MIN) {
break;
}
term = (usbvc_terms_t *)
if (cvs_buf_len > 9) {
}
break;
}
case VC_PROCESSING_UNIT:
{
USB_SUCCESS) {
break;
}
/* bControlSize */
break;
}
unit = (usbvc_units_t *)
if (sz != 0) {
}
/*
* video class 1.1 version add one element
* (bmVideoStandards) to processing unit descriptor
*/
}
break;
}
case VC_SELECTOR_UNIT:
{
USB_SUCCESS) {
break;
}
break;
}
unit = (usbvc_units_t *)
if (pins > 0) {
}
break;
}
case VC_EXTENSION_UNIT:
{
USB_SUCCESS) {
break;
}
break;
}
/* Size of bmControls */
USB_SUCCESS) {
break;
}
break;
}
unit = (usbvc_units_t *)
if (pins != 0) {
}
if (unit->bControlSize != 0) {
}
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) {
"usbvc_parse_ctrl_if: no header descriptor");
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/* Parse all the cvs descriptors in one stream interface. */
{
uint_t i, j;
KM_SLEEP);
"usbvc_parse_stream_if: parse header fail");
return (NULL);
}
"usbvc_parse_stream_if: parse groups fail");
return (NULL);
}
/* Parse the alternate settings to find the maximum bandwidth. */
for (j = 0; j < if_alt_data->altif_n_ep; j++) {
ep_adr =
ep_adr !=
continue;
}
ep_adr !=
continue;
}
pktsize =
}
}
}
/* initialize MJPEC FID toggle */
/*
* initialize desired number of buffers used internally in read() mode
*/
"usbvc_parse_stream_if: return. max_isoc_payload=%x",
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
{
if (if_cnt == 0) {
"usbvc_parse_stream_ifs: no stream interfaces");
return (USB_SUCCESS);
}
for (i = 0; i < if_cnt; i++) {
" parse stream interface %d failed.", if_num);
return (USB_FAILURE);
}
/* video data buffers */
}
/* Make the first stream interface as the default one. */
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
{
/*
* 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;
}
USB_SUCCESS) {
continue;
}
/* Number of Image Size patterns of this format */
n = cvs_buf[4];
/* offset of bNumCompressionPattern */
if (off >= cvs_buf_len) {
continue;
}
/* Number of compression pattern of this format */
USB_SUCCESS) {
continue;
}
kmem_zalloc(sizeof (usbvc_still_image_frame_t),
KM_SLEEP);
if (n > 0) {
st->width_height =
}
}
}
}
}
"usbvc_parse_color_still: still=%p, color=%p",
}
/*
* Parse frame descriptors of a format group. There might be multi frame
* descriptors in one format group.
*/
static void
{
uint8_t i;
"usbvc_parse_format_group: frame_cnt=%d", frame_cnt);
if (frame_cnt == 0) {
return;
}
/* All these mem allocated will be freed in cleanup() */
cvs_num++;
i = 0;
/*
* Traverse from the format decr's first frame decr to the the last
* frame descr.
*/
"usbvc_parse_frames: cvs_num=%d, i=%d", cvs_num, i);
if (cvs_num >= altif_n_cvs) {
"usbvc_parse_frames: less frames than "
"expected, cvs_num=%d, i=%d", cvs_num, i);
break;
}
if (cvs_buf_len < USBVC_FRAME_LEN_MIN) {
i++;
continue;
}
/* Descriptor for discrete frame interval */
i++;
continue;
}
} else { /* Continuous interval */
if (cvs_buf_len < USBVC_FRAME_LEN_CON) {
i++;
continue;
}
/* Continuous frame intervals */
}
i++;
}
"usbvc_parse_frames: %d frames are actually parsed",
}
/* Parse one of the format groups in a stream interface */
static int
{
"usbvc_parse_format_group: frame_cnt=%d, cvs_num=%d",
switch (fmt->bDescriptorSubType) {
case VS_FORMAT_UNCOMPRESSED:
break;
case VS_FORMAT_MJPEG:
break;
case VS_FORMAT_MPEG2TS:
case VS_FORMAT_DV:
case VS_FORMAT_FRAME_BASED:
case VS_FORMAT_STREAM_BASED:
"usbvc_parse_format_group: format not supported yet.");
return (USB_FAILURE);
default:
"usbvc_parse_format_group: unknown format.");
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/* Parse the descriptors belong to one format */
static int
{
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) {
} else if (strm_if->output_header) {
}
if (!fmtgrp_cnt) {
return (USB_FAILURE);
}
"usbvc_parse_format_groups: fmtgrp_cnt=%d", fmtgrp_cnt);
fmtgrp = (usbvc_format_group_t *)
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:
/*
* 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_num++;
break;
default:
break;
}
}
/* Save the number of parsed format groups. */
"usbvc_parse_format_groups: acctually %d formats parsed",
/*
* If can't find any formats, then free all allocated
* usbvc_format_group_t, return failure.
*/
"usbvc_parse_format_groups: can't find any formats");
return (USB_FAILURE);
}
"usbvc_parse_format_groups: %d format groups parsed", fmtgrp_num);
return (USB_SUCCESS);
}
/*
* UVC Spec: there must be one and only one header in one stream interface.
*/
int
{
int cvs_num;
"usbvc_parse_stream_header: cvs_num= %d", cvs_num);
/*
* parse interface cvs descriptors here; by checking
* bDescriptorType (cvs_buf[1])
*/
continue;
}
USB_SUCCESS) {
continue;
}
kmem_zalloc(sizeof (usbvc_input_header_t),
KM_SLEEP);
}
return (USB_SUCCESS);
USB_SUCCESS) {
continue;
}
kmem_zalloc(sizeof (usbvc_output_header_t),
KM_SLEEP);
}
return (USB_SUCCESS);
} else {
continue;
}
}
/* Didn't find one header descriptor. */
"usbvc_parse_stream_header: FAIL");
return (USB_FAILURE);
}
/* read I/O functions */
/* Allocate bufs for read I/O method */
static int
{
int i;
if (!len) {
return (USB_FAILURE);
}
for (i = 0; i < strm_if->buf_read_num; i++) {
KM_SLEEP);
}
return (USB_SUCCESS);
}
/* Read a done buf, copy data to bp. This function is for read I/O method */
static int
{
int buf_residue;
int len_to_copy;
"usbvc_read_buf: empty list(uv_buf_done)!");
return (USB_FAILURE);
}
/* read a buf from full list and then put it to free list */
"usbvc_read_buf: buf=%p, buf->filled=%d, buf->len=%d,"
" buf->len_read=%d bp->b_bcount=%ld, bp->b_resid=%lu",
if (len_to_copy == buf_residue) {
/*
* the bp can accommodate all the remaining bytes of
* the buf. Then we can reuse this buf.
*/
buf);
buf);
}
return (USB_SUCCESS);
}
static void
{
}
}
}
static void
{
if (!strm_if) {
return;
}
}
}
}
}
"usbvc_free_read_bufs: return");
}
/*
* Allocate bufs for mapped I/O , return the number of allocated bufs
* if success, return 0 if fail.
*/
int
{
int i = 0;
"usbvc_alloc_map_bufs: len<=0, cnt<=0");
return (0);
}
KM_SLEEP);
&bufs[0].umem_cookie);
for (i = 0; i < buf_cnt; i++) {
"usbvc_alloc_map_bufs: prepare %d buffers of %d bytes",
}
return (buf_cnt);
}
/* Free all bufs which are for memory map IO style */
void
{
if (!strm_if) {
return;
}
}
}
if (!buf) {
"usbvc_free_map_bufs: no data buf need be freed, return");
return;
}
if (buf->umem_cookie) {
}
"usbvc_free_map_bufs: return");
}
/*
* Open the isoc pipe, this pipe is for video data transfer
*/
int
{
return (rval);
}
"usbvc_open_isoc_pipe: open pipe fail");
return (rval);
}
strm_if->start_polling = 0;
"usbvc_open_isoc_pipe: success, datain_ph=%p",
return (rval);
}
/*
* Open the isoc pipe
*/
static void
{
if (!strm_if) {
"usbvc_close_isoc_pipe: stream interface is NULL");
return;
}
}
}
/*
* Start to get video data from isoc pipe in the stream interface,
* issue isoc req.
*/
int
{
"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 (n_pkt > USBVC_MAX_PKTS) {
}
"usbvc_start_isoc_polling: n_pkt=%d", n_pkt);
/* Initialize the packet descriptor */
}
/*
* zero here indicates that HCDs will use
* isoc_pkt_descr->isoc_pkt_length to calculate
* isoc_pkts_length.
*/
req->isoc_pkts_length = 0;
} else {
"usbvc_start_isoc_polling: alloc_isoc_req fail");
return (USB_FAILURE);
}
if (rval != USB_SUCCESS) {
if (req) {
}
}
"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
{
int i;
" data=0x%p, cnt=%d",
for (i = 0; i < isoc_req->isoc_pkts_count; i++) {
"\tpkt%d: "
"pktsize=%d status=%d resid=%d",
i,
USB_CR_OK) {
"record: pkt=%d status=%s", i, usb_str_cr(
}
} else {
}
!= USB_SUCCESS) {
}
if (bufgrp->buf_filling &&
/* Move the buf to the full list */
} else {
}
}
}
}
}
/*ARGSUSED*/
static void
{
int rval;
/* get the first stream interface */
"usbvc_isoc_exc_cb: ph=0x%p, isoc_req=0x%p, cr=%d",
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
*/
"usbvc_isoc_exc_cb: restart capture rval=%d", rval);
return;
default:
"usbvc_isoc_exc_cb: stop polling");
}
strm_if->start_polling = 0;
"usbvc_isoc_exc_cb: start_polling=%d cr=%d",
}
/*
* 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
{
if (!bandwidth) {
"usbvc_set_alt: bandwidth is not set yet");
return (USB_FAILURE);
}
"usbvc_set_alt: bandwidth=%x", bandwidth);
curr_pktsize = 0xffff;
/*
* Find one alternate setting whose isoc ep's max pktsize is just
* enough for the bandwidth.
*/
for (j = 0; j < alt->altif_n_ep; j++) {
/* if this stream interface is for input */
continue;
}
/* if this stream interface is for output */
continue;
}
pktsize =
}
}
}
"usbvc_set_alt: can't find a proper ep to satisfy"
" the given bandwidth");
return (USB_FAILURE);
}
"usbvc_set_alt: usb_set_alt_if fail, if.alt=%d.%d, rval=%d",
return (rval);
}
"usbvc_set_alt: return, if_num=%d, alt=%d",
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: enter. actual_len=%x", actual_len);
/* header length check. */
if (actual_len < 2) {
"usbvc_decode_stream_header: header is not completed");
return (USB_FAILURE);
}
"usbvc_decode_stream_header: headlen=%x", head_len);
/* header length check. */
if (actual_len < head_len) {
"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.
*/
"usbvc_decode_stream_header: only header, no data");
return (USB_FAILURE);
}
/* Get the first stream interface */
"usbvc_decode_stream_header: dwMaxVideoFrameSize=%x, head_flag=%x",
/*
* if no buf is filling, pick one buf from free list and alloc data
* mem for the buf.
*/
if (!bufgrp->buf_filling) {
"usbvc_decode_stream_header: free list are empty");
return (USB_FAILURE);
} else {
/* unlink from buf free list */
}
"usbvc_decode_stream_header: status=%d",
}
/* if no buf room left, then return with a err status */
if (buf_left == 0) {
/* buffer full, got an EOF packet(head only, no payload) */
if ((head_flag & USBVC_STREAM_EOF) &&
(actual_len == head_len)) {
"usbvc_decode_stream_header: got a EOF packet");
return (USB_SUCCESS);
}
/* Otherwise, mark the buf error and return failure */
"usbvc_decode_stream_header: frame buf full");
return (USB_FAILURE);
}
/* get this sample's data length except header */
"usbvc_decode_stream_header: fid=%x, len=%x, filled=%x",
/* 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.
*/
(head_flag & USBVC_STREAM_FID)) {
"usbvc_decode_stream_header: 1st sample of a frame,"
" fid is incorrect.");
return (USB_FAILURE);
}
/* If in the middle of a frame, fid should be consistent. */
"usbvc_decode_stream_header: fid is incorrect.");
return (USB_FAILURE);
}
if (data_len) {
}
/* If the last packet for this frame */
if (head_flag & USBVC_STREAM_EOF) {
}
}
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
{
while (usbvcp->usbvc_serial_inuse) {
if (waitsig == USBVC_SER_SIG) {
&usbvcp->usbvc_mutex);
} else {
&usbvcp->usbvc_mutex);
}
}
return (rval);
}
/*
* usbvc_release_access:
* Release the serial synchronization object.
*/
static void
{
}
/* Send req to video control interface to get ctrl */
int
{
"usbvc_vc_get_ctrl: cmd failed, cr=%d, cb_flags=%x",
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/* Send req to video control interface to get ctrl */
int
{
"usbvc_vc_set_ctrl: cmd failed, cr=%d, cb_flags=%x",
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/* Set probe or commit ctrl for video stream interface */
int
{
int rval;
/* wValue, VS_PROBE_CONTROL or VS_COMMIT_CONTROL */
/* UVC Spec: this value must be put to the high byte */
/* Data block */
"usbvc_vs_set_probe_commit: allocb failed");
return (USB_FAILURE);
}
if (data) {
}
"usbvc_vs_set_probe_commit: fail, rval=%d, cr=%d, "
return (rval);
}
if (data) {
}
return (USB_SUCCESS);
}
/* Get probe ctrl for vodeo stream interface */
int
{
if (data) {
}
"usbvc_vs_get_probe: cmd failed, cr=%d, cb_flags=%x",
return (USB_FAILURE);
}
if (data) {
}
return (USB_SUCCESS);
}
/* Set a default format when open the device */
static int
{
"usbvc_set_default_stream_fmt: enter");
"usbvc_set_default_stream_fmt: no stream interface, fail");
return (USB_FAILURE);
}
/* Get the current stream interface */
/* Fill the probe commit req data */
for (i = 0; i < strm_if->fmtgrp_cnt; i++) {
/*
* If v4l2_pixelformat is NULL, then that means there is not
* a parsed format in format_group[i].
*/
curr_fmtgrp->frame_cnt == 0) {
"usbvc_set_default_stream_fmt: no frame, fail");
continue;
} else {
break;
}
}
"usbvc_set_default_stream_fmt: can't find a fmtgrp"
"which has a frame, fail");
return (USB_FAILURE);
}
/* use the first frame descr as default */
/* use bcopy to keep the byte sequence as 32 bit little endian */
!= USB_SUCCESS) {
return (USB_FAILURE);
}
!= USB_SUCCESS) {
return (USB_FAILURE);
}
"usbvc_set_default_stream_fmt: get bandwidth=%x", bandwidth);
VS_COMMIT_CONTROL) != USB_SUCCESS) {
return (USB_FAILURE);
}
/* it's good to check index here before use it */
} else {
"usbvc_set_default_stream_fmt: format index out of range");
return (USB_FAILURE);
}
} else {
"usbvc_set_default_stream: frame index out of range");
return (USB_FAILURE);
}
/*
* by now, the video format is set successfully. record the current
* setting to strm_if->ctrl_pc
*/
return (USB_SUCCESS);
}