OpenCloudOS-Kernel/drivers/media/usb/uvc/uvc_driver.c

2540 lines
68 KiB
C
Raw Normal View History

/*
* uvc_driver.c -- USB Video Class driver
*
* Copyright (C) 2005-2010
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
#include <linux/atomic.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <linux/version.h>
#include <asm/unaligned.h>
#include <media/v4l2-common.h>
#include "uvcvideo.h"
#define DRIVER_AUTHOR "Laurent Pinchart " \
"<laurent.pinchart@ideasonboard.com>"
#define DRIVER_DESC "USB Video Class driver"
unsigned int uvc_clock_param = CLOCK_MONOTONIC;
unsigned int uvc_no_drop_param;
static unsigned int uvc_quirks_param = -1;
unsigned int uvc_trace_param;
unsigned int uvc_timeout_param = UVC_CTRL_STREAMING_TIMEOUT;
/* ------------------------------------------------------------------------
* Video formats
*/
static struct uvc_format_desc uvc_fmts[] = {
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:2 (YUYV)",
.guid = UVC_GUID_FORMAT_YUY2_ISIGHT,
.fcc = V4L2_PIX_FMT_YUYV,
},
{
.name = "YUV 4:2:0 (NV12)",
.guid = UVC_GUID_FORMAT_NV12,
.fcc = V4L2_PIX_FMT_NV12,
},
{
.name = "MJPEG",
.guid = UVC_GUID_FORMAT_MJPEG,
.fcc = V4L2_PIX_FMT_MJPEG,
},
{
.name = "YVU 4:2:0 (YV12)",
.guid = UVC_GUID_FORMAT_YV12,
.fcc = V4L2_PIX_FMT_YVU420,
},
{
.name = "YUV 4:2:0 (I420)",
.guid = UVC_GUID_FORMAT_I420,
.fcc = V4L2_PIX_FMT_YUV420,
},
{
.name = "YUV 4:2:0 (M420)",
.guid = UVC_GUID_FORMAT_M420,
.fcc = V4L2_PIX_FMT_M420,
},
{
.name = "YUV 4:2:2 (UYVY)",
.guid = UVC_GUID_FORMAT_UYVY,
.fcc = V4L2_PIX_FMT_UYVY,
},
{
.name = "Greyscale 8-bit (Y800)",
.guid = UVC_GUID_FORMAT_Y800,
.fcc = V4L2_PIX_FMT_GREY,
},
{
.name = "Greyscale 8-bit (Y8 )",
.guid = UVC_GUID_FORMAT_Y8,
.fcc = V4L2_PIX_FMT_GREY,
},
{
.name = "Greyscale 10-bit (Y10 )",
.guid = UVC_GUID_FORMAT_Y10,
.fcc = V4L2_PIX_FMT_Y10,
},
{
.name = "Greyscale 12-bit (Y12 )",
.guid = UVC_GUID_FORMAT_Y12,
.fcc = V4L2_PIX_FMT_Y12,
},
{
.name = "Greyscale 16-bit (Y16 )",
.guid = UVC_GUID_FORMAT_Y16,
.fcc = V4L2_PIX_FMT_Y16,
},
{
.name = "BGGR Bayer (BY8 )",
.guid = UVC_GUID_FORMAT_BY8,
.fcc = V4L2_PIX_FMT_SBGGR8,
},
{
.name = "BGGR Bayer (BA81)",
.guid = UVC_GUID_FORMAT_BA81,
.fcc = V4L2_PIX_FMT_SBGGR8,
},
{
.name = "GBRG Bayer (GBRG)",
.guid = UVC_GUID_FORMAT_GBRG,
.fcc = V4L2_PIX_FMT_SGBRG8,
},
{
.name = "GRBG Bayer (GRBG)",
.guid = UVC_GUID_FORMAT_GRBG,
.fcc = V4L2_PIX_FMT_SGRBG8,
},
{
.name = "RGGB Bayer (RGGB)",
.guid = UVC_GUID_FORMAT_RGGB,
.fcc = V4L2_PIX_FMT_SRGGB8,
},
{
.name = "RGB565",
.guid = UVC_GUID_FORMAT_RGBP,
.fcc = V4L2_PIX_FMT_RGB565,
},
{
.name = "H.264",
.guid = UVC_GUID_FORMAT_H264,
.fcc = V4L2_PIX_FMT_H264,
},
};
/* ------------------------------------------------------------------------
* Utility functions
*/
struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts,
__u8 epaddr)
{
struct usb_host_endpoint *ep;
unsigned int i;
for (i = 0; i < alts->desc.bNumEndpoints; ++i) {
ep = &alts->endpoint[i];
if (ep->desc.bEndpointAddress == epaddr)
return ep;
}
return NULL;
}
static struct uvc_format_desc *uvc_format_by_guid(const __u8 guid[16])
{
unsigned int len = ARRAY_SIZE(uvc_fmts);
unsigned int i;
for (i = 0; i < len; ++i) {
if (memcmp(guid, uvc_fmts[i].guid, 16) == 0)
return &uvc_fmts[i];
}
return NULL;
}
static __u32 uvc_colorspace(const __u8 primaries)
{
static const __u8 colorprimaries[] = {
0,
V4L2_COLORSPACE_SRGB,
V4L2_COLORSPACE_470_SYSTEM_M,
V4L2_COLORSPACE_470_SYSTEM_BG,
V4L2_COLORSPACE_SMPTE170M,
V4L2_COLORSPACE_SMPTE240M,
};
if (primaries < ARRAY_SIZE(colorprimaries))
return colorprimaries[primaries];
return 0;
}
/* Simplify a fraction using a simple continued fraction decomposition. The
* idea here is to convert fractions such as 333333/10000000 to 1/30 using
* 32 bit arithmetic only. The algorithm is not perfect and relies upon two
* arbitrary parameters to remove non-significative terms from the simple
* continued fraction decomposition. Using 8 and 333 for n_terms and threshold
* respectively seems to give nice results.
*/
void uvc_simplify_fraction(uint32_t *numerator, uint32_t *denominator,
unsigned int n_terms, unsigned int threshold)
{
uint32_t *an;
uint32_t x, y, r;
unsigned int i, n;
an = kmalloc(n_terms * sizeof *an, GFP_KERNEL);
if (an == NULL)
return;
/* Convert the fraction to a simple continued fraction. See
* http://mathforum.org/dr.math/faq/faq.fractions.html
* Stop if the current term is bigger than or equal to the given
* threshold.
*/
x = *numerator;
y = *denominator;
for (n = 0; n < n_terms && y != 0; ++n) {
an[n] = x / y;
if (an[n] >= threshold) {
if (n < 2)
n++;
break;
}
r = x - an[n] * y;
x = y;
y = r;
}
/* Expand the simple continued fraction back to an integer fraction. */
x = 0;
y = 1;
for (i = n; i > 0; --i) {
r = y;
y = an[i-1] * y + x;
x = r;
}
*numerator = y;
*denominator = x;
kfree(an);
}
/* Convert a fraction to a frame interval in 100ns multiples. The idea here is
* to compute numerator / denominator * 10000000 using 32 bit fixed point
* arithmetic only.
*/
uint32_t uvc_fraction_to_interval(uint32_t numerator, uint32_t denominator)
{
uint32_t multiplier;
/* Saturate the result if the operation would overflow. */
if (denominator == 0 ||
numerator/denominator >= ((uint32_t)-1)/10000000)
return (uint32_t)-1;
/* Divide both the denominator and the multiplier by two until
* numerator * multiplier doesn't overflow. If anyone knows a better
* algorithm please let me know.
*/
multiplier = 10000000;
while (numerator > ((uint32_t)-1)/multiplier) {
multiplier /= 2;
denominator /= 2;
}
return denominator ? numerator * multiplier / denominator : 0;
}
/* ------------------------------------------------------------------------
* Terminal and unit management
*/
struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id)
{
struct uvc_entity *entity;
list_for_each_entry(entity, &dev->entities, list) {
if (entity->id == id)
return entity;
}
return NULL;
}
static struct uvc_entity *uvc_entity_by_reference(struct uvc_device *dev,
int id, struct uvc_entity *entity)
{
unsigned int i;
if (entity == NULL)
entity = list_entry(&dev->entities, struct uvc_entity, list);
list_for_each_entry_continue(entity, &dev->entities, list) {
for (i = 0; i < entity->bNrInPins; ++i)
if (entity->baSourceID[i] == id)
return entity;
}
return NULL;
}
static struct uvc_streaming *uvc_stream_by_id(struct uvc_device *dev, int id)
{
struct uvc_streaming *stream;
list_for_each_entry(stream, &dev->streams, list) {
if (stream->header.bTerminalLink == id)
return stream;
}
return NULL;
}
/* ------------------------------------------------------------------------
* Descriptors parsing
*/
static int uvc_parse_format(struct uvc_device *dev,
struct uvc_streaming *streaming, struct uvc_format *format,
__u32 **intervals, unsigned char *buffer, int buflen)
{
struct usb_interface *intf = streaming->intf;
struct usb_host_interface *alts = intf->cur_altsetting;
struct uvc_format_desc *fmtdesc;
struct uvc_frame *frame;
const unsigned char *start = buffer;
unsigned int interval;
unsigned int i, n;
__u8 ftype;
format->type = buffer[2];
format->index = buffer[3];
switch (buffer[2]) {
case UVC_VS_FORMAT_UNCOMPRESSED:
case UVC_VS_FORMAT_FRAME_BASED:
n = buffer[2] == UVC_VS_FORMAT_UNCOMPRESSED ? 27 : 28;
if (buflen < n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d FORMAT error\n",
dev->udev->devnum,
alts->desc.bInterfaceNumber);
return -EINVAL;
}
/* Find the format descriptor from its GUID. */
fmtdesc = uvc_format_by_guid(&buffer[5]);
if (fmtdesc != NULL) {
strlcpy(format->name, fmtdesc->name,
sizeof format->name);
format->fcc = fmtdesc->fcc;
} else {
uvc_printk(KERN_INFO, "Unknown video format %pUl\n",
&buffer[5]);
snprintf(format->name, sizeof(format->name), "%pUl\n",
&buffer[5]);
format->fcc = 0;
}
format->bpp = buffer[21];
if (buffer[2] == UVC_VS_FORMAT_UNCOMPRESSED) {
ftype = UVC_VS_FRAME_UNCOMPRESSED;
} else {
ftype = UVC_VS_FRAME_FRAME_BASED;
if (buffer[27])
format->flags = UVC_FMT_FLAG_COMPRESSED;
}
break;
case UVC_VS_FORMAT_MJPEG:
if (buflen < 11) {
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d FORMAT error\n",
dev->udev->devnum,
alts->desc.bInterfaceNumber);
return -EINVAL;
}
strlcpy(format->name, "MJPEG", sizeof format->name);
format->fcc = V4L2_PIX_FMT_MJPEG;
format->flags = UVC_FMT_FLAG_COMPRESSED;
format->bpp = 0;
ftype = UVC_VS_FRAME_MJPEG;
break;
case UVC_VS_FORMAT_DV:
if (buflen < 9) {
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d FORMAT error\n",
dev->udev->devnum,
alts->desc.bInterfaceNumber);
return -EINVAL;
}
switch (buffer[8] & 0x7f) {
case 0:
strlcpy(format->name, "SD-DV", sizeof format->name);
break;
case 1:
strlcpy(format->name, "SDL-DV", sizeof format->name);
break;
case 2:
strlcpy(format->name, "HD-DV", sizeof format->name);
break;
default:
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d: unknown DV format %u\n",
dev->udev->devnum,
alts->desc.bInterfaceNumber, buffer[8]);
return -EINVAL;
}
strlcat(format->name, buffer[8] & (1 << 7) ? " 60Hz" : " 50Hz",
sizeof format->name);
format->fcc = V4L2_PIX_FMT_DV;
format->flags = UVC_FMT_FLAG_COMPRESSED | UVC_FMT_FLAG_STREAM;
format->bpp = 0;
ftype = 0;
/* Create a dummy frame descriptor. */
frame = &format->frame[0];
memset(&format->frame[0], 0, sizeof format->frame[0]);
frame->bFrameIntervalType = 1;
frame->dwDefaultFrameInterval = 1;
frame->dwFrameInterval = *intervals;
*(*intervals)++ = 1;
format->nframes = 1;
break;
case UVC_VS_FORMAT_MPEG2TS:
case UVC_VS_FORMAT_STREAM_BASED:
/* Not supported yet. */
default:
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d unsupported format %u\n",
dev->udev->devnum, alts->desc.bInterfaceNumber,
buffer[2]);
return -EINVAL;
}
uvc_trace(UVC_TRACE_DESCR, "Found format %s.\n", format->name);
buflen -= buffer[0];
buffer += buffer[0];
/* Parse the frame descriptors. Only uncompressed, MJPEG and frame
* based formats have frame descriptors.
*/
while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
buffer[2] == ftype) {
frame = &format->frame[format->nframes];
if (ftype != UVC_VS_FRAME_FRAME_BASED)
n = buflen > 25 ? buffer[25] : 0;
else
n = buflen > 21 ? buffer[21] : 0;
n = n ? n : 3;
if (buflen < 26 + 4*n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d FRAME error\n", dev->udev->devnum,
alts->desc.bInterfaceNumber);
return -EINVAL;
}
frame->bFrameIndex = buffer[3];
frame->bmCapabilities = buffer[4];
frame->wWidth = get_unaligned_le16(&buffer[5]);
frame->wHeight = get_unaligned_le16(&buffer[7]);
frame->dwMinBitRate = get_unaligned_le32(&buffer[9]);
frame->dwMaxBitRate = get_unaligned_le32(&buffer[13]);
if (ftype != UVC_VS_FRAME_FRAME_BASED) {
frame->dwMaxVideoFrameBufferSize =
get_unaligned_le32(&buffer[17]);
frame->dwDefaultFrameInterval =
get_unaligned_le32(&buffer[21]);
frame->bFrameIntervalType = buffer[25];
} else {
frame->dwMaxVideoFrameBufferSize = 0;
frame->dwDefaultFrameInterval =
get_unaligned_le32(&buffer[17]);
frame->bFrameIntervalType = buffer[21];
}
frame->dwFrameInterval = *intervals;
/* Several UVC chipsets screw up dwMaxVideoFrameBufferSize
* completely. Observed behaviours range from setting the
* value to 1.1x the actual frame size to hardwiring the
* 16 low bits to 0. This results in a higher than necessary
* memory usage as well as a wrong image size information. For
* uncompressed formats this can be fixed by computing the
* value from the frame size.
*/
if (!(format->flags & UVC_FMT_FLAG_COMPRESSED))
frame->dwMaxVideoFrameBufferSize = format->bpp
* frame->wWidth * frame->wHeight / 8;
/* Some bogus devices report dwMinFrameInterval equal to
* dwMaxFrameInterval and have dwFrameIntervalStep set to
* zero. Setting all null intervals to 1 fixes the problem and
* some other divisions by zero that could happen.
*/
for (i = 0; i < n; ++i) {
interval = get_unaligned_le32(&buffer[26+4*i]);
*(*intervals)++ = interval ? interval : 1;
}
/* Make sure that the default frame interval stays between
* the boundaries.
*/
n -= frame->bFrameIntervalType ? 1 : 2;
frame->dwDefaultFrameInterval =
min(frame->dwFrameInterval[n],
max(frame->dwFrameInterval[0],
frame->dwDefaultFrameInterval));
if (dev->quirks & UVC_QUIRK_RESTRICT_FRAME_RATE) {
frame->bFrameIntervalType = 1;
frame->dwFrameInterval[0] =
frame->dwDefaultFrameInterval;
}
uvc_trace(UVC_TRACE_DESCR, "- %ux%u (%u.%u fps)\n",
frame->wWidth, frame->wHeight,
10000000/frame->dwDefaultFrameInterval,
(100000000/frame->dwDefaultFrameInterval)%10);
format->nframes++;
buflen -= buffer[0];
buffer += buffer[0];
}
if (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
buffer[2] == UVC_VS_STILL_IMAGE_FRAME) {
buflen -= buffer[0];
buffer += buffer[0];
}
if (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
buffer[2] == UVC_VS_COLORFORMAT) {
if (buflen < 6) {
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d COLORFORMAT error\n",
dev->udev->devnum,
alts->desc.bInterfaceNumber);
return -EINVAL;
}
format->colorspace = uvc_colorspace(buffer[3]);
buflen -= buffer[0];
buffer += buffer[0];
}
return buffer - start;
}
static int uvc_parse_streaming(struct uvc_device *dev,
struct usb_interface *intf)
{
struct uvc_streaming *streaming = NULL;
struct uvc_format *format;
struct uvc_frame *frame;
struct usb_host_interface *alts = &intf->altsetting[0];
unsigned char *_buffer, *buffer = alts->extra;
int _buflen, buflen = alts->extralen;
unsigned int nformats = 0, nframes = 0, nintervals = 0;
unsigned int size, i, n, p;
__u32 *interval;
__u16 psize;
int ret = -EINVAL;
if (intf->cur_altsetting->desc.bInterfaceSubClass
!= UVC_SC_VIDEOSTREAMING) {
uvc_trace(UVC_TRACE_DESCR, "device %d interface %d isn't a "
"video streaming interface\n", dev->udev->devnum,
intf->altsetting[0].desc.bInterfaceNumber);
return -EINVAL;
}
if (usb_driver_claim_interface(&uvc_driver.driver, intf, dev)) {
uvc_trace(UVC_TRACE_DESCR, "device %d interface %d is already "
"claimed\n", dev->udev->devnum,
intf->altsetting[0].desc.bInterfaceNumber);
return -EINVAL;
}
streaming = kzalloc(sizeof *streaming, GFP_KERNEL);
if (streaming == NULL) {
usb_driver_release_interface(&uvc_driver.driver, intf);
return -EINVAL;
}
mutex_init(&streaming->mutex);
streaming->dev = dev;
streaming->intf = usb_get_intf(intf);
streaming->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
/* The Pico iMage webcam has its class-specific interface descriptors
* after the endpoint descriptors.
*/
if (buflen == 0) {
for (i = 0; i < alts->desc.bNumEndpoints; ++i) {
struct usb_host_endpoint *ep = &alts->endpoint[i];
if (ep->extralen == 0)
continue;
if (ep->extralen > 2 &&
ep->extra[1] == USB_DT_CS_INTERFACE) {
uvc_trace(UVC_TRACE_DESCR, "trying extra data "
"from endpoint %u.\n", i);
buffer = alts->endpoint[i].extra;
buflen = alts->endpoint[i].extralen;
break;
}
}
}
/* Skip the standard interface descriptors. */
while (buflen > 2 && buffer[1] != USB_DT_CS_INTERFACE) {
buflen -= buffer[0];
buffer += buffer[0];
}
if (buflen <= 2) {
uvc_trace(UVC_TRACE_DESCR, "no class-specific streaming "
"interface descriptors found.\n");
goto error;
}
/* Parse the header descriptor. */
switch (buffer[2]) {
case UVC_VS_OUTPUT_HEADER:
streaming->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
size = 9;
break;
case UVC_VS_INPUT_HEADER:
streaming->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
size = 13;
break;
default:
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming interface "
"%d HEADER descriptor not found.\n", dev->udev->devnum,
alts->desc.bInterfaceNumber);
goto error;
}
p = buflen >= 4 ? buffer[3] : 0;
n = buflen >= size ? buffer[size-1] : 0;
if (buflen < size + p*n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d HEADER descriptor is invalid.\n",
dev->udev->devnum, alts->desc.bInterfaceNumber);
goto error;
}
streaming->header.bNumFormats = p;
streaming->header.bEndpointAddress = buffer[6];
if (buffer[2] == UVC_VS_INPUT_HEADER) {
streaming->header.bmInfo = buffer[7];
streaming->header.bTerminalLink = buffer[8];
streaming->header.bStillCaptureMethod = buffer[9];
streaming->header.bTriggerSupport = buffer[10];
streaming->header.bTriggerUsage = buffer[11];
} else {
streaming->header.bTerminalLink = buffer[7];
}
streaming->header.bControlSize = n;
streaming->header.bmaControls = kmemdup(&buffer[size], p * n,
GFP_KERNEL);
if (streaming->header.bmaControls == NULL) {
ret = -ENOMEM;
goto error;
}
buflen -= buffer[0];
buffer += buffer[0];
_buffer = buffer;
_buflen = buflen;
/* Count the format and frame descriptors. */
while (_buflen > 2 && _buffer[1] == USB_DT_CS_INTERFACE) {
switch (_buffer[2]) {
case UVC_VS_FORMAT_UNCOMPRESSED:
case UVC_VS_FORMAT_MJPEG:
case UVC_VS_FORMAT_FRAME_BASED:
nformats++;
break;
case UVC_VS_FORMAT_DV:
/* DV format has no frame descriptor. We will create a
* dummy frame descriptor with a dummy frame interval.
*/
nformats++;
nframes++;
nintervals++;
break;
case UVC_VS_FORMAT_MPEG2TS:
case UVC_VS_FORMAT_STREAM_BASED:
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
"interface %d FORMAT %u is not supported.\n",
dev->udev->devnum,
alts->desc.bInterfaceNumber, _buffer[2]);
break;
case UVC_VS_FRAME_UNCOMPRESSED:
case UVC_VS_FRAME_MJPEG:
nframes++;
if (_buflen > 25)
nintervals += _buffer[25] ? _buffer[25] : 3;
break;
case UVC_VS_FRAME_FRAME_BASED:
nframes++;
if (_buflen > 21)
nintervals += _buffer[21] ? _buffer[21] : 3;
break;
}
_buflen -= _buffer[0];
_buffer += _buffer[0];
}
if (nformats == 0) {
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming interface "
"%d has no supported formats defined.\n",
dev->udev->devnum, alts->desc.bInterfaceNumber);
goto error;
}
size = nformats * sizeof *format + nframes * sizeof *frame
+ nintervals * sizeof *interval;
format = kzalloc(size, GFP_KERNEL);
if (format == NULL) {
ret = -ENOMEM;
goto error;
}
frame = (struct uvc_frame *)&format[nformats];
interval = (__u32 *)&frame[nframes];
streaming->format = format;
streaming->nformats = nformats;
/* Parse the format descriptors. */
while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE) {
switch (buffer[2]) {
case UVC_VS_FORMAT_UNCOMPRESSED:
case UVC_VS_FORMAT_MJPEG:
case UVC_VS_FORMAT_DV:
case UVC_VS_FORMAT_FRAME_BASED:
format->frame = frame;
ret = uvc_parse_format(dev, streaming, format,
&interval, buffer, buflen);
if (ret < 0)
goto error;
frame += format->nframes;
format++;
buflen -= ret;
buffer += ret;
continue;
default:
break;
}
buflen -= buffer[0];
buffer += buffer[0];
}
if (buflen)
uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming interface "
"%d has %u bytes of trailing descriptor garbage.\n",
dev->udev->devnum, alts->desc.bInterfaceNumber, buflen);
/* Parse the alternate settings to find the maximum bandwidth. */
for (i = 0; i < intf->num_altsetting; ++i) {
struct usb_host_endpoint *ep;
alts = &intf->altsetting[i];
ep = uvc_find_endpoint(alts,
streaming->header.bEndpointAddress);
if (ep == NULL)
continue;
psize = le16_to_cpu(ep->desc.wMaxPacketSize);
psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
if (psize > streaming->maxpsize)
streaming->maxpsize = psize;
}
list_add_tail(&streaming->list, &dev->streams);
return 0;
error:
usb_driver_release_interface(&uvc_driver.driver, intf);
usb_put_intf(intf);
kfree(streaming->format);
kfree(streaming->header.bmaControls);
kfree(streaming);
return ret;
}
static struct uvc_entity *uvc_alloc_entity(u16 type, u8 id,
unsigned int num_pads, unsigned int extra_size)
{
struct uvc_entity *entity;
unsigned int num_inputs;
unsigned int size;
unsigned int i;
extra_size = ALIGN(extra_size, sizeof(*entity->pads));
num_inputs = (type & UVC_TERM_OUTPUT) ? num_pads : num_pads - 1;
size = sizeof(*entity) + extra_size + sizeof(*entity->pads) * num_pads
+ num_inputs;
entity = kzalloc(size, GFP_KERNEL);
if (entity == NULL)
return NULL;
entity->id = id;
entity->type = type;
entity->num_links = 0;
entity->num_pads = num_pads;
entity->pads = ((void *)(entity + 1)) + extra_size;
for (i = 0; i < num_inputs; ++i)
entity->pads[i].flags = MEDIA_PAD_FL_SINK;
if (!UVC_ENTITY_IS_OTERM(entity))
entity->pads[num_pads-1].flags = MEDIA_PAD_FL_SOURCE;
entity->bNrInPins = num_inputs;
entity->baSourceID = (__u8 *)(&entity->pads[num_pads]);
return entity;
}
/* Parse vendor-specific extensions. */
static int uvc_parse_vendor_control(struct uvc_device *dev,
const unsigned char *buffer, int buflen)
{
struct usb_device *udev = dev->udev;
struct usb_host_interface *alts = dev->intf->cur_altsetting;
struct uvc_entity *unit;
unsigned int n, p;
int handled = 0;
switch (le16_to_cpu(dev->udev->descriptor.idVendor)) {
case 0x046d: /* Logitech */
if (buffer[1] != 0x41 || buffer[2] != 0x01)
break;
/* Logitech implements several vendor specific functions
* through vendor specific extension units (LXU).
*
* The LXU descriptors are similar to XU descriptors
* (see "USB Device Video Class for Video Devices", section
* 3.7.2.6 "Extension Unit Descriptor") with the following
* differences:
*
* ----------------------------------------------------------
* 0 bLength 1 Number
* Size of this descriptor, in bytes: 24+p+n*2
* ----------------------------------------------------------
* 23+p+n bmControlsType N Bitmap
* Individual bits in the set are defined:
* 0: Absolute
* 1: Relative
*
* This bitset is mapped exactly the same as bmControls.
* ----------------------------------------------------------
* 23+p+n*2 bReserved 1 Boolean
* ----------------------------------------------------------
* 24+p+n*2 iExtension 1 Index
* Index of a string descriptor that describes this
* extension unit.
* ----------------------------------------------------------
*/
p = buflen >= 22 ? buffer[21] : 0;
n = buflen >= 25 + p ? buffer[22+p] : 0;
if (buflen < 25 + p + 2*n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d EXTENSION_UNIT error\n",
udev->devnum, alts->desc.bInterfaceNumber);
break;
}
unit = uvc_alloc_entity(UVC_VC_EXTENSION_UNIT, buffer[3],
p + 1, 2*n);
if (unit == NULL)
return -ENOMEM;
memcpy(unit->extension.guidExtensionCode, &buffer[4], 16);
unit->extension.bNumControls = buffer[20];
memcpy(unit->baSourceID, &buffer[22], p);
unit->extension.bControlSize = buffer[22+p];
unit->extension.bmControls = (__u8 *)unit + sizeof(*unit);
unit->extension.bmControlsType = (__u8 *)unit + sizeof(*unit)
+ n;
memcpy(unit->extension.bmControls, &buffer[23+p], 2*n);
if (buffer[24+p+2*n] != 0)
usb_string(udev, buffer[24+p+2*n], unit->name,
sizeof unit->name);
else
sprintf(unit->name, "Extension %u", buffer[3]);
list_add_tail(&unit->list, &dev->entities);
handled = 1;
break;
}
return handled;
}
static int uvc_parse_standard_control(struct uvc_device *dev,
const unsigned char *buffer, int buflen)
{
struct usb_device *udev = dev->udev;
struct uvc_entity *unit, *term;
struct usb_interface *intf;
struct usb_host_interface *alts = dev->intf->cur_altsetting;
unsigned int i, n, p, len;
__u16 type;
switch (buffer[2]) {
case UVC_VC_HEADER:
n = buflen >= 12 ? buffer[11] : 0;
if (buflen < 12 + n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d HEADER error\n", udev->devnum,
alts->desc.bInterfaceNumber);
return -EINVAL;
}
dev->uvc_version = get_unaligned_le16(&buffer[3]);
dev->clock_frequency = get_unaligned_le32(&buffer[7]);
/* Parse all USB Video Streaming interfaces. */
for (i = 0; i < n; ++i) {
intf = usb_ifnum_to_if(udev, buffer[12+i]);
if (intf == NULL) {
uvc_trace(UVC_TRACE_DESCR, "device %d "
"interface %d doesn't exists\n",
udev->devnum, i);
continue;
}
uvc_parse_streaming(dev, intf);
}
break;
case UVC_VC_INPUT_TERMINAL:
if (buflen < 8) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d INPUT_TERMINAL error\n",
udev->devnum, alts->desc.bInterfaceNumber);
return -EINVAL;
}
/* Make sure the terminal type MSB is not null, otherwise it
* could be confused with a unit.
*/
type = get_unaligned_le16(&buffer[4]);
if ((type & 0xff00) == 0) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d INPUT_TERMINAL %d has invalid "
"type 0x%04x, skipping\n", udev->devnum,
alts->desc.bInterfaceNumber,
buffer[3], type);
return 0;
}
n = 0;
p = 0;
len = 8;
if (type == UVC_ITT_CAMERA) {
n = buflen >= 15 ? buffer[14] : 0;
len = 15;
} else if (type == UVC_ITT_MEDIA_TRANSPORT_INPUT) {
n = buflen >= 9 ? buffer[8] : 0;
p = buflen >= 10 + n ? buffer[9+n] : 0;
len = 10;
}
if (buflen < len + n + p) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d INPUT_TERMINAL error\n",
udev->devnum, alts->desc.bInterfaceNumber);
return -EINVAL;
}
term = uvc_alloc_entity(type | UVC_TERM_INPUT, buffer[3],
1, n + p);
if (term == NULL)
return -ENOMEM;
if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) {
term->camera.bControlSize = n;
term->camera.bmControls = (__u8 *)term + sizeof *term;
term->camera.wObjectiveFocalLengthMin =
get_unaligned_le16(&buffer[8]);
term->camera.wObjectiveFocalLengthMax =
get_unaligned_le16(&buffer[10]);
term->camera.wOcularFocalLength =
get_unaligned_le16(&buffer[12]);
memcpy(term->camera.bmControls, &buffer[15], n);
} else if (UVC_ENTITY_TYPE(term) ==
UVC_ITT_MEDIA_TRANSPORT_INPUT) {
term->media.bControlSize = n;
term->media.bmControls = (__u8 *)term + sizeof *term;
term->media.bTransportModeSize = p;
term->media.bmTransportModes = (__u8 *)term
+ sizeof *term + n;
memcpy(term->media.bmControls, &buffer[9], n);
memcpy(term->media.bmTransportModes, &buffer[10+n], p);
}
if (buffer[7] != 0)
usb_string(udev, buffer[7], term->name,
sizeof term->name);
else if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA)
sprintf(term->name, "Camera %u", buffer[3]);
else if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT)
sprintf(term->name, "Media %u", buffer[3]);
else
sprintf(term->name, "Input %u", buffer[3]);
list_add_tail(&term->list, &dev->entities);
break;
case UVC_VC_OUTPUT_TERMINAL:
if (buflen < 9) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d OUTPUT_TERMINAL error\n",
udev->devnum, alts->desc.bInterfaceNumber);
return -EINVAL;
}
/* Make sure the terminal type MSB is not null, otherwise it
* could be confused with a unit.
*/
type = get_unaligned_le16(&buffer[4]);
if ((type & 0xff00) == 0) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d OUTPUT_TERMINAL %d has invalid "
"type 0x%04x, skipping\n", udev->devnum,
alts->desc.bInterfaceNumber, buffer[3], type);
return 0;
}
term = uvc_alloc_entity(type | UVC_TERM_OUTPUT, buffer[3],
1, 0);
if (term == NULL)
return -ENOMEM;
memcpy(term->baSourceID, &buffer[7], 1);
if (buffer[8] != 0)
usb_string(udev, buffer[8], term->name,
sizeof term->name);
else
sprintf(term->name, "Output %u", buffer[3]);
list_add_tail(&term->list, &dev->entities);
break;
case UVC_VC_SELECTOR_UNIT:
p = buflen >= 5 ? buffer[4] : 0;
if (buflen < 5 || buflen < 6 + p) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d SELECTOR_UNIT error\n",
udev->devnum, alts->desc.bInterfaceNumber);
return -EINVAL;
}
unit = uvc_alloc_entity(buffer[2], buffer[3], p + 1, 0);
if (unit == NULL)
return -ENOMEM;
memcpy(unit->baSourceID, &buffer[5], p);
if (buffer[5+p] != 0)
usb_string(udev, buffer[5+p], unit->name,
sizeof unit->name);
else
sprintf(unit->name, "Selector %u", buffer[3]);
list_add_tail(&unit->list, &dev->entities);
break;
case UVC_VC_PROCESSING_UNIT:
n = buflen >= 8 ? buffer[7] : 0;
p = dev->uvc_version >= 0x0110 ? 10 : 9;
if (buflen < p + n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d PROCESSING_UNIT error\n",
udev->devnum, alts->desc.bInterfaceNumber);
return -EINVAL;
}
unit = uvc_alloc_entity(buffer[2], buffer[3], 2, n);
if (unit == NULL)
return -ENOMEM;
memcpy(unit->baSourceID, &buffer[4], 1);
unit->processing.wMaxMultiplier =
get_unaligned_le16(&buffer[5]);
unit->processing.bControlSize = buffer[7];
unit->processing.bmControls = (__u8 *)unit + sizeof *unit;
memcpy(unit->processing.bmControls, &buffer[8], n);
if (dev->uvc_version >= 0x0110)
unit->processing.bmVideoStandards = buffer[9+n];
if (buffer[8+n] != 0)
usb_string(udev, buffer[8+n], unit->name,
sizeof unit->name);
else
sprintf(unit->name, "Processing %u", buffer[3]);
list_add_tail(&unit->list, &dev->entities);
break;
case UVC_VC_EXTENSION_UNIT:
p = buflen >= 22 ? buffer[21] : 0;
n = buflen >= 24 + p ? buffer[22+p] : 0;
if (buflen < 24 + p + n) {
uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
"interface %d EXTENSION_UNIT error\n",
udev->devnum, alts->desc.bInterfaceNumber);
return -EINVAL;
}
unit = uvc_alloc_entity(buffer[2], buffer[3], p + 1, n);
if (unit == NULL)
return -ENOMEM;
memcpy(unit->extension.guidExtensionCode, &buffer[4], 16);
unit->extension.bNumControls = buffer[20];
memcpy(unit->baSourceID, &buffer[22], p);
unit->extension.bControlSize = buffer[22+p];
unit->extension.bmControls = (__u8 *)unit + sizeof *unit;
memcpy(unit->extension.bmControls, &buffer[23+p], n);
if (buffer[23+p+n] != 0)
usb_string(udev, buffer[23+p+n], unit->name,
sizeof unit->name);
else
sprintf(unit->name, "Extension %u", buffer[3]);
list_add_tail(&unit->list, &dev->entities);
break;
default:
uvc_trace(UVC_TRACE_DESCR, "Found an unknown CS_INTERFACE "
"descriptor (%u)\n", buffer[2]);
break;
}
return 0;
}
static int uvc_parse_control(struct uvc_device *dev)
{
struct usb_host_interface *alts = dev->intf->cur_altsetting;
unsigned char *buffer = alts->extra;
int buflen = alts->extralen;
int ret;
/* Parse the default alternate setting only, as the UVC specification
* defines a single alternate setting, the default alternate setting
* zero.
*/
while (buflen > 2) {
if (uvc_parse_vendor_control(dev, buffer, buflen) ||
buffer[1] != USB_DT_CS_INTERFACE)
goto next_descriptor;
if ((ret = uvc_parse_standard_control(dev, buffer, buflen)) < 0)
return ret;
next_descriptor:
buflen -= buffer[0];
buffer += buffer[0];
}
/* Check if the optional status endpoint is present. Built-in iSight
* webcams have an interrupt endpoint but spit proprietary data that
* don't conform to the UVC status endpoint messages. Don't try to
* handle the interrupt endpoint for those cameras.
*/
if (alts->desc.bNumEndpoints == 1 &&
!(dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)) {
struct usb_host_endpoint *ep = &alts->endpoint[0];
struct usb_endpoint_descriptor *desc = &ep->desc;
if (usb_endpoint_is_int_in(desc) &&
le16_to_cpu(desc->wMaxPacketSize) >= 8 &&
desc->bInterval != 0) {
uvc_trace(UVC_TRACE_DESCR, "Found a Status endpoint "
"(addr %02x).\n", desc->bEndpointAddress);
dev->int_ep = ep;
}
}
return 0;
}
/* ------------------------------------------------------------------------
* UVC device scan
*/
/*
* Scan the UVC descriptors to locate a chain starting at an Output Terminal
* and containing the following units:
*
* - one or more Output Terminals (USB Streaming or Display)
* - zero or one Processing Unit
* - zero, one or more single-input Selector Units
* - zero or one multiple-input Selector Units, provided all inputs are
* connected to input terminals
* - zero, one or mode single-input Extension Units
* - one or more Input Terminals (Camera, External or USB Streaming)
*
* The terminal and units must match on of the following structures:
*
* ITT_*(0) -> +---------+ +---------+ +---------+ -> TT_STREAMING(0)
* ... | SU{0,1} | -> | PU{0,1} | -> | XU{0,n} | ...
* ITT_*(n) -> +---------+ +---------+ +---------+ -> TT_STREAMING(n)
*
* +---------+ +---------+ -> OTT_*(0)
* TT_STREAMING -> | PU{0,1} | -> | XU{0,n} | ...
* +---------+ +---------+ -> OTT_*(n)
*
* The Processing Unit and Extension Units can be in any order. Additional
* Extension Units connected to the main chain as single-unit branches are
* also supported. Single-input Selector Units are ignored.
*/
static int uvc_scan_chain_entity(struct uvc_video_chain *chain,
struct uvc_entity *entity)
{
switch (UVC_ENTITY_TYPE(entity)) {
case UVC_VC_EXTENSION_UNIT:
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" <- XU %d", entity->id);
if (entity->bNrInPins != 1) {
uvc_trace(UVC_TRACE_DESCR, "Extension unit %d has more "
"than 1 input pin.\n", entity->id);
return -1;
}
break;
case UVC_VC_PROCESSING_UNIT:
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" <- PU %d", entity->id);
if (chain->processing != NULL) {
uvc_trace(UVC_TRACE_DESCR, "Found multiple "
"Processing Units in chain.\n");
return -1;
}
chain->processing = entity;
break;
case UVC_VC_SELECTOR_UNIT:
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" <- SU %d", entity->id);
/* Single-input selector units are ignored. */
if (entity->bNrInPins == 1)
break;
if (chain->selector != NULL) {
uvc_trace(UVC_TRACE_DESCR, "Found multiple Selector "
"Units in chain.\n");
return -1;
}
chain->selector = entity;
break;
case UVC_ITT_VENDOR_SPECIFIC:
case UVC_ITT_CAMERA:
case UVC_ITT_MEDIA_TRANSPORT_INPUT:
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" <- IT %d\n", entity->id);
break;
case UVC_OTT_VENDOR_SPECIFIC:
case UVC_OTT_DISPLAY:
case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" OT %d", entity->id);
break;
case UVC_TT_STREAMING:
if (UVC_ENTITY_IS_ITERM(entity)) {
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" <- IT %d\n", entity->id);
} else {
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" OT %d", entity->id);
}
break;
default:
uvc_trace(UVC_TRACE_DESCR, "Unsupported entity type "
"0x%04x found in chain.\n", UVC_ENTITY_TYPE(entity));
return -1;
}
list_add_tail(&entity->chain, &chain->entities);
return 0;
}
static int uvc_scan_chain_forward(struct uvc_video_chain *chain,
struct uvc_entity *entity, struct uvc_entity *prev)
{
struct uvc_entity *forward;
int found;
/* Forward scan */
forward = NULL;
found = 0;
while (1) {
forward = uvc_entity_by_reference(chain->dev, entity->id,
forward);
if (forward == NULL)
break;
if (forward == prev)
continue;
switch (UVC_ENTITY_TYPE(forward)) {
case UVC_VC_EXTENSION_UNIT:
if (forward->bNrInPins != 1) {
uvc_trace(UVC_TRACE_DESCR, "Extension unit %d "
"has more than 1 input pin.\n",
entity->id);
return -EINVAL;
}
list_add_tail(&forward->chain, &chain->entities);
if (uvc_trace_param & UVC_TRACE_PROBE) {
if (!found)
printk(" (->");
printk(" XU %d", forward->id);
found = 1;
}
break;
case UVC_OTT_VENDOR_SPECIFIC:
case UVC_OTT_DISPLAY:
case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
case UVC_TT_STREAMING:
if (UVC_ENTITY_IS_ITERM(forward)) {
uvc_trace(UVC_TRACE_DESCR, "Unsupported input "
"terminal %u.\n", forward->id);
return -EINVAL;
}
list_add_tail(&forward->chain, &chain->entities);
if (uvc_trace_param & UVC_TRACE_PROBE) {
if (!found)
printk(" (->");
printk(" OT %d", forward->id);
found = 1;
}
break;
}
}
if (found)
printk(")");
return 0;
}
static int uvc_scan_chain_backward(struct uvc_video_chain *chain,
struct uvc_entity **_entity)
{
struct uvc_entity *entity = *_entity;
struct uvc_entity *term;
int id = -EINVAL, i;
switch (UVC_ENTITY_TYPE(entity)) {
case UVC_VC_EXTENSION_UNIT:
case UVC_VC_PROCESSING_UNIT:
id = entity->baSourceID[0];
break;
case UVC_VC_SELECTOR_UNIT:
/* Single-input selector units are ignored. */
if (entity->bNrInPins == 1) {
id = entity->baSourceID[0];
break;
}
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" <- IT");
chain->selector = entity;
for (i = 0; i < entity->bNrInPins; ++i) {
id = entity->baSourceID[i];
term = uvc_entity_by_id(chain->dev, id);
if (term == NULL || !UVC_ENTITY_IS_ITERM(term)) {
uvc_trace(UVC_TRACE_DESCR, "Selector unit %d "
"input %d isn't connected to an "
"input terminal\n", entity->id, i);
return -1;
}
if (uvc_trace_param & UVC_TRACE_PROBE)
printk(" %d", term->id);
list_add_tail(&term->chain, &chain->entities);
uvc_scan_chain_forward(chain, term, entity);
}
if (uvc_trace_param & UVC_TRACE_PROBE)
printk("\n");
id = 0;
break;
case UVC_ITT_VENDOR_SPECIFIC:
case UVC_ITT_CAMERA:
case UVC_ITT_MEDIA_TRANSPORT_INPUT:
case UVC_OTT_VENDOR_SPECIFIC:
case UVC_OTT_DISPLAY:
case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
case UVC_TT_STREAMING:
id = UVC_ENTITY_IS_OTERM(entity) ? entity->baSourceID[0] : 0;
break;
}
if (id <= 0) {
*_entity = NULL;
return id;
}
entity = uvc_entity_by_id(chain->dev, id);
if (entity == NULL) {
uvc_trace(UVC_TRACE_DESCR, "Found reference to "
"unknown entity %d.\n", id);
return -EINVAL;
}
*_entity = entity;
return 0;
}
static int uvc_scan_chain(struct uvc_video_chain *chain,
struct uvc_entity *term)
{
struct uvc_entity *entity, *prev;
uvc_trace(UVC_TRACE_PROBE, "Scanning UVC chain:");
entity = term;
prev = NULL;
while (entity != NULL) {
/* Entity must not be part of an existing chain */
if (entity->chain.next || entity->chain.prev) {
uvc_trace(UVC_TRACE_DESCR, "Found reference to "
"entity %d already in chain.\n", entity->id);
return -EINVAL;
}
/* Process entity */
if (uvc_scan_chain_entity(chain, entity) < 0)
return -EINVAL;
/* Forward scan */
if (uvc_scan_chain_forward(chain, entity, prev) < 0)
return -EINVAL;
/* Backward scan */
prev = entity;
if (uvc_scan_chain_backward(chain, &entity) < 0)
return -EINVAL;
}
return 0;
}
static unsigned int uvc_print_terms(struct list_head *terms, u16 dir,
char *buffer)
{
struct uvc_entity *term;
unsigned int nterms = 0;
char *p = buffer;
list_for_each_entry(term, terms, chain) {
if (!UVC_ENTITY_IS_TERM(term) ||
UVC_TERM_DIRECTION(term) != dir)
continue;
if (nterms)
p += sprintf(p, ",");
if (++nterms >= 4) {
p += sprintf(p, "...");
break;
}
p += sprintf(p, "%u", term->id);
}
return p - buffer;
}
static const char *uvc_print_chain(struct uvc_video_chain *chain)
{
static char buffer[43];
char *p = buffer;
p += uvc_print_terms(&chain->entities, UVC_TERM_INPUT, p);
p += sprintf(p, " -> ");
uvc_print_terms(&chain->entities, UVC_TERM_OUTPUT, p);
return buffer;
}
/*
* Scan the device for video chains and register video devices.
*
* Chains are scanned starting at their output terminals and walked backwards.
*/
static int uvc_scan_device(struct uvc_device *dev)
{
struct uvc_video_chain *chain;
struct uvc_entity *term;
list_for_each_entry(term, &dev->entities, list) {
if (!UVC_ENTITY_IS_OTERM(term))
continue;
/* If the terminal is already included in a chain, skip it.
* This can happen for chains that have multiple output
* terminals, where all output terminals beside the first one
* will be inserted in the chain in forward scans.
*/
if (term->chain.next || term->chain.prev)
continue;
chain = kzalloc(sizeof(*chain), GFP_KERNEL);
if (chain == NULL)
return -ENOMEM;
INIT_LIST_HEAD(&chain->entities);
mutex_init(&chain->ctrl_mutex);
chain->dev = dev;
v4l2_prio_init(&chain->prio);
term->flags |= UVC_ENTITY_FLAG_DEFAULT;
if (uvc_scan_chain(chain, term) < 0) {
kfree(chain);
continue;
}
uvc_trace(UVC_TRACE_PROBE, "Found a valid video chain (%s).\n",
uvc_print_chain(chain));
list_add_tail(&chain->list, &dev->chains);
}
if (list_empty(&dev->chains)) {
uvc_printk(KERN_INFO, "No valid video chain found.\n");
return -1;
}
return 0;
}
/* ------------------------------------------------------------------------
* Video device registration and unregistration
*/
/*
* Delete the UVC device.
*
* Called by the kernel when the last reference to the uvc_device structure
* is released.
*
* As this function is called after or during disconnect(), all URBs have
* already been canceled by the USB core. There is no need to kill the
* interrupt URB manually.
*/
static void uvc_delete(struct uvc_device *dev)
{
struct list_head *p, *n;
usb_put_intf(dev->intf);
usb_put_dev(dev->udev);
uvc_status_cleanup(dev);
uvc_ctrl_cleanup_device(dev);
if (dev->vdev.dev)
v4l2_device_unregister(&dev->vdev);
#ifdef CONFIG_MEDIA_CONTROLLER
if (media_devnode_is_registered(&dev->mdev.devnode))
media_device_unregister(&dev->mdev);
#endif
list_for_each_safe(p, n, &dev->chains) {
struct uvc_video_chain *chain;
chain = list_entry(p, struct uvc_video_chain, list);
kfree(chain);
}
list_for_each_safe(p, n, &dev->entities) {
struct uvc_entity *entity;
entity = list_entry(p, struct uvc_entity, list);
#ifdef CONFIG_MEDIA_CONTROLLER
uvc_mc_cleanup_entity(entity);
#endif
if (entity->vdev) {
video_device_release(entity->vdev);
entity->vdev = NULL;
}
kfree(entity);
}
list_for_each_safe(p, n, &dev->streams) {
struct uvc_streaming *streaming;
streaming = list_entry(p, struct uvc_streaming, list);
usb_driver_release_interface(&uvc_driver.driver,
streaming->intf);
usb_put_intf(streaming->intf);
kfree(streaming->format);
kfree(streaming->header.bmaControls);
kfree(streaming);
}
kfree(dev);
}
static void uvc_release(struct video_device *vdev)
{
struct uvc_streaming *stream = video_get_drvdata(vdev);
struct uvc_device *dev = stream->dev;
/* Decrement the registered streams count and delete the device when it
* reaches zero.
*/
if (atomic_dec_and_test(&dev->nstreams))
uvc_delete(dev);
}
/*
* Unregister the video devices.
*/
static void uvc_unregister_video(struct uvc_device *dev)
{
struct uvc_streaming *stream;
/* Unregistering all video devices might result in uvc_delete() being
* called from inside the loop if there's no open file handle. To avoid
* that, increment the stream count before iterating over the streams
* and decrement it when done.
*/
atomic_inc(&dev->nstreams);
list_for_each_entry(stream, &dev->streams, list) {
if (stream->vdev == NULL)
continue;
video_unregister_device(stream->vdev);
stream->vdev = NULL;
uvc_debugfs_cleanup_stream(stream);
}
/* Decrement the stream count and call uvc_delete explicitly if there
* are no stream left.
*/
if (atomic_dec_and_test(&dev->nstreams))
uvc_delete(dev);
}
static int uvc_register_video(struct uvc_device *dev,
struct uvc_streaming *stream)
{
struct video_device *vdev;
int ret;
/* Initialize the streaming interface with default streaming
* parameters.
*/
ret = uvc_video_init(stream);
if (ret < 0) {
uvc_printk(KERN_ERR, "Failed to initialize the device "
"(%d).\n", ret);
return ret;
}
uvc_debugfs_init_stream(stream);
/* Register the device with V4L. */
vdev = video_device_alloc();
if (vdev == NULL) {
uvc_printk(KERN_ERR, "Failed to allocate video device (%d).\n",
ret);
return -ENOMEM;
}
/* We already hold a reference to dev->udev. The video device will be
* unregistered before the reference is released, so we don't need to
* get another one.
*/
vdev->v4l2_dev = &dev->vdev;
vdev->fops = &uvc_fops;
vdev->release = uvc_release;
vdev->prio = &stream->chain->prio;
set_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags);
if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
vdev->vfl_dir = VFL_DIR_TX;
strlcpy(vdev->name, dev->name, sizeof vdev->name);
/* Set the driver data before calling video_register_device, otherwise
* uvc_v4l2_open might race us.
*/
stream->vdev = vdev;
video_set_drvdata(vdev, stream);
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
if (ret < 0) {
uvc_printk(KERN_ERR, "Failed to register video device (%d).\n",
ret);
stream->vdev = NULL;
video_device_release(vdev);
return ret;
}
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE;
else
stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;
atomic_inc(&dev->nstreams);
return 0;
}
/*
* Register all video devices in all chains.
*/
static int uvc_register_terms(struct uvc_device *dev,
struct uvc_video_chain *chain)
{
struct uvc_streaming *stream;
struct uvc_entity *term;
int ret;
list_for_each_entry(term, &chain->entities, chain) {
if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING)
continue;
stream = uvc_stream_by_id(dev, term->id);
if (stream == NULL) {
uvc_printk(KERN_INFO, "No streaming interface found "
"for terminal %u.", term->id);
continue;
}
stream->chain = chain;
ret = uvc_register_video(dev, stream);
if (ret < 0)
return ret;
term->vdev = stream->vdev;
}
return 0;
}
static int uvc_register_chains(struct uvc_device *dev)
{
struct uvc_video_chain *chain;
int ret;
list_for_each_entry(chain, &dev->chains, list) {
ret = uvc_register_terms(dev, chain);
if (ret < 0)
return ret;
#ifdef CONFIG_MEDIA_CONTROLLER
ret = uvc_mc_register_entities(chain);
if (ret < 0) {
uvc_printk(KERN_INFO, "Failed to register entites "
"(%d).\n", ret);
}
#endif
}
return 0;
}
/* ------------------------------------------------------------------------
* USB probe, disconnect, suspend and resume
*/
static int uvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct uvc_device *dev;
int ret;
if (id->idVendor && id->idProduct)
uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
"(%04x:%04x)\n", udev->devpath, id->idVendor,
id->idProduct);
else
uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",
udev->devpath);
/* Allocate memory for the device and initialize it. */
if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)
return -ENOMEM;
INIT_LIST_HEAD(&dev->entities);
INIT_LIST_HEAD(&dev->chains);
INIT_LIST_HEAD(&dev->streams);
atomic_set(&dev->nstreams, 0);
atomic_set(&dev->nmappings, 0);
mutex_init(&dev->lock);
dev->udev = usb_get_dev(udev);
dev->intf = usb_get_intf(intf);
dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
dev->quirks = (uvc_quirks_param == -1)
? id->driver_info : uvc_quirks_param;
if (udev->product != NULL)
strlcpy(dev->name, udev->product, sizeof dev->name);
else
snprintf(dev->name, sizeof dev->name,
"UVC Camera (%04x:%04x)",
le16_to_cpu(udev->descriptor.idVendor),
le16_to_cpu(udev->descriptor.idProduct));
/* Parse the Video Class control descriptor. */
if (uvc_parse_control(dev) < 0) {
uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
"descriptors.\n");
goto error;
}
uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
dev->uvc_version >> 8, dev->uvc_version & 0xff,
udev->product ? udev->product : "<unnamed>",
le16_to_cpu(udev->descriptor.idVendor),
le16_to_cpu(udev->descriptor.idProduct));
if (dev->quirks != id->driver_info) {
uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module "
"parameter for testing purpose.\n", dev->quirks);
uvc_printk(KERN_INFO, "Please report required quirks to the "
"linux-uvc-devel mailing list.\n");
}
/* Register the media and V4L2 devices. */
#ifdef CONFIG_MEDIA_CONTROLLER
dev->mdev.dev = &intf->dev;
strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
if (udev->serial)
strlcpy(dev->mdev.serial, udev->serial,
sizeof(dev->mdev.serial));
strcpy(dev->mdev.bus_info, udev->devpath);
dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
dev->mdev.driver_version = LINUX_VERSION_CODE;
if (media_device_register(&dev->mdev) < 0)
goto error;
dev->vdev.mdev = &dev->mdev;
#endif
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
goto error;
/* Initialize controls. */
if (uvc_ctrl_init_device(dev) < 0)
goto error;
/* Scan the device for video chains. */
if (uvc_scan_device(dev) < 0)
goto error;
/* Register video device nodes. */
if (uvc_register_chains(dev) < 0)
goto error;
/* Save our data pointer in the interface data. */
usb_set_intfdata(intf, dev);
/* Initialize the interrupt URB. */
if ((ret = uvc_status_init(dev)) < 0) {
uvc_printk(KERN_INFO, "Unable to initialize the status "
"endpoint (%d), status interrupt will not be "
"supported.\n", ret);
}
uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");
usb_enable_autosuspend(udev);
return 0;
error:
uvc_unregister_video(dev);
return -ENODEV;
}
static void uvc_disconnect(struct usb_interface *intf)
{
struct uvc_device *dev = usb_get_intfdata(intf);
/* Set the USB interface data to NULL. This can be done outside the
* lock, as there's no other reader.
*/
usb_set_intfdata(intf, NULL);
if (intf->cur_altsetting->desc.bInterfaceSubClass ==
UVC_SC_VIDEOSTREAMING)
return;
dev->state |= UVC_DEV_DISCONNECTED;
V4L/DVB (8257): uvcvideo: Fix possible AB-BA deadlock with videodev_lock and open_mutex The uvcvideo driver's uvc_v4l2_open() method is called from videodev's video_open() function, which means it is called with the videodev_lock mutex held. uvc_v4l2_open() then takes uvc_driver.open_mutex to check dev->state and avoid racing against a device disconnect, which means that open_mutex must nest inside videodev_lock. However uvc_disconnect() takes the open_mutex around setting dev->state and also around putting its device reference. However, if uvc_disconnect() ends up dropping the last reference, it will call uvc_delete(), which calls into the videodev code to unregister its device, and this will end up taking videodev_lock. This opens a (unlikely in practice) window for an AB-BA deadlock and also causes a lockdep warning because of the lock misordering. Fortunately there is no apparent reason to hold open_mutex when doing kref_put() in uvc_disconnect(): if uvc_v4l2_open() runs before the state is set to UVC_DEV_DISCONNECTED, then it will take another reference to the device and kref_put() won't call uvc_delete; if uvc_v4l2_open() runs after the state is set, it will run before uvc_delete(), see the state, and return immediately -- uvc_delete() does uvc_unregister_video() (and hence video_unregister_device(), which is synchronized with videodev_lock) as its first thing, so there is no risk of use-after-free in uvc_v4l2_open(). Bug diagnosed based on a lockdep warning reported by Romano Giannetti <romano@dea.icai.upcomillas.es>. Signed-off-by: Roland Dreier <roland@digitalvampire.org> Signed-off-by: Laurent Pinchart <laurent.pinchart@skynet.be> Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
2008-07-12 06:32:15 +08:00
uvc_unregister_video(dev);
}
static int uvc_suspend(struct usb_interface *intf, pm_message_t message)
{
struct uvc_device *dev = usb_get_intfdata(intf);
struct uvc_streaming *stream;
uvc_trace(UVC_TRACE_SUSPEND, "Suspending interface %u\n",
intf->cur_altsetting->desc.bInterfaceNumber);
/* Controls are cached on the fly so they don't need to be saved. */
if (intf->cur_altsetting->desc.bInterfaceSubClass ==
UVC_SC_VIDEOCONTROL) {
mutex_lock(&dev->lock);
if (dev->users)
uvc_status_stop(dev);
mutex_unlock(&dev->lock);
return 0;
}
list_for_each_entry(stream, &dev->streams, list) {
if (stream->intf == intf)
return uvc_video_suspend(stream);
}
uvc_trace(UVC_TRACE_SUSPEND, "Suspend: video streaming USB interface "
"mismatch.\n");
return -EINVAL;
}
static int __uvc_resume(struct usb_interface *intf, int reset)
{
struct uvc_device *dev = usb_get_intfdata(intf);
struct uvc_streaming *stream;
uvc_trace(UVC_TRACE_SUSPEND, "Resuming interface %u\n",
intf->cur_altsetting->desc.bInterfaceNumber);
if (intf->cur_altsetting->desc.bInterfaceSubClass ==
UVC_SC_VIDEOCONTROL) {
int ret = 0;
if (reset) {
ret = uvc_ctrl_resume_device(dev);
if (ret < 0)
return ret;
}
mutex_lock(&dev->lock);
if (dev->users)
ret = uvc_status_start(dev, GFP_NOIO);
mutex_unlock(&dev->lock);
return ret;
}
list_for_each_entry(stream, &dev->streams, list) {
if (stream->intf == intf)
return uvc_video_resume(stream, reset);
}
uvc_trace(UVC_TRACE_SUSPEND, "Resume: video streaming USB interface "
"mismatch.\n");
return -EINVAL;
}
static int uvc_resume(struct usb_interface *intf)
{
return __uvc_resume(intf, 0);
}
static int uvc_reset_resume(struct usb_interface *intf)
{
return __uvc_resume(intf, 1);
}
/* ------------------------------------------------------------------------
* Module parameters
*/
static int uvc_clock_param_get(char *buffer, struct kernel_param *kp)
{
if (uvc_clock_param == CLOCK_MONOTONIC)
return sprintf(buffer, "CLOCK_MONOTONIC");
else
return sprintf(buffer, "CLOCK_REALTIME");
}
static int uvc_clock_param_set(const char *val, struct kernel_param *kp)
{
if (strncasecmp(val, "clock_", strlen("clock_")) == 0)
val += strlen("clock_");
if (strcasecmp(val, "monotonic") == 0)
uvc_clock_param = CLOCK_MONOTONIC;
else if (strcasecmp(val, "realtime") == 0)
uvc_clock_param = CLOCK_REALTIME;
else
return -EINVAL;
return 0;
}
module_param_call(clock, uvc_clock_param_set, uvc_clock_param_get,
&uvc_clock_param, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(clock, "Video buffers timestamp clock");
module_param_named(nodrop, uvc_no_drop_param, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(nodrop, "Don't drop incomplete frames");
module_param_named(quirks, uvc_quirks_param, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(quirks, "Forced device quirks");
module_param_named(trace, uvc_trace_param, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(trace, "Trace level bitmask");
module_param_named(timeout, uvc_timeout_param, uint, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(timeout, "Streaming control requests timeout");
/* ------------------------------------------------------------------------
* Driver initialization and cleanup
*/
/*
* The Logitech cameras listed below have their interface class set to
* VENDOR_SPEC because they don't announce themselves as UVC devices, even
* though they are compliant.
*/
static struct usb_device_id uvc_ids[] = {
/* LogiLink Wireless Webcam */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0416,
.idProduct = 0xa91a,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Genius eFace 2025 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0458,
.idProduct = 0x706e,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Microsoft Lifecam NX-6000 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x045e,
.idProduct = 0x00f8,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Microsoft Lifecam NX-3000 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x045e,
.idProduct = 0x0721,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_DEF },
/* Microsoft Lifecam VX-7000 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x045e,
.idProduct = 0x0723,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Logitech Quickcam Fusion */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x046d,
.idProduct = 0x08c1,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0 },
/* Logitech Quickcam Orbit MP */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x046d,
.idProduct = 0x08c2,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0 },
/* Logitech Quickcam Pro for Notebook */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x046d,
.idProduct = 0x08c3,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0 },
/* Logitech Quickcam Pro 5000 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x046d,
.idProduct = 0x08c5,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0 },
/* Logitech Quickcam OEM Dell Notebook */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x046d,
.idProduct = 0x08c6,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0 },
/* Logitech Quickcam OEM Cisco VT Camera II */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x046d,
.idProduct = 0x08c7,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0 },
/* Chicony CNF7129 (Asus EEE 100HE) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x04f2,
.idProduct = 0xb071,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_RESTRICT_FRAME_RATE },
/* Alcor Micro AU3820 (Future Boy PC USB Webcam) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x058f,
.idProduct = 0x3820,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Dell XPS m1530 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x05a9,
.idProduct = 0x2640,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_DEF },
/* Dell SP2008WFP Monitor */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x05a9,
.idProduct = 0x2641,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_DEF },
/* Dell Alienware X51 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x05a9,
.idProduct = 0x2643,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_DEF },
/* Dell Studio Hybrid 140g (OmniVision webcam) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x05a9,
.idProduct = 0x264a,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_DEF },
/* Apple Built-In iSight */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x05ac,
.idProduct = 0x8501,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX
| UVC_QUIRK_BUILTIN_ISIGHT },
[media] uvcvideo: Add FIX_BANDWIDTH quirk to HP Webcam on HP Mini 5103 netbook The camera there identifies itself as being manufactured by Cheng Uei Precision Industry Co., Ltd (Foxlink), and product is titled as "HP Webcam [2 MP Fixed]". I was trying to get 2 USB video capture devices to work simultaneously, and noticed that the above mentioned webcam always requires packet size = 3072 bytes per micro frame (~= 23.4 MB/s isoc bandwidth), which is far more than enough to get standard NTSC 640x480x2x30 = ~17.6 MB/s isoc bandwidth. As there are alt interfaces with smaller MxPS T: Bus=01 Lev=01 Prnt=01 Port=03 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=05c8 ProdID=0403 Rev= 1.06 S: Manufacturer=Foxlink S: Product=HP Webcam [2 MP Fixed] S: SerialNumber=200909240102 C:* #Ifs= 2 Cfg#= 1 Atr=80 MxPwr=500mA A: FirstIf#= 0 IfCount= 2 Cls=0e(video) Sub=03 Prot=00 I:* If#= 0 Alt= 0 #EPs= 1 Cls=0e(video) Sub=01 Prot=00 Driver=uvcvideo E: Ad=83(I) Atr=03(Int.) MxPS= 16 Ivl=4ms I:* If#= 1 Alt= 0 #EPs= 0 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo I: If#= 1 Alt= 1 #EPs= 1 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo E: Ad=81(I) Atr=05(Isoc) MxPS= 128 Ivl=125us I: If#= 1 Alt= 2 #EPs= 1 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo E: Ad=81(I) Atr=05(Isoc) MxPS= 512 Ivl=125us I: If#= 1 Alt= 3 #EPs= 1 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo E: Ad=81(I) Atr=05(Isoc) MxPS=1024 Ivl=125us I: If#= 1 Alt= 4 #EPs= 1 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo E: Ad=81(I) Atr=05(Isoc) MxPS=1536 Ivl=125us I: If#= 1 Alt= 5 #EPs= 1 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo E: Ad=81(I) Atr=05(Isoc) MxPS=2048 Ivl=125us I: If#= 1 Alt= 6 #EPs= 1 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo E: Ad=81(I) Atr=05(Isoc) MxPS=2688 Ivl=125us I: If#= 1 Alt= 7 #EPs= 1 Cls=0e(video) Sub=02 Prot=00 Driver=uvcvideo E: Ad=81(I) Atr=05(Isoc) MxPS=3072 Ivl=125us UVC_QUIRK_FIX_BANDWIDTH helps here and NTSC video can be served with MxPS=2688 i.e. 20.5 MB/s isoc bandwidth. In terms of microframe time allocation, before the quirk NTSC video required 60 usecs / microframe and 53 usecs / microframe after. Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Kirill Smelkov <kirr@mns.spb.ru> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
2011-07-22 22:47:22 +08:00
/* Foxlink ("HP Webcam" on HP Mini 5103) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x05c8,
.idProduct = 0x0403,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_FIX_BANDWIDTH },
/* Genesys Logic USB 2.0 PC Camera */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x05e3,
.idProduct = 0x0505,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Hercules Classic Silver */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x06f8,
.idProduct = 0x300c,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_FIX_BANDWIDTH },
/* ViMicro Vega */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0ac8,
.idProduct = 0x332d,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_FIX_BANDWIDTH },
/* ViMicro - Minoru3D */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0ac8,
.idProduct = 0x3410,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_FIX_BANDWIDTH },
/* ViMicro Venus - Minoru3D */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0ac8,
.idProduct = 0x3420,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_FIX_BANDWIDTH },
/* Ophir Optronics - SPCAM 620U */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0bd3,
.idProduct = 0x0555,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* MT6227 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0e8d,
.idProduct = 0x0004,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX
| UVC_QUIRK_PROBE_DEF },
/* IMC Networks (Medion Akoya) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x13d3,
.idProduct = 0x5103,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* JMicron USB2.0 XGA WebCam */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x152d,
.idProduct = 0x0310,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Syntek (HP Spartan) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x174f,
.idProduct = 0x5212,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Syntek (Samsung Q310) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x174f,
.idProduct = 0x5931,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Syntek (Packard Bell EasyNote MX52 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x174f,
.idProduct = 0x8a12,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Syntek (Asus F9SG) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x174f,
.idProduct = 0x8a31,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Syntek (Asus U3S) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x174f,
.idProduct = 0x8a33,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Syntek (JAOtech Smart Terminal) */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x174f,
.idProduct = 0x8a34,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Miricle 307K */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x17dc,
.idProduct = 0x0202,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Lenovo Thinkpad SL400/SL500 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x17ef,
.idProduct = 0x480b,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STREAM_NO_FID },
/* Aveo Technology USB 2.0 Camera */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x1871,
.idProduct = 0x0306,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX
| UVC_QUIRK_PROBE_EXTRAFIELDS },
/* Ecamm Pico iMage */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x18cd,
.idProduct = 0xcafe,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_EXTRAFIELDS },
/* Manta MM-353 Plako */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x18ec,
.idProduct = 0x3188,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* FSC WebCam V30S */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x18ec,
.idProduct = 0x3288,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Arkmicro unbranded */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x18ec,
.idProduct = 0x3290,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_DEF },
/* The Imaging Source USB CCD cameras */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x199e,
.idProduct = 0x8102,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0 },
/* Bodelin ProScopeHR */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_DEV_HI
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x19ab,
.idProduct = 0x1000,
.bcdDevice_hi = 0x0126,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_STATUS_INTERVAL },
/* MSI StarCam 370i */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x1b3b,
.idProduct = 0x2951,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
/* SiGma Micro USB Web Camera */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x1c4f,
.idProduct = 0x3000,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX
| UVC_QUIRK_IGNORE_SELECTOR_UNIT },
/* Generic USB Video Class */
{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },
{}
};
MODULE_DEVICE_TABLE(usb, uvc_ids);
struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe,
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,
.supports_autosuspend = 1,
},
};
static int __init uvc_init(void)
{
int ret;
uvc_debugfs_init();
ret = usb_register(&uvc_driver.driver);
if (ret < 0) {
uvc_debugfs_cleanup();
return ret;
}
printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
return 0;
}
static void __exit uvc_cleanup(void)
{
usb_deregister(&uvc_driver.driver);
uvc_debugfs_cleanup();
}
module_init(uvc_init);
module_exit(uvc_cleanup);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);