new file mode 100644
@@ -0,0 +1,674 @@
+/*
+ * uvc_gadget.c -- USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009
+ * 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/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/version.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/video.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+
+#include <media/v4l2-dev.h>
+
+#include "uvc.h"
+
+unsigned int uvc_trace_param;
+
+/* --------------------------------------------------------------------------
+ * Function descriptors
+ */
+
+/* string IDs are assigned dynamically */
+
+#define UVC_STRING_ASSOCIATION_IDX 0
+#define UVC_STRING_CONTROL_IDX 1
+#define UVC_STRING_STREAMING_IDX 2
+
+static struct usb_string uvc_en_us_strings[] = {
+ [UVC_STRING_ASSOCIATION_IDX].s = "UVC Camera",
+ [UVC_STRING_CONTROL_IDX].s = "Video Control",
+ [UVC_STRING_STREAMING_IDX].s = "Video Streaming",
+ { }
+};
+
+static struct usb_gadget_strings uvc_stringtab = {
+ .language = 0x0409, /* en-us */
+ .strings = uvc_en_us_strings,
+};
+
+static struct usb_gadget_strings *uvc_function_strings[] = {
+ &uvc_stringtab,
+ NULL,
+};
+
+#define UVC_INTF_VIDEO_CONTROL 0
+#define UVC_INTF_VIDEO_STREAMING 1
+
+static struct usb_interface_assoc_descriptor uvc_iad __initdata = {
+ .bLength = USB_DT_INTERFACE_ASSOCIATION_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+ .bFirstInterface = 0,
+ .bInterfaceCount = 2,
+ .bFunctionClass = USB_CLASS_VIDEO,
+ .bFunctionSubClass = 0x03,
+ .bFunctionProtocol = 0x00,
+ .iFunction = 0,
+};
+
+static struct usb_interface_descriptor uvc_control_intf __initdata = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = UVC_INTF_VIDEO_CONTROL,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 0x01,
+ .bInterfaceProtocol = 0x00,
+ .iInterface = 0,
+};
+
+static struct usb_endpoint_descriptor uvc_control_ep __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(16),
+ .bInterval = 8,
+};
+
+static struct uvc_control_endpoint_descriptor uvc_control_cs_ep __initdata = {
+ .bLength = UVC_DT_CONTROL_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_CS_ENDPOINT,
+ .bDescriptorSubType = UVC_EP_INTERRUPT,
+ .wMaxTransferSize = cpu_to_le16(16),
+};
+
+static struct usb_interface_descriptor uvc_streaming_intf_alt0 __initdata = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 0x02,
+ .bInterfaceProtocol = 0x00,
+ .iInterface = 0,
+};
+
+static struct usb_interface_descriptor uvc_streaming_intf_alt1 __initdata = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
+ .bAlternateSetting = 1,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 0x02,
+ .bInterfaceProtocol = 0x00,
+ .iInterface = 0,
+};
+
+static struct usb_endpoint_descriptor uvc_streaming_ep = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = cpu_to_le16(1024),
+ .bInterval = 1,
+};
+
+static const struct usb_descriptor_header * const uvc_fs_streaming[] = {
+ (struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
+ (struct usb_descriptor_header *) &uvc_streaming_ep,
+ NULL,
+};
+
+static const struct usb_descriptor_header * const uvc_hs_streaming[] = {
+ (struct usb_descriptor_header *) &uvc_streaming_intf_alt1,
+ (struct usb_descriptor_header *) &uvc_streaming_ep,
+ NULL,
+};
+
+/* --------------------------------------------------------------------------
+ * Control requests
+ */
+
+static void
+uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct uvc_device *uvc = req->context;
+ struct uvc_event event;
+
+ if (uvc->event_setup_out) {
+ uvc->event_setup_out = 0;
+
+ memset(&event, 0, sizeof(event));
+ event.type = UVC_EVENT_DATA;
+ event.data.length = req->actual;
+ memcpy(&event.data.data, req->buf, req->actual);
+ kfifo_put(uvc->events, (unsigned char*)&event, sizeof(event));
+
+ wake_up(&uvc->event_wait);
+ }
+}
+
+static int
+uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct uvc_device *uvc = to_uvc(f);
+ struct uvc_event event;
+
+ /* printk(KERN_INFO "setup request %02x %02x value %04x index %04x %04x\n",
+ * ctrl->bRequestType, ctrl->bRequest, le16_to_cpu(ctrl->wValue),
+ * le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength));
+ */
+
+ if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
+ INFO(f->config->cdev, "invalid request type\n");
+ return -EINVAL;
+ }
+
+ /* Stall too big requests. */
+ if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE)
+ return -EINVAL;
+
+ memset(&event, 0, sizeof(event));
+ event.type = UVC_EVENT_SETUP;
+ memcpy(&event.req, ctrl, sizeof(event.req));
+ kfifo_put(uvc->events, (unsigned char*)&event, sizeof(event));
+
+ wake_up(&uvc->event_wait);
+
+ return 0;
+}
+
+static int
+uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
+{
+ struct uvc_device *uvc = to_uvc(f);
+ struct uvc_event event;
+
+ INFO(f->config->cdev, "uvc_function_set_alt(%u, %u)\n", interface, alt);
+
+ if (interface == uvc->control_intf) {
+ if (alt)
+ return -EINVAL;
+
+ if (uvc->state == UVC_STATE_DISCONNECTED) {
+ memset(&event, 0, sizeof(event));
+ event.type = UVC_EVENT_CONNECT;
+ event.speed = f->config->cdev->gadget->speed;
+ kfifo_put(uvc->events, (unsigned char*)&event,
+ sizeof(event));
+
+ wake_up(&uvc->event_wait);
+
+ uvc->state = UVC_STATE_CONNECTED;
+ }
+
+ return 0;
+ }
+
+ if (interface != uvc->streaming_intf)
+ return -EINVAL;
+
+ /* TODO
+ if (usb_endpoint_xfer_bulk(&uvc->desc.vs_ep))
+ return alt ? -EINVAL : 0;
+ */
+
+ switch (alt) {
+ case 0:
+ if (uvc->state != UVC_STATE_STREAMING)
+ return 0;
+
+ if (uvc->video.ep)
+ usb_ep_disable(uvc->video.ep);
+
+ memset(&event, 0, sizeof(event));
+ event.type = UVC_EVENT_STREAMOFF;
+ kfifo_put(uvc->events, (unsigned char*)&event, sizeof(event));
+
+ wake_up(&uvc->event_wait);
+
+ uvc->state = UVC_STATE_CONNECTED;
+ break;
+
+ case 1:
+ if (uvc->state != UVC_STATE_CONNECTED)
+ return 0;
+
+ if (uvc->video.ep)
+ usb_ep_enable(uvc->video.ep, &uvc_streaming_ep);
+
+ memset(&event, 0, sizeof(event));
+ event.type = UVC_EVENT_STREAMON;
+ kfifo_put(uvc->events, (unsigned char*)&event, sizeof(event));
+
+ wake_up(&uvc->event_wait);
+
+ uvc->state = UVC_STATE_STREAMING;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void
+uvc_function_disable(struct usb_function *f)
+{
+ struct uvc_device *uvc = to_uvc(f);
+ struct uvc_event event;
+
+ INFO(f->config->cdev, "uvc_function_disable\n");
+
+ memset(&event, 0, sizeof(event));
+ event.type = UVC_EVENT_DISCONNECT;
+ kfifo_put(uvc->events, (unsigned char*)&event, sizeof(event));
+
+ wake_up(&uvc->event_wait);
+
+ uvc->state = UVC_STATE_DISCONNECTED;
+}
+
+/* --------------------------------------------------------------------------
+ * Connection / disconnection
+ */
+
+void
+uvc_function_connect(struct uvc_device *uvc)
+{
+ struct usb_composite_dev *cdev = uvc->func.config->cdev;
+ int ret;
+
+ if ((ret = usb_function_activate(&uvc->func)) < 0)
+ INFO(cdev, "UVC connect failed with %d\n", ret);
+}
+
+void
+uvc_function_disconnect(struct uvc_device *uvc)
+{
+ struct usb_composite_dev *cdev = uvc->func.config->cdev;
+ int ret;
+
+ if ((ret = usb_function_deactivate(&uvc->func)) < 0)
+ INFO(cdev, "UVC disconnect failed with %d\n", ret);
+}
+
+/* --------------------------------------------------------------------------
+ * USB probe and disconnect
+ */
+
+static int
+uvc_register_video(struct uvc_device *uvc)
+{
+ struct usb_composite_dev *cdev = uvc->func.config->cdev;
+ struct video_device *video;
+
+ /* TODO reference counting. */
+ video = video_device_alloc();
+ if (video == NULL)
+ return -ENOMEM;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+ video->dev = &cdev->gadget->dev;
+ video->type = 0;
+ video->type2 = 0;
+#else
+ video->parent = &cdev->gadget->dev;
+#endif
+ video->minor = -1;
+ video->fops = &uvc_v4l2_fops;
+ video->release = video_device_release;
+ strncpy(video->name, cdev->gadget->name, sizeof(video->name));
+
+ uvc->vdev = video;
+ video_set_drvdata(video, uvc);
+
+ return video_register_device(video, VFL_TYPE_GRABBER, -1);
+}
+
+#define UVC_COPY_DESCRIPTOR(mem, dst, desc) \
+ do { \
+ memcpy(mem, desc, (desc)->bLength); \
+ *(dst)++ = mem; \
+ mem += (desc)->bLength; \
+ } while (0);
+
+#define UVC_COPY_DESCRIPTORS(mem, dst, src) \
+ do { \
+ const struct usb_descriptor_header * const *__src; \
+ for (__src = src; *__src; ++__src) { \
+ memcpy(mem, *__src, (*__src)->bLength); \
+ *dst++ = mem; \
+ mem += (*__src)->bLength; \
+ } \
+ } while (0)
+
+static struct usb_descriptor_header ** __init
+uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
+{
+ struct uvc_input_header_descriptor *uvc_streaming_header;
+ struct uvc_header_descriptor *uvc_control_header;
+ const struct uvc_descriptor_header * const *uvc_streaming_cls;
+ const struct usb_descriptor_header * const *uvc_streaming_std;
+ const struct usb_descriptor_header * const *src;
+ struct usb_descriptor_header **dst;
+ struct usb_descriptor_header **hdr;
+ unsigned int control_size;
+ unsigned int streaming_size;
+ unsigned int n_desc;
+ unsigned int bytes;
+ void *mem;
+
+ uvc_streaming_cls = (speed == USB_SPEED_FULL)
+ ? uvc->desc.fs_streaming : uvc->desc.hs_streaming;
+ uvc_streaming_std = (speed == USB_SPEED_FULL)
+ ? uvc_fs_streaming : uvc_hs_streaming;
+
+ /* Descriptors layout
+ *
+ * uvc_iad
+ * uvc_control_intf
+ * Class-specific UVC control descriptors
+ * uvc_control_ep
+ * uvc_control_cs_ep
+ * uvc_streaming_intf_alt0
+ * Class-specific UVC streaming descriptors
+ * uvc_{fs|hs}_streaming
+ */
+
+ /* Count descriptors and compute their size. */
+ control_size = 0;
+ streaming_size = 0;
+ bytes = uvc_iad.bLength + uvc_control_intf.bLength
+ + uvc_control_ep.bLength + uvc_control_cs_ep.bLength
+ + uvc_streaming_intf_alt0.bLength;
+ n_desc = 5;
+
+ for (src = (const struct usb_descriptor_header**)uvc->desc.control; *src; ++src) {
+ control_size += (*src)->bLength;
+ bytes += (*src)->bLength;
+ n_desc++;
+ }
+ for (src = (const struct usb_descriptor_header**)uvc_streaming_cls; *src; ++src) {
+ streaming_size += (*src)->bLength;
+ bytes += (*src)->bLength;
+ n_desc++;
+ }
+ for (src = uvc_streaming_std; *src; ++src) {
+ bytes += (*src)->bLength;
+ n_desc++;
+ }
+
+ mem = kmalloc((n_desc + 1) * sizeof(*src) + bytes, GFP_KERNEL);
+ if (mem == NULL)
+ return NULL;
+
+ hdr = mem;
+ dst = mem;
+ mem += (n_desc + 1) * sizeof(*src);
+
+ /* Copy the descriptors. */
+ UVC_COPY_DESCRIPTOR(mem, dst, &uvc_iad);
+ UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_intf);
+
+ uvc_control_header = mem;
+ UVC_COPY_DESCRIPTORS(mem, dst,
+ (const struct usb_descriptor_header**)uvc->desc.control);
+ uvc_control_header->wTotalLength = cpu_to_le16(control_size);
+ uvc_control_header->bInCollection = 1;
+ uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf;
+
+ UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_ep);
+ UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_cs_ep);
+ UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0);
+
+ uvc_streaming_header = mem;
+ UVC_COPY_DESCRIPTORS(mem, dst,
+ (const struct usb_descriptor_header**)uvc_streaming_cls);
+ uvc_streaming_header->wTotalLength = cpu_to_le16(streaming_size);
+ uvc_streaming_header->bEndpointAddress = uvc_streaming_ep.bEndpointAddress;
+
+ UVC_COPY_DESCRIPTORS(mem, dst, uvc_streaming_std);
+
+ *dst = NULL;
+ return hdr;
+}
+
+static void
+uvc_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct uvc_device *uvc = to_uvc(f);
+
+ INFO(cdev, "uvc_function_unbind\n");
+
+ if (uvc->vdev) {
+ if (uvc->vdev->minor == -1)
+ video_device_release(uvc->vdev);
+ else
+ video_unregister_device(uvc->vdev);
+ uvc->vdev = NULL;
+ }
+
+ if (uvc->control_ep)
+ uvc->control_ep->driver_data = NULL;
+ if (uvc->video.ep)
+ uvc->video.ep->driver_data = NULL;
+
+ if (uvc->control_req) {
+ usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
+ kfree(uvc->control_buf);
+ }
+
+ kfree(f->descriptors);
+ kfree(f->hs_descriptors);
+
+ if (uvc->events)
+ kfifo_free(uvc->events);
+
+ kfree(uvc);
+}
+
+static int __init
+uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct uvc_device *uvc = to_uvc(f);
+ struct usb_ep *ep;
+ int ret = -EINVAL;
+
+ INFO(cdev, "uvc_function_bind\n");
+
+ /* Allocate endpoints. */
+ ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep);
+ if (!ep) {
+ INFO(cdev, "Unable to allocate control EP\n");
+ goto error;
+ }
+ uvc->control_ep = ep;
+ ep->driver_data = uvc;
+
+ ep = usb_ep_autoconfig(cdev->gadget, &uvc_streaming_ep);
+ if (!ep) {
+ INFO(cdev, "Unable to allocate streaming EP\n");
+ goto error;
+ }
+ uvc->video.ep = ep;
+ ep->driver_data = uvc;
+
+ /* Allocate interface IDs. */
+ if ((ret = usb_interface_id(c, f)) < 0)
+ goto error;
+ uvc_iad.bFirstInterface = ret;
+ uvc_control_intf.bInterfaceNumber = ret;
+ uvc->control_intf = ret;
+
+ if ((ret = usb_interface_id(c, f)) < 0)
+ goto error;
+ uvc_streaming_intf_alt0.bInterfaceNumber = ret;
+ uvc_streaming_intf_alt1.bInterfaceNumber = ret;
+ uvc->streaming_intf = ret;
+
+ /* Copy descriptors. */
+ f->descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL);
+ f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH);
+
+ /* Preallocate control endpoint request. */
+ uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
+ uvc->control_buf = kmalloc(UVC_MAX_REQUEST_SIZE, GFP_KERNEL);
+ if (uvc->control_req == NULL || uvc->control_buf == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ uvc->control_req->buf = uvc->control_buf;
+ uvc->control_req->complete = uvc_function_ep0_complete;
+ uvc->control_req->context = uvc;
+
+ /* Avoid letting this gadget enumerate until the userspace server is
+ * active.
+ */
+ if ((ret = usb_function_deactivate(f)) < 0)
+ goto error;
+
+ /* Initialise video. */
+ ret = uvc_video_init(&uvc->video);
+ if (ret < 0)
+ goto error;
+
+ /* Register a V4L2 device. */
+ ret = uvc_register_video(uvc);
+ if (ret < 0) {
+ printk(KERN_INFO "Unable to register video device\n");
+ goto error;
+ }
+
+ return 0;
+
+error:
+ uvc_function_unbind(c, f);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * USB gadget function
+ */
+
+/**
+ * uvc_bind_config - add a UVC function to a configuration
+ * @c: the configuration to support the UVC instance
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @uvc_setup(). Caller is also responsible for
+ * calling @uvc_cleanup() before module unload.
+ */
+int __init
+uvc_bind_config(struct usb_configuration *c,
+ const struct uvc_descriptor_header * const *control,
+ const struct uvc_descriptor_header * const *fs_streaming,
+ const struct uvc_descriptor_header * const *hs_streaming)
+{
+ struct uvc_device *uvc;
+ int ret = 0;
+
+ /* TODO Check if the USB device controller supports the required
+ * features.
+ */
+ if (!gadget_is_dualspeed(c->cdev->gadget))
+ return -EINVAL;
+
+ uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
+ if (uvc == NULL)
+ return -ENOMEM;
+
+ uvc->state = UVC_STATE_DISCONNECTED;
+
+ /* TODO Optimized structure-based kfifo implementation to avoid
+ * copies.
+ */
+ init_waitqueue_head(&uvc->event_wait);
+ spin_lock_init(&uvc->event_lock);
+ uvc->events = kfifo_alloc(sizeof(struct uvc_event) * UVC_MAX_EVENTS,
+ GFP_KERNEL, &uvc->event_lock);
+ if (uvc->events == NULL)
+ goto error;
+
+ /* Validate the descriptors. */
+ if (control == NULL || control[0] == NULL ||
+ control[0]->bDescriptorSubType != UVC_DT_HEADER)
+ goto error;
+
+ if (fs_streaming == NULL || fs_streaming[0] == NULL ||
+ fs_streaming[0]->bDescriptorSubType != UVC_DT_INPUT_HEADER)
+ goto error;
+
+ if (hs_streaming == NULL || hs_streaming[0] == NULL ||
+ hs_streaming[0]->bDescriptorSubType != UVC_DT_INPUT_HEADER)
+ goto error;
+
+ uvc->desc.control = control;
+ uvc->desc.fs_streaming = fs_streaming;
+ uvc->desc.hs_streaming = hs_streaming;
+
+ /* Allocate string descriptor numbers. */
+ if ((ret = usb_string_id(c->cdev)) < 0)
+ goto error;
+ uvc_en_us_strings[UVC_STRING_ASSOCIATION_IDX].id = ret;
+ uvc_iad.iFunction = ret;
+
+ if ((ret = usb_string_id(c->cdev)) < 0)
+ goto error;
+ uvc_en_us_strings[UVC_STRING_CONTROL_IDX].id = ret;
+ uvc_control_intf.iInterface = ret;
+
+ if ((ret = usb_string_id(c->cdev)) < 0)
+ goto error;
+ uvc_en_us_strings[UVC_STRING_STREAMING_IDX].id = ret;
+ uvc_streaming_intf_alt0.iInterface = ret;
+ uvc_streaming_intf_alt1.iInterface = ret;
+
+ /* Register the function. */
+ uvc->func.name = "uvc";
+ uvc->func.strings = uvc_function_strings;
+ uvc->func.bind = uvc_function_bind;
+ uvc->func.unbind = uvc_function_unbind;
+ uvc->func.set_alt = uvc_function_set_alt;
+ uvc->func.disable = uvc_function_disable;
+ uvc->func.setup = uvc_function_setup;
+
+ ret = usb_add_function(c, &uvc->func);
+ if (ret)
+ kfree(uvc);
+
+ return 0;
+
+error:
+ kfree(uvc);
+ return ret;
+}
+
+module_param_named(trace, uvc_trace_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(trace, "Trace level bitmask");
+
new file mode 100644
@@ -0,0 +1,376 @@
+/*
+ * f_uvc.h -- USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009
+ * 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.
+ *
+ */
+
+#ifndef _F_UVC_H_
+#define _F_UVC_H_
+
+#include <linux/usb/composite.h>
+
+#define USB_CLASS_VIDEO_CONTROL 1
+#define USB_CLASS_VIDEO_STREAMING 2
+
+struct uvc_descriptor_header {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+} __attribute__ ((packed));
+
+struct uvc_header_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u16 bcdUVC;
+ __u16 wTotalLength;
+ __u32 dwClockFrequency;
+ __u8 bInCollection;
+ __u8 baInterfaceNr[];
+} __attribute__((__packed__));
+
+#define UVC_HEADER_DESCRIPTOR(n) uvc_header_descriptor_##n
+
+#define DECLARE_UVC_HEADER_DESCRIPTOR(n) \
+struct UVC_HEADER_DESCRIPTOR(n) { \
+ __u8 bLength; \
+ __u8 bDescriptorType; \
+ __u8 bDescriptorSubType; \
+ __u16 bcdUVC; \
+ __u16 wTotalLength; \
+ __u32 dwClockFrequency; \
+ __u8 bInCollection; \
+ __u8 baInterfaceNr[n]; \
+} __attribute__ ((packed))
+
+struct uvc_input_terminal_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bTerminalID;
+ __u16 wTerminalType;
+ __u8 bAssocTerminal;
+ __u8 iTerminal;
+} __attribute__((__packed__));
+
+struct uvc_output_terminal_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bTerminalID;
+ __u16 wTerminalType;
+ __u8 bAssocTerminal;
+ __u8 bSourceID;
+ __u8 iTerminal;
+} __attribute__((__packed__));
+
+struct uvc_camera_terminal_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bTerminalID;
+ __u16 wTerminalType;
+ __u8 bAssocTerminal;
+ __u8 iTerminal;
+ __u16 wObjectiveFocalLengthMin;
+ __u16 wObjectiveFocalLengthMax;
+ __u16 wOcularFocalLength;
+ __u8 bControlSize;
+ __u8 bmControls[3];
+} __attribute__((__packed__));
+
+struct uvc_selector_unit_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bUnitID;
+ __u8 bNrInPins;
+ __u8 baSourceID[0];
+ __u8 iSelector;
+} __attribute__((__packed__));
+
+#define UVC_SELECTOR_UNIT_DESCRIPTOR(n) \
+ uvc_selector_unit_descriptor_##n
+
+#define DECLARE_UVC_SELECTOR_UNIT_DESCRIPTOR(n) \
+struct UVC_SELECTOR_UNIT_DESCRIPTOR(n) { \
+ __u8 bLength; \
+ __u8 bDescriptorType; \
+ __u8 bDescriptorSubType; \
+ __u8 bUnitID; \
+ __u8 bNrInPins; \
+ __u8 baSourceID[n]; \
+ __u8 iSelector; \
+} __attribute__ ((packed))
+
+struct uvc_processing_unit_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bUnitID;
+ __u8 bSourceID;
+ __u16 wMaxMultiplier;
+ __u8 bControlSize;
+ __u8 bmControls[2];
+ __u8 iProcessing;
+} __attribute__((__packed__));
+
+struct uvc_extension_unit_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bUnitID;
+ __u8 guidExtensionCode[16];
+ __u8 bNumControls;
+ __u8 bNrInPins;
+ __u8 baSourceID[0];
+ __u8 bControlSize;
+ __u8 bmControls[0];
+ __u8 iExtension;
+} __attribute__((__packed__));
+
+#define UVC_EXTENSION_UNIT_DESCRIPTOR(p, n) \
+ uvc_extension_unit_descriptor_##p_##n
+
+#define DECLARE_UVC_EXTENSION_UNIT_DESCRIPTOR(p, n) \
+struct UVC_EXTENSION_UNIT_DESCRIPTOR(p, n) { \
+ __u8 bLength; \
+ __u8 bDescriptorType; \
+ __u8 bDescriptorSubType; \
+ __u8 bUnitID; \
+ __u8 guidExtensionCode[16]; \
+ __u8 bNumControls; \
+ __u8 bNrInPins; \
+ __u8 baSourceID[p]; \
+ __u8 bControlSize; \
+ __u8 bmControls[n]; \
+ __u8 iExtension; \
+} __attribute__ ((packed))
+
+struct uvc_control_endpoint_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u16 wMaxTransferSize;
+} __attribute__((__packed__));
+
+#define UVC_DT_HEADER 1
+#define UVC_DT_INPUT_TERMINAL 2
+#define UVC_DT_OUTPUT_TERMINAL 3
+#define UVC_DT_SELECTOR_UNIT 4
+#define UVC_DT_PROCESSING_UNIT 5
+#define UVC_DT_EXTENSION_UNIT 6
+
+#define UVC_DT_HEADER_SIZE(n) (12+(n))
+#define UVC_DT_INPUT_TERMINAL_SIZE 8
+#define UVC_DT_OUTPUT_TERMINAL_SIZE 9
+#define UVC_DT_CAMERA_TERMINAL_SIZE(n) (15+(n))
+#define UVC_DT_SELECTOR_UNIT_SIZE(n) (6+(n))
+#define UVC_DT_PROCESSING_UNIT_SIZE(n) (9+(n))
+#define UVC_DT_EXTENSION_UNIT_SIZE(p,n) (24+(p)+(n))
+#define UVC_DT_CONTROL_ENDPOINT_SIZE 5
+
+struct uvc_input_header_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bNumFormats;
+ __u16 wTotalLength;
+ __u8 bEndpointAddress;
+ __u8 bmInfo;
+ __u8 bTerminalLink;
+ __u8 bStillCaptureMethod;
+ __u8 bTriggerSupport;
+ __u8 bTriggerUsage;
+ __u8 bControlSize;
+ __u8 bmaControls[];
+} __attribute__((__packed__));
+
+#define UVC_INPUT_HEADER_DESCRIPTOR(n, p) \
+ uvc_input_header_descriptor_##n_##p
+
+#define DECLARE_UVC_INPUT_HEADER_DESCRIPTOR(n, p) \
+struct UVC_INPUT_HEADER_DESCRIPTOR(n, p) { \
+ __u8 bLength; \
+ __u8 bDescriptorType; \
+ __u8 bDescriptorSubType; \
+ __u8 bNumFormats; \
+ __u16 wTotalLength; \
+ __u8 bEndpointAddress; \
+ __u8 bmInfo; \
+ __u8 bTerminalLink; \
+ __u8 bStillCaptureMethod; \
+ __u8 bTriggerSupport; \
+ __u8 bTriggerUsage; \
+ __u8 bControlSize; \
+ __u8 bmaControls[p][n]; \
+} __attribute__ ((packed))
+
+struct uvc_output_header_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bNumFormats;
+ __u16 wTotalLength;
+ __u8 bEndpointAddress;
+ __u8 bTerminalLink;
+ __u8 bControlSize;
+ __u8 bmaControls[];
+} __attribute__((__packed__));
+
+#define UVC_OUTPUT_HEADER_DESCRIPTOR(n, p) \
+ uvc_output_header_descriptor_##n_##p
+
+#define DECLARE_UVC_OUTPUT_HEADER_DESCRIPTOR(n, p) \
+struct UVC_OUTPUT_HEADER_DESCRIPTOR(n, p) { \
+ __u8 bLength; \
+ __u8 bDescriptorType; \
+ __u8 bDescriptorSubType; \
+ __u8 bNumFormats; \
+ __u16 wTotalLength; \
+ __u8 bEndpointAddress; \
+ __u8 bTerminalLink; \
+ __u8 bControlSize; \
+ __u8 bmaControls[p][n]; \
+} __attribute__ ((packed))
+
+struct uvc_format_uncompressed {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bFormatIndex;
+ __u8 bNumFrameDescriptors;
+ __u8 guidFormat[16];
+ __u8 bBitsPerPixel;
+ __u8 bDefaultFrameIndex;
+ __u8 bAspectRatioX;
+ __u8 bAspectRatioY;
+ __u8 bmInterfaceFlags;
+ __u8 bCopyProtect;
+} __attribute__((__packed__));
+
+struct uvc_frame_uncompressed {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bFrameIndex;
+ __u8 bmCapabilities;
+ __u16 wWidth;
+ __u16 wHeight;
+ __u32 dwMinBitRate;
+ __u32 dwMaxBitRate;
+ __u32 dwMaxVideoFrameBufferSize;
+ __u32 dwDefaultFrameInterval;
+ __u8 bFrameIntervalType;
+ __u32 dwFrameInterval[];
+} __attribute__((__packed__));
+
+#define UVC_FRAME_UNCOMPRESSED(n) \
+ uvc_frame_uncompressed_##n
+
+#define DECLARE_UVC_FRAME_UNCOMPRESSED(n) \
+struct UVC_FRAME_UNCOMPRESSED(n) { \
+ __u8 bLength; \
+ __u8 bDescriptorType; \
+ __u8 bDescriptorSubType; \
+ __u8 bFrameIndex; \
+ __u8 bmCapabilities; \
+ __u16 wWidth; \
+ __u16 wHeight; \
+ __u32 dwMinBitRate; \
+ __u32 dwMaxBitRate; \
+ __u32 dwMaxVideoFrameBufferSize; \
+ __u32 dwDefaultFrameInterval; \
+ __u8 bFrameIntervalType; \
+ __u32 dwFrameInterval[n]; \
+} __attribute__ ((packed))
+
+struct uvc_format_mjpeg {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bFormatIndex;
+ __u8 bNumFrameDescriptors;
+ __u8 bmFlags;
+ __u8 bDefaultFrameIndex;
+ __u8 bAspectRatioX;
+ __u8 bAspectRatioY;
+ __u8 bmInterfaceFlags;
+ __u8 bCopyProtect;
+} __attribute__((__packed__));
+
+struct uvc_frame_mjpeg {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bFrameIndex;
+ __u8 bmCapabilities;
+ __u16 wWidth;
+ __u16 wHeight;
+ __u32 dwMinBitRate;
+ __u32 dwMaxBitRate;
+ __u32 dwMaxVideoFrameBufferSize;
+ __u32 dwDefaultFrameInterval;
+ __u8 bFrameIntervalType;
+ __u32 dwFrameInterval[];
+} __attribute__((__packed__));
+
+#define UVC_FRAME_MJPEG(n) \
+ uvc_frame_mjpeg_##n
+
+#define DECLARE_UVC_FRAME_MJPEG(n) \
+struct UVC_FRAME_MJPEG(n) { \
+ __u8 bLength; \
+ __u8 bDescriptorType; \
+ __u8 bDescriptorSubType; \
+ __u8 bFrameIndex; \
+ __u8 bmCapabilities; \
+ __u16 wWidth; \
+ __u16 wHeight; \
+ __u32 dwMinBitRate; \
+ __u32 dwMaxBitRate; \
+ __u32 dwMaxVideoFrameBufferSize; \
+ __u32 dwDefaultFrameInterval; \
+ __u8 bFrameIntervalType; \
+ __u32 dwFrameInterval[n]; \
+} __attribute__ ((packed))
+
+struct uvc_color_matching_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubType;
+ __u8 bColorPrimaries;
+ __u8 bTransferCharacteristics;
+ __u8 bMatrixCoefficients;
+} __attribute__((__packed__));
+
+#define UVC_DT_INPUT_HEADER 1
+#define UVC_DT_OUTPUT_HEADER 2
+#define UVC_DT_FORMAT_UNCOMPRESSED 4
+#define UVC_DT_FRAME_UNCOMPRESSED 5
+#define UVC_DT_FORMAT_MJPEG 6
+#define UVC_DT_FRAME_MJPEG 7
+#define UVC_DT_COLOR_MATCHING 13
+
+#define UVC_DT_INPUT_HEADER_SIZE(n, p) (13+(n*p))
+#define UVC_DT_OUTPUT_HEADER_SIZE(n, p) (9+(n*p))
+#define UVC_DT_FORMAT_UNCOMPRESSED_SIZE 27
+#define UVC_DT_FRAME_UNCOMPRESSED_SIZE(n) (26+4*(n))
+#define UVC_DT_FORMAT_MJPEG_SIZE 11
+#define UVC_DT_FRAME_MJPEG_SIZE(n) (26+4*(n))
+#define UVC_DT_COLOR_MATCHING_SIZE 6
+
+extern int uvc_bind_config(struct usb_configuration *c,
+ const struct uvc_descriptor_header * const *control,
+ const struct uvc_descriptor_header * const *fs_streaming,
+ const struct uvc_descriptor_header * const *hs_streaming);
+
+#endif /* _F_UVC_H_ */
+
new file mode 100644
@@ -0,0 +1,246 @@
+/*
+ * uvc_gadget.h -- USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009
+ * 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.
+ *
+ */
+
+#ifndef _UVC_GADGET_H_
+#define _UVC_GADGET_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+#include <linux/usb/ch9.h>
+
+enum uvc_event_type
+{
+ UVC_EVENT_CONNECT,
+ UVC_EVENT_DISCONNECT,
+ UVC_EVENT_STREAMON,
+ UVC_EVENT_STREAMOFF,
+ UVC_EVENT_SETUP,
+ UVC_EVENT_DATA,
+};
+
+struct uvc_event_data
+{
+ int length;
+ __u8 data[64];
+};
+
+struct uvc_event
+{
+ enum uvc_event_type type;
+ union {
+ enum usb_device_speed speed;
+ struct usb_ctrlrequest req;
+ struct uvc_event_data data;
+ };
+};
+
+#define UVCIOC_EVENT_READ _IOR('U', 1, struct uvc_event)
+#define UVCIOC_EVENT_WRITE _IOW('U', 2, struct uvc_event_data)
+
+#define UVC_INTF_CONTROL 0
+#define UVC_INTF_STREAMING 1
+
+/* ------------------------------------------------------------------------
+ * UVC constants & structures
+ */
+
+/* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */
+#define UVC_STREAM_EOH (1 << 7)
+#define UVC_STREAM_ERR (1 << 6)
+#define UVC_STREAM_STI (1 << 5)
+#define UVC_STREAM_RES (1 << 4)
+#define UVC_STREAM_SCR (1 << 3)
+#define UVC_STREAM_PTS (1 << 2)
+#define UVC_STREAM_EOF (1 << 1)
+#define UVC_STREAM_FID (1 << 0)
+
+struct uvc_streaming_control {
+ __u16 bmHint;
+ __u8 bFormatIndex;
+ __u8 bFrameIndex;
+ __u32 dwFrameInterval;
+ __u16 wKeyFrameRate;
+ __u16 wPFrameRate;
+ __u16 wCompQuality;
+ __u16 wCompWindowSize;
+ __u16 wDelay;
+ __u32 dwMaxVideoFrameSize;
+ __u32 dwMaxPayloadTransferSize;
+ __u32 dwClockFrequency;
+ __u8 bmFramingInfo;
+ __u8 bPreferedVersion;
+ __u8 bMinVersion;
+ __u8 bMaxVersion;
+} __attribute__((__packed__));
+
+/* ------------------------------------------------------------------------
+ * Debugging, printing and logging
+ */
+
+#ifdef __KERNEL__
+
+#include <linux/kfifo.h>
+#include <linux/usb.h> /* For usb_endpoint_* */
+#include <linux/usb/gadget.h>
+
+#include "uvc_queue.h"
+
+#define UVC_TRACE_PROBE (1 << 0)
+#define UVC_TRACE_DESCR (1 << 1)
+#define UVC_TRACE_CONTROL (1 << 2)
+#define UVC_TRACE_FORMAT (1 << 3)
+#define UVC_TRACE_CAPTURE (1 << 4)
+#define UVC_TRACE_CALLS (1 << 5)
+#define UVC_TRACE_IOCTL (1 << 6)
+#define UVC_TRACE_FRAME (1 << 7)
+#define UVC_TRACE_SUSPEND (1 << 8)
+#define UVC_TRACE_STATUS (1 << 9)
+
+#define UVC_WARN_MINMAX 0
+#define UVC_WARN_PROBE_DEF 1
+
+extern unsigned int uvc_trace_param;
+
+#define uvc_trace(flag, msg...) \
+ do { \
+ if (uvc_trace_param & flag) \
+ printk(KERN_DEBUG "uvcvideo: " msg); \
+ } while (0)
+
+#define uvc_warn_once(dev, warn, msg...) \
+ do { \
+ if (!test_and_set_bit(warn, &dev->warnings)) \
+ printk(KERN_INFO "uvcvideo: " msg); \
+ } while (0)
+
+#define uvc_printk(level, msg...) \
+ printk(level "uvcvideo: " msg)
+
+/* ------------------------------------------------------------------------
+ * Driver specific constants
+ */
+
+#define DRIVER_VERSION "0.1.0"
+#define DRIVER_VERSION_NUMBER KERNEL_VERSION(0, 1, 0)
+
+#define DMA_ADDR_INVALID (~(dma_addr_t)0)
+
+#define UVC_NUM_REQUESTS 4
+#define UVC_MAX_REQUEST_SIZE 64
+#define UVC_MAX_EVENTS 4
+
+#define USB_DT_INTERFACE_ASSOCIATION_SIZE 8
+#define USB_CLASS_MISC 0xef
+
+/* ------------------------------------------------------------------------
+ * Structures
+ */
+
+struct uvc_video
+{
+ struct usb_ep *ep;
+
+ /* Frame parameters */
+ u8 bpp;
+ u32 fcc;
+ unsigned int width;
+ unsigned int height;
+ unsigned int imagesize;
+
+ /* Requests */
+ unsigned int req_size;
+ struct usb_request *req[UVC_NUM_REQUESTS];
+ __u8 *req_buffer[UVC_NUM_REQUESTS];
+ struct list_head req_free;
+ spinlock_t req_lock;
+
+ void (*encode) (struct usb_request *req, struct uvc_video *video,
+ struct uvc_buffer *buf);
+
+ /* Context data used by the completion handler */
+ __u32 payload_size;
+ __u32 max_payload_size;
+
+ struct uvc_video_queue queue;
+ unsigned int fid;
+};
+
+enum uvc_state
+{
+ UVC_STATE_DISCONNECTED,
+ UVC_STATE_CONNECTED,
+ UVC_STATE_STREAMING,
+};
+
+struct uvc_device
+{
+ struct video_device *vdev;
+ enum uvc_state state;
+ struct usb_function func;
+ struct uvc_video video;
+
+ /* Descriptors */
+ struct {
+ const struct uvc_descriptor_header * const *control;
+ const struct uvc_descriptor_header * const *fs_streaming;
+ const struct uvc_descriptor_header * const *hs_streaming;
+ } desc;
+
+ unsigned int control_intf;
+ struct usb_ep *control_ep;
+ struct usb_request *control_req;
+ void *control_buf;
+
+ unsigned int streaming_intf;
+
+ /* Events queue */
+ wait_queue_head_t event_wait;
+ spinlock_t event_lock;
+ struct kfifo *events;
+ unsigned int event_length;
+ unsigned int event_setup_out : 1;
+};
+
+static inline struct uvc_device *to_uvc(struct usb_function *f)
+{
+ return container_of(f, struct uvc_device, func);
+}
+
+struct uvc_file_handle
+{
+ struct uvc_video *device;
+};
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
+extern struct file_operations uvc_v4l2_fops;
+#else
+extern struct v4l2_file_operations uvc_v4l2_fops;
+#endif
+
+/* ------------------------------------------------------------------------
+ * Functions
+ */
+
+extern int uvc_video_enable(struct uvc_video *video, int enable);
+extern int uvc_video_init(struct uvc_video *video);
+extern int uvc_video_pump(struct uvc_video *video);
+
+extern void uvc_endpoint_stream(struct uvc_device *dev);
+
+extern void uvc_function_connect(struct uvc_device *uvc);
+extern void uvc_function_disconnect(struct uvc_device *uvc);
+
+#endif /* __KERNEL__ */
+
+#endif /* _UVC_GADGET_H_ */
+
new file mode 100644
@@ -0,0 +1,890 @@
+/*
+ * uvc_gadget.c -- USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009
+ * 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/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/usb_ch9.h>
+#include <linux/usb_gadget.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+
+#include <media/v4l2-dev.h>
+
+#include "uvc.h"
+
+#define DRIVER_AUTHOR "Laurent Pinchart"
+#define DRIVER_DESCRIPTION "USB Video Class Gadget"
+
+unsigned int uvc_trace_param;
+
+/* --------------------------------------------------------------------------
+ * Streaming endpoint operations
+ */
+
+static int
+uvc_endpoint_set_interface(struct uvc_device *dev, u16 altsetting)
+{
+ if (usb_endpoint_xfer_bulk(&dev->desc.vs_ep))
+ return altsetting ? -EINVAL : 0;
+
+ switch (altsetting) {
+ case 0:
+ if (dev->video.ep)
+ usb_ep_disable(dev->video.ep);
+
+ dev->event.type = UVC_EVENT_STREAMOFF;
+ dev->event_ready = 1;
+ wake_up(&dev->event_wait);
+ break;
+
+ case 1:
+ if (dev->video.ep)
+ usb_ep_enable(dev->video.ep, &dev->desc.vs_ep);
+
+ dev->event.type = UVC_EVENT_STREAMON;
+ dev->event_ready = 1;
+ wake_up(&dev->event_wait);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+uvc_endpoint_set_configuration(struct uvc_device *dev, u16 config)
+{
+ switch (config) {
+ case 0:
+ dev->power = 8;
+ break;
+
+ case 1:
+ dev->power = dev->desc.config.bMaxPower * 2;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+uvc_endpoint_init(struct uvc_device *dev)
+{
+ /* Find a matching endpoint. */
+ usb_ep_autoconfig_reset(dev->gadget);
+ dev->video.ep = usb_ep_autoconfig(dev->gadget, &dev->desc.vs_ep);
+ if (dev->video.ep == NULL)
+ return -EINVAL;
+
+ dev->desc.vs_ep.wMaxPacketSize = dev->video.ep->maxpacket;
+ dev->video.ep->driver_data = dev;
+
+ /* TODO Make max_payload_size configurable. */
+ if (usb_endpoint_xfer_isoc(&dev->desc.vs_ep)) {
+ printk(KERN_INFO "g_uvc: Isochronous mode selected\n");
+ dev->video.max_payload_size = 0;
+ } else {
+ printk(KERN_INFO "g_uvc: Bulk mode selected\n");
+ dev->video.max_payload_size = 16 * 1024;
+ usb_ep_enable(dev->video.ep, &dev->desc.vs_ep);
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Descriptor operations
+ */
+
+#define UVC_INTF_VIDEO_CONTROL 0
+#define UVC_INTF_VIDEO_STREAMING 1
+
+static const struct usb_device_descriptor uvc_device_descriptor = {
+ .bLength = USB_DT_DEVICE_SIZE,
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_MISC,
+ .bDeviceSubClass = 0x02,
+ .bDeviceProtocol = 0x01,
+ .bMaxPacketSize0 = 0,
+ .idVendor = 0,
+ .idProduct = 0,
+ .bcdDevice = 0,
+ .iManufacturer = 0,
+ .iProduct = 0,
+ .iSerialNumber = 0,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb_config_descriptor uvc_config_descriptor = {
+ .bLength = USB_DT_CONFIG_SIZE,
+ .bDescriptorType = USB_DT_CONFIG,
+ .wTotalLength = 0,
+ .bNumInterfaces = 2,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = USB_CONFIG_ATT_ONE,
+ .bMaxPower = 100,
+};
+
+static const struct usb_interface_assoc_descriptor uvc_iad = {
+ .bLength = USB_DT_INTERFACE_ASSOCIATION_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+ .bFirstInterface = 0,
+ .bInterfaceCount = 2,
+ .bFunctionClass = USB_CLASS_VIDEO,
+ .bFunctionSubClass = 0x03,
+ .bFunctionProtocol = 0x00,
+ .iFunction = 0,
+};
+
+static const struct usb_interface_descriptor uvc_video_control_descriptor = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = UVC_INTF_VIDEO_CONTROL,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 0x01,
+ .bInterfaceProtocol = 0x00,
+ .iInterface = 0,
+};
+
+#define UVC_DT_CS_ENDPOINT_SIZE 5
+
+static const struct uvc_interrupt_endpoint_descriptor uvc_interrupt_descriptor = {
+ .bLength = UVC_DT_CS_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_CS_ENDPOINT,
+ .bDescriptorSubType = EP_INTERRUPT,
+ .wMaxTransferSize = 0x0010,
+};
+
+static const struct usb_interface_descriptor uvc_video_streaming_descriptor = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = UVC_INTF_VIDEO_STREAMING,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 0x02,
+ .bInterfaceProtocol = 0x00,
+ .iInterface = 0,
+};
+
+static int
+uvc_make_qualifier(struct uvc_device *dev)
+{
+ struct usb_qualifier_descriptor qual;
+
+ qual.bLength = sizeof(qual);
+ qual.bDescriptorType = USB_DT_DEVICE_QUALIFIER;
+ qual.bcdUSB = cpu_to_le16(0x0200);
+ qual.bRESERVED = 0;
+
+ qual.bDeviceClass = dev->desc.device.bDeviceClass;
+ qual.bDeviceSubClass = dev->desc.device.bDeviceSubClass;
+ qual.bDeviceProtocol = dev->desc.device.bDeviceProtocol;
+ qual.bMaxPacketSize0 = dev->desc.device.bMaxPacketSize0;
+ qual.bNumConfigurations = dev->desc.device.bNumConfigurations;
+
+ memcpy(dev->ep0buf, &qual, sizeof(qual));
+ return sizeof(qual);
+}
+
+#define UVC_APPEND_DESCRIPTOR_RAW(buf, desc, size) \
+ do { \
+ memcpy(buf, desc, size); \
+ buf += size; \
+ } while (0)
+
+#define UVC_APPEND_DESCRIPTOR(buf, desc) \
+ UVC_APPEND_DESCRIPTOR_RAW(buf, desc, sizeof(typeof(*desc)))
+
+static int
+uvc_make_config(struct uvc_device *dev, u8 type)
+{
+ struct usb_config_descriptor *config;
+ struct usb_interface_descriptor *intf;
+ struct usb_endpoint_descriptor *endp;
+ void *buf = dev->ep0buf;
+ int len;
+
+ UVC_APPEND_DESCRIPTOR(buf, &dev->desc.config);
+ UVC_APPEND_DESCRIPTOR(buf, &uvc_iad);
+
+ /* Video control. */
+ intf = buf;
+ UVC_APPEND_DESCRIPTOR(buf, &uvc_video_control_descriptor);
+ UVC_APPEND_DESCRIPTOR_RAW(buf, dev->desc.vc_data, dev->desc.vc_size);
+
+ if (dev->desc.vc_ep.bInterval) {
+ endp = buf;
+ UVC_APPEND_DESCRIPTOR_RAW(buf, &dev->desc.vc_ep, USB_DT_ENDPOINT_SIZE);
+ endp->wMaxPacketSize = 16;
+ UVC_APPEND_DESCRIPTOR(buf, &uvc_interrupt_descriptor);
+
+ intf->bNumEndpoints = 1;
+ }
+
+ /* Video streaming. */
+ intf = buf;
+ UVC_APPEND_DESCRIPTOR(buf, &uvc_video_streaming_descriptor);
+ UVC_APPEND_DESCRIPTOR_RAW(buf, dev->desc.vs_data, dev->desc.vs_size);
+
+ if (usb_endpoint_xfer_isoc(&dev->desc.vs_ep)) {
+ intf = buf;
+ UVC_APPEND_DESCRIPTOR(buf, &uvc_video_streaming_descriptor);
+ intf->bAlternateSetting = 1;
+ }
+
+ intf->bNumEndpoints = 1;
+ UVC_APPEND_DESCRIPTOR_RAW(buf, &dev->desc.vs_ep, USB_DT_ENDPOINT_SIZE);
+
+ len = buf - (void *)dev->ep0buf;
+ config = (struct usb_config_descriptor *)dev->ep0buf;
+ config->wTotalLength = cpu_to_le16(len);
+
+ return len;
+}
+
+static ssize_t
+uvc_config_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_gadget *gadget = container_of(dev, struct usb_gadget, dev);
+ struct uvc_device *udev = get_gadget_data(gadget);
+ struct usb_string *string;
+ char *p = buf;
+
+ for (string = udev->desc.strings.strings; string->id; ++string)
+ p += sprintf(p, "%u %s\n", string->id, string->s);
+
+ return p - buf;
+}
+
+static ssize_t
+uvc_config_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_gadget *gadget = container_of(dev, struct usb_gadget, dev);
+ struct uvc_device *udev = get_gadget_data(gadget);
+ const struct usb_descriptor_header *usb_header;
+ const struct usb_device_descriptor *usb_dd;
+ const struct usb_config_descriptor *usb_cd;
+ const struct usb_endpoint_descriptor *usb_ep;
+ const char *end = buf + count;
+ const char *data, *p, *s;
+ unsigned int nstrings;
+ unsigned int len, i;
+ int ret = -EINVAL;
+
+ if (udev->state != UVC_STATE_DISABLED)
+ return -EEXIST;
+
+ udev->desc.vc_ep.bLength = USB_DT_ENDPOINT_SIZE;
+ udev->desc.vc_ep.bDescriptorType = USB_DT_ENDPOINT;
+ udev->desc.vc_ep.bEndpointAddress = USB_DIR_IN | 0x03;
+ udev->desc.vc_ep.bmAttributes = USB_ENDPOINT_XFER_INT;
+ udev->desc.vc_ep.wMaxPacketSize = 0;
+ udev->desc.vc_ep.bInterval = 0;
+
+ udev->desc.vs_ep.bLength = USB_DT_ENDPOINT_SIZE;
+ udev->desc.vs_ep.bDescriptorType = USB_DT_ENDPOINT;
+ udev->desc.vs_ep.bEndpointAddress = USB_DIR_IN;
+ udev->desc.vs_ep.bmAttributes = 0;
+ udev->desc.vs_ep.wMaxPacketSize = 0;
+ udev->desc.vs_ep.bInterval = 1;
+
+ /*
+ * Offset | Size | Description
+ * ----------+------+------------------------------------------------
+ * 0 | 4 | Number of string descriptors (n)
+ * 4 | p(1) | Zero-terminated string 1
+ * 4+p(1) | p(2) | Zero-terminated string 2
+ * ... | |
+ * 4+p(1) | p(n) | Zero-terminated string n
+ * +... | |
+ * +p(n-1) | |
+ * 4+p | 18 | Device descriptor
+ * 22+p | 9 | Configuration descriptor
+ * 31+p | 8 | Interface association descriptor
+ * 39+p | 9 | Video control interface descriptor
+ * 48+p | m | Class-specific video control descriptors
+ * 48+p+m | 9 | Video streaming interface descriptor
+ * 57+p+m | q | Class-specific video streaming descriptors
+ *
+ * - Device: idVendor, idProduct, bcdDevice, iManufacturer, iProduct,
+ * iSerial
+ * - Configuration: iConfiguration, bmAttributes, bMaxPower
+ * - Interface association: none
+ * - Video control interface: none
+ * - Video streaming interface: none
+ */
+
+ /* Parse string data and create string descriptors. The first pass
+ * computes the total string data length to allocate memory in one
+ * chunk and the second pass creates and fills the string descriptors.
+ */
+ nstrings = *(u32 *)buf;
+ buf += 4;
+
+ for (p = buf, i = nstrings; p < end && i > 0; ++p) {
+ if (*p == '\0')
+ i--;
+ }
+
+ if (i > 0)
+ return -EINVAL;
+
+ len = (nstrings + 1) * sizeof(*udev->desc.string_data);
+ len += p - buf + 1;
+
+ udev->desc.string_data = kzalloc(len, GFP_KERNEL);
+ if (udev->desc.string_data == NULL)
+ return -ENOMEM;
+
+ memcpy(&udev->desc.string_data[nstrings+1], buf, len);
+ s = (char *)&udev->desc.string_data[nstrings+1];
+
+ for (i = 0; i < nstrings; ++i) {
+ udev->desc.string_data[i].id = i + 1;
+ udev->desc.string_data[i].s = s;
+ s += strlen(s) + 1;
+ }
+
+ udev->desc.strings.language = 0x0409;
+ udev->desc.strings.strings = udev->desc.string_data;
+
+ /* Fill device and configuration descriptors. */
+ if (end - p < USB_DT_DEVICE_SIZE + USB_DT_CONFIG_SIZE)
+ goto error;
+
+ usb_dd = (const struct usb_device_descriptor *)p;
+ if (usb_dd->bDescriptorType != USB_DT_DEVICE)
+ goto error;
+
+ memcpy(&udev->desc.device, &uvc_device_descriptor,
+ sizeof(udev->desc.device));
+ udev->desc.device.bMaxPacketSize0 = gadget->ep0->maxpacket;
+ udev->desc.device.idVendor = usb_dd->idVendor;
+ udev->desc.device.idProduct = usb_dd->idProduct;
+ udev->desc.device.bcdDevice = usb_dd->bcdDevice;
+ udev->desc.device.iManufacturer = usb_dd->iManufacturer;
+ udev->desc.device.iProduct = usb_dd->iProduct;
+ udev->desc.device.iSerialNumber = usb_dd->iSerialNumber;
+ p += USB_DT_DEVICE_SIZE;
+
+ usb_cd = (const struct usb_config_descriptor *)p;
+ if (usb_cd->bDescriptorType != USB_DT_CONFIG)
+ goto error;
+
+ memcpy(&udev->desc.config, &uvc_config_descriptor,
+ sizeof(udev->desc.config));
+ udev->desc.config.iConfiguration = usb_cd->iConfiguration;
+ udev->desc.config.bmAttributes = usb_cd->bmAttributes | USB_CONFIG_ATT_ONE;
+ udev->desc.config.bMaxPower = usb_cd->bMaxPower;
+ p += USB_DT_CONFIG_SIZE;
+
+ /* Video interface association descriptor. */
+ if (end - p < USB_DT_INTERFACE_ASSOCIATION_SIZE)
+ goto error;
+
+ usb_header = (const struct usb_descriptor_header *)p;
+ if (usb_header->bDescriptorType != USB_DT_INTERFACE_ASSOCIATION)
+ goto error;
+ p += USB_DT_INTERFACE_ASSOCIATION_SIZE;
+
+ /* Video control interface descriptors. */
+ if (end - p < USB_DT_INTERFACE_SIZE)
+ goto error;
+
+ usb_header = (const struct usb_descriptor_header *)p;
+ if (usb_header->bDescriptorType != USB_DT_INTERFACE)
+ goto error;
+ p += USB_DT_INTERFACE_SIZE;
+
+ for (data = p; p + 3 <= end && p[1] == USB_DT_CS_INTERFACE; p += p[0]) {
+ switch (p[2]) {
+ case VC_HEADER:
+ break;
+ }
+ }
+
+ if (p > end)
+ goto error;
+
+ udev->desc.vc_size = p - data;
+ udev->desc.vc_data = kmalloc(udev->desc.vc_size, GFP_KERNEL);
+ if (udev->desc.vc_data == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ memcpy(udev->desc.vc_data, data, udev->desc.vc_size);
+
+ /* Optional interrupt endpoint descriptor. */
+ if (end - p < sizeof(usb_header))
+ goto error;
+
+ usb_header = (const struct usb_descriptor_header *)p;
+ if (usb_header->bDescriptorType == USB_DT_ENDPOINT) {
+ if (end - p < USB_DT_ENDPOINT_SIZE + UVC_DT_CS_ENDPOINT_SIZE)
+ goto error;
+
+ usb_ep = (const struct usb_endpoint_descriptor *)p;
+ udev->desc.vc_ep.bInterval = usb_ep->bInterval;
+
+ p += USB_DT_ENDPOINT_SIZE + UVC_DT_CS_ENDPOINT_SIZE;
+ }
+
+ /* Video streaming interface descriptors. */
+ if (end - p < USB_DT_INTERFACE_SIZE)
+ goto error;
+
+ usb_header = (const struct usb_descriptor_header *)p;
+ if (usb_header->bDescriptorType != USB_DT_INTERFACE)
+ goto error;
+ p += USB_DT_INTERFACE_SIZE;
+
+ for (data = p; p + 3 <= end && p[1] == USB_DT_CS_INTERFACE; p += p[0]) {
+ switch (p[2]) {
+ case VS_INPUT_HEADER:
+ break;
+ }
+ }
+
+ if (p > end)
+ goto error;
+
+ udev->desc.vs_size = p - data;
+ udev->desc.vs_data = kmalloc(udev->desc.vs_size, GFP_KERNEL);
+ if (udev->desc.vs_data == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ memcpy(udev->desc.vs_data, data, udev->desc.vs_size);
+
+ /* Video streaming bulk endpoint. */
+ usb_header = (const struct usb_descriptor_header *)p;
+ if (end - p >= USB_DT_ENDPOINT_SIZE &&
+ usb_header->bDescriptorType == USB_DT_ENDPOINT) {
+ usb_ep = (const struct usb_endpoint_descriptor *)p;
+ if (usb_ep->bmAttributes != USB_ENDPOINT_XFER_BULK)
+ goto error;
+
+ udev->desc.vs_ep.bmAttributes = usb_ep->bmAttributes;
+ p += USB_DT_ENDPOINT_SIZE;
+
+ if (p != end)
+ goto error;
+ }
+
+ /* Video streaming alternate settings and isochronous endpoints. */
+ while (end - p >= USB_DT_INTERFACE_SIZE + USB_DT_ENDPOINT_SIZE) {
+ usb_header = (const struct usb_descriptor_header *)p;
+ if (usb_header->bDescriptorType != USB_DT_INTERFACE)
+ goto error;
+ p += USB_DT_INTERFACE_SIZE;
+
+ usb_header = (const struct usb_descriptor_header *)p;
+ if (usb_header->bDescriptorType != USB_DT_ENDPOINT)
+ goto error;
+
+ usb_ep = (const struct usb_endpoint_descriptor *)p;
+ if (usb_ep->bmAttributes != (0X04 | USB_ENDPOINT_XFER_ISOC))
+ goto error;
+
+ udev->desc.vs_ep.bmAttributes = usb_ep->bmAttributes;
+ p += USB_DT_ENDPOINT_SIZE;
+ }
+
+ if (p != end)
+ goto error;
+
+ /* Select endpoints. */
+ ret = uvc_endpoint_init(udev);
+ if (ret < 0)
+ goto error;
+
+ udev->state = UVC_STATE_UNCONNECTED;
+ usb_gadget_connect(gadget);
+ return count;
+
+error:
+ kfree(udev->desc.string_data);
+ udev->desc.string_data = NULL;
+ kfree(udev->desc.vc_data);
+ udev->desc.vc_data = NULL;
+ kfree(udev->desc.vs_data);
+ udev->desc.vs_data = NULL;
+
+ return ret;
+}
+
+static DEVICE_ATTR(config, 0644, uvc_config_show, uvc_config_store);
+
+/* --------------------------------------------------------------------------
+ * Endpoint 0
+ */
+
+static void
+uvc_ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct uvc_device *dev = req->context;
+
+ if (dev->event_setup_out) {
+ dev->event_setup_out = 0;
+
+ dev->event.type = UVC_EVENT_DATA;
+ dev->event.data.length = req->actual;
+ memcpy(&dev->event.data.data, &req->buf, req->actual);
+
+ dev->event_ready = 1;
+ wake_up(&dev->event_wait);
+ }
+}
+
+#define DeviceInRequest \
+ ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8)
+#define DeviceOutRequest \
+ ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8)
+#define InterfaceInRequest \
+ ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8)
+#define InterfaceOutRequest \
+ ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8)
+
+static int
+uvc_setup_standard(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct uvc_device *dev = get_gadget_data(gadget);
+ u16 wIndex = le16_to_cpu(ctrl->wIndex);
+ u16 wValue = le16_to_cpu(ctrl->wValue);
+ int value = -EOPNOTSUPP;
+
+ switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+ case DeviceInRequest | USB_REQ_GET_DESCRIPTOR:
+ switch (wValue >> 8) {
+ case USB_DT_DEVICE:
+ value = sizeof(dev->desc.device);
+ dev->ep0req->buf = &dev->desc.device;
+ break;
+
+ case USB_DT_DEVICE_QUALIFIER:
+ value = uvc_make_qualifier(dev);
+ break;
+
+ case USB_DT_CONFIG:
+ case USB_DT_OTHER_SPEED_CONFIG:
+ value = uvc_make_config(dev, wValue >> 8);
+ break;
+
+ case USB_DT_STRING:
+ value = usb_gadget_get_string(&dev->desc.strings,
+ wValue & 0xff, dev->ep0buf);
+ break;
+
+ case USB_DT_DEBUG:
+ default:
+ printk(KERN_INFO "Unsupported descriptor type %u\n",
+ wIndex >> 8);
+ break;
+ }
+ break;
+
+ case DeviceInRequest | USB_REQ_GET_CONFIGURATION:
+ *(u8 *)dev->ep0buf = dev->config;
+ value = 1;
+ break;
+
+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+ if (uvc_endpoint_set_configuration(dev, wValue) == 0) {
+ value = 0;
+ dev->config = wValue;
+ usb_gadget_vbus_draw(gadget, dev->power);
+ }
+ break;
+
+ case InterfaceInRequest | USB_REQ_GET_INTERFACE:
+ switch (wIndex) {
+ case UVC_INTF_VIDEO_CONTROL:
+ /* Video control interface has a single alternate
+ * setting.
+ */
+ *(u8 *)dev->ep0buf = 0;
+ value = 1;
+ break;
+
+ case UVC_INTF_VIDEO_STREAMING:
+ *(u8 *)dev->ep0buf = dev->altsetting;
+ value = 1;
+ break;
+ }
+ break;
+
+ case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+ if (wIndex != UVC_INTF_VIDEO_STREAMING)
+ break;
+
+ if (uvc_endpoint_set_interface(dev, wValue) == 0) {
+ value = 0;
+ dev->altsetting = wValue;
+ }
+ break;
+
+ default:
+ printk(KERN_INFO "Unsupported request bRequestType 0x%02x "
+ "bRequest 0x%02x wValue 0x%04x wIndex 0x%04x\n",
+ ctrl->bRequestType, ctrl->bRequest, wValue, wIndex);
+ break;
+ }
+
+ return value;
+}
+
+static int
+uvc_setup_class(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct uvc_device *dev = get_gadget_data(gadget);
+
+ /* printk(KERN_INFO "setup request %02x %02x value %04x index %04x %04x\n",
+ * ctrl->bRequestType, ctrl->bRequest, le16_to_cpu(ctrl->wValue),
+ * le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength));
+ */
+
+ /* TODO stall too big requests */
+
+ memcpy(&dev->event.req, ctrl, sizeof(dev->event.req));
+ dev->event.type = UVC_EVENT_SETUP;
+ dev->event_ready = 1;
+ wake_up(&dev->event_wait);
+
+ return -EAGAIN;
+}
+
+static int
+uvc_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
+{
+ struct uvc_device *dev = get_gadget_data(gadget);
+ u16 wLength = le16_to_cpu(ctrl->wLength);
+ int value = -EOPNOTSUPP;
+
+ dev->state = UVC_STATE_CONNECTED;
+
+ dev->ep0req->buf = &dev->ep0buf;
+
+ switch (ctrl->bRequestType & USB_TYPE_MASK) {
+ case USB_TYPE_STANDARD:
+ value = uvc_setup_standard(gadget, ctrl);
+ break;
+
+ case USB_TYPE_CLASS:
+ value = uvc_setup_class(gadget, ctrl);
+ break;
+
+ default:
+ printk(KERN_INFO "Unsupported request type 0x%02x\n",
+ ctrl->bRequestType);
+ break;
+ }
+
+ if (value >= 0) {
+ dev->ep0req->length = min((int)wLength, value);
+ dev->ep0req->zero = value < wLength;
+ dev->ep0req->dma = DMA_ADDR_INVALID;
+
+ value = usb_ep_queue(gadget->ep0, dev->ep0req, GFP_ATOMIC);
+ }
+
+ return (value == -EAGAIN) ? 0 : value;
+}
+
+/* --------------------------------------------------------------------------
+ * USB probe and disconnect
+ */
+
+static int
+uvc_register_video(struct uvc_device *dev)
+{
+ struct video_device *video;
+
+ /* TODO reference counting. */
+ video = video_device_alloc();
+ if (video == NULL)
+ return -ENOMEM;
+
+ video->dev = &dev->gadget->dev;
+ video->minor = -1;
+ video->fops = &uvc_v4l2_fops;
+ video->release = video_device_release;
+ strncpy(video->name, dev->gadget->name, sizeof(video->name));
+
+ dev->vdev = video;
+ video_set_drvdata(video, dev);
+
+ return video_register_device(video, VFL_TYPE_GRABBER, -1);
+}
+
+static void
+uvc_disconnect(struct usb_gadget *gadget)
+{
+ struct uvc_device *dev = get_gadget_data(gadget);
+
+ printk(KERN_DEBUG "uvc_disconnect: gadget %s\n", gadget->name);
+ if (dev->state != UVC_STATE_DISABLED)
+ dev->state = UVC_STATE_UNCONNECTED;
+}
+
+static void
+uvc_unbind(struct usb_gadget *gadget)
+{
+ struct uvc_device *dev = get_gadget_data(gadget);
+
+ printk(KERN_DEBUG "uvc_unbind: gadget %s\n", gadget->name);
+
+ if (dev->vdev) {
+ if (dev->vdev->minor == -1)
+ video_device_release(dev->vdev);
+ else
+ video_unregister_device(dev->vdev);
+ dev->vdev = NULL;
+ }
+
+ device_remove_file(&gadget->dev, &dev_attr_config);
+
+ set_gadget_data(gadget, NULL);
+ kfree(dev->desc.string_data);
+ kfree(dev->desc.vc_data);
+ kfree(dev->desc.vs_data);
+ kfree(dev);
+}
+
+static int
+uvc_bind(struct usb_gadget *gadget)
+{
+ struct uvc_device *dev;
+ int ret;
+
+ printk(KERN_INFO "uvc_bind: gadget %s\n", gadget->name);
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+
+ dev->state = UVC_STATE_DISABLED;
+ init_waitqueue_head(&dev->event_wait);
+ mutex_init(&dev->event_lock);
+
+ dev->gadget = gadget;
+ set_gadget_data(gadget, dev);
+
+ /* Create the sysfs configuration file. */
+ device_create_file(&gadget->dev, &dev_attr_config);
+
+ /* Preallocate control endpoint request. */
+ dev->ep0req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
+ if (dev->ep0req == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ dev->ep0req->complete = uvc_ep0_complete;
+ dev->ep0req->context = dev;
+
+ /* Initialise video. */
+ ret = uvc_video_init(&dev->video);
+ if (ret < 0)
+ goto error;
+
+ /* Register a V4L2 device. */
+ ret = uvc_register_video(dev);
+ if (ret < 0) {
+ printk(KERN_INFO "Unable to register video device\n");
+ goto error;
+ }
+
+ usb_gadget_disconnect(gadget);
+ return 0;
+
+error:
+ uvc_unbind(gadget);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+static void
+uvc_suspend(struct usb_gadget *gadget)
+{
+ printk(KERN_INFO "uvc_suspend: gadget %s\n", gadget->name);
+}
+
+static void
+uvc_resume(struct usb_gadget *gadget)
+{
+ printk(KERN_INFO "uvc_resume: gadget %s\n", gadget->name);
+}
+
+/* --------------------------------------------------------------------------
+ * Driver
+ */
+
+static struct usb_gadget_driver uvc_driver = {
+ .function = "USB Video Class",
+ .speed = USB_SPEED_HIGH,
+ .bind = uvc_bind,
+ .unbind = uvc_unbind,
+ .setup = uvc_setup,
+ .disconnect = uvc_disconnect,
+ .suspend = uvc_suspend,
+ .resume = uvc_resume,
+ .driver = {
+ .name = "g_uvc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init uvc_init (void)
+{
+ printk(KERN_INFO "%s (%s)\n", DRIVER_DESCRIPTION, DRIVER_VERSION);
+ return usb_gadget_register_driver(&uvc_driver);
+}
+
+static void __exit uvc_cleanup (void)
+{
+ usb_gadget_unregister_driver(&uvc_driver);
+}
+
+module_init(uvc_init);
+module_exit(uvc_cleanup);
+
+module_param_named(trace, uvc_trace_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(trace, "Trace level bitmask");
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_AUTHOR(DRIVER_DESCRIPTION);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);
+
new file mode 100644
@@ -0,0 +1,583 @@
+/*
+ * uvc_queue.c -- USB Video Class driver - Buffers management
+ *
+ * Copyright (C) 2005-2009
+ * 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/kernel.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <asm/atomic.h>
+
+#include "uvc.h"
+
+/* ------------------------------------------------------------------------
+ * Video buffers queue management.
+ *
+ * Video queues is initialized by uvc_queue_init(). The function performs
+ * basic initialization of the uvc_video_queue struct and never fails.
+ *
+ * Video buffer allocation and freeing are performed by uvc_alloc_buffers and
+ * uvc_free_buffers respectively. The former acquires the video queue lock,
+ * while the later must be called with the lock held (so that allocation can
+ * free previously allocated buffers). Trying to free buffers that are mapped
+ * to user space will return -EBUSY.
+ *
+ * Video buffers are managed using two queues. However, unlike most USB video
+ * drivers that use an in queue and an out queue, we use a main queue to hold
+ * all queued buffers (both 'empty' and 'done' buffers), and an irq queue to
+ * hold empty buffers. This design (copied from video-buf) minimizes locking
+ * in interrupt, as only one queue is shared between interrupt and user
+ * contexts.
+ *
+ * Use cases
+ * ---------
+ *
+ * Unless stated otherwise, all operations that modify the irq buffers queue
+ * are protected by the irq spinlock.
+ *
+ * 1. The user queues the buffers, starts streaming and dequeues a buffer.
+ *
+ * The buffers are added to the main and irq queues. Both operations are
+ * protected by the queue lock, and the later is protected by the irq
+ * spinlock as well.
+ *
+ * The completion handler fetches a buffer from the irq queue and fills it
+ * with video data. If no buffer is available (irq queue empty), the handler
+ * returns immediately.
+ *
+ * When the buffer is full, the completion handler removes it from the irq
+ * queue, marks it as ready (UVC_BUF_STATE_DONE) and wakes its wait queue.
+ * At that point, any process waiting on the buffer will be woken up. If a
+ * process tries to dequeue a buffer after it has been marked ready, the
+ * dequeing will succeed immediately.
+ *
+ * 2. Buffers are queued, user is waiting on a buffer and the device gets
+ * disconnected.
+ *
+ * When the device is disconnected, the kernel calls the completion handler
+ * with an appropriate status code. The handler marks all buffers in the
+ * irq queue as being erroneous (UVC_BUF_STATE_ERROR) and wakes them up so
+ * that any process waiting on a buffer gets woken up.
+ *
+ * Waking up up the first buffer on the irq list is not enough, as the
+ * process waiting on the buffer might restart the dequeue operation
+ * immediately.
+ *
+ */
+
+void uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type)
+{
+ mutex_init(&queue->mutex);
+ spin_lock_init(&queue->irqlock);
+ INIT_LIST_HEAD(&queue->mainqueue);
+ INIT_LIST_HEAD(&queue->irqqueue);
+ queue->type = type;
+}
+
+/*
+ * Allocate the video buffers.
+ *
+ * Pages are reserved to make sure they will not be swapped, as they will be
+ * filled in the URB completion handler.
+ *
+ * Buffers will be individually mapped, so they must all be page aligned.
+ */
+int uvc_alloc_buffers(struct uvc_video_queue *queue, unsigned int nbuffers,
+ unsigned int buflength)
+{
+ unsigned int bufsize = PAGE_ALIGN(buflength);
+ unsigned int i;
+ void *mem = NULL;
+ int ret;
+
+ if (nbuffers > UVC_MAX_VIDEO_BUFFERS)
+ nbuffers = UVC_MAX_VIDEO_BUFFERS;
+
+ mutex_lock(&queue->mutex);
+
+ if ((ret = uvc_free_buffers(queue)) < 0)
+ goto done;
+
+ /* Bail out if no buffers should be allocated. */
+ if (nbuffers == 0)
+ goto done;
+
+ /* Decrement the number of buffers until allocation succeeds. */
+ for (; nbuffers > 0; --nbuffers) {
+ mem = vmalloc_32(nbuffers * bufsize);
+ if (mem != NULL)
+ break;
+ }
+
+ if (mem == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < nbuffers; ++i) {
+ memset(&queue->buffer[i], 0, sizeof queue->buffer[i]);
+ queue->buffer[i].buf.index = i;
+ queue->buffer[i].buf.m.offset = i * bufsize;
+ queue->buffer[i].buf.length = buflength;
+ queue->buffer[i].buf.type = queue->type;
+ queue->buffer[i].buf.sequence = 0;
+ queue->buffer[i].buf.field = V4L2_FIELD_NONE;
+ queue->buffer[i].buf.memory = V4L2_MEMORY_MMAP;
+ queue->buffer[i].buf.flags = 0;
+ init_waitqueue_head(&queue->buffer[i].wait);
+ }
+
+ queue->mem = mem;
+ queue->count = nbuffers;
+ queue->buf_size = bufsize;
+ ret = nbuffers;
+
+done:
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+
+/*
+ * Free the video buffers.
+ *
+ * This function must be called with the queue lock held.
+ */
+int uvc_free_buffers(struct uvc_video_queue *queue)
+{
+ unsigned int i;
+
+ for (i = 0; i < queue->count; ++i) {
+ if (queue->buffer[i].vma_use_count != 0)
+ return -EBUSY;
+ }
+
+ if (queue->count) {
+ vfree(queue->mem);
+ queue->count = 0;
+ }
+
+ return 0;
+}
+
+static void __uvc_query_buffer(struct uvc_buffer *buf,
+ struct v4l2_buffer *v4l2_buf)
+{
+ memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf);
+
+ if (buf->vma_use_count)
+ v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;
+
+ switch (buf->state) {
+ case UVC_BUF_STATE_ERROR:
+ case UVC_BUF_STATE_DONE:
+ v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;
+ break;
+ case UVC_BUF_STATE_QUEUED:
+ case UVC_BUF_STATE_ACTIVE:
+ v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
+ break;
+ case UVC_BUF_STATE_IDLE:
+ default:
+ break;
+ }
+}
+
+int uvc_query_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf)
+{
+ int ret = 0;
+
+ mutex_lock(&queue->mutex);
+ if (v4l2_buf->index >= queue->count) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ __uvc_query_buffer(&queue->buffer[v4l2_buf->index], v4l2_buf);
+
+done:
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+
+/*
+ * Queue a video buffer. Attempting to queue a buffer that has already been
+ * queued will return -EINVAL.
+ */
+int uvc_queue_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf)
+{
+ struct uvc_buffer *buf;
+ unsigned long flags;
+ int ret = 0;
+
+ uvc_trace(UVC_TRACE_CAPTURE, "Queuing buffer %u.\n", v4l2_buf->index);
+
+ if (v4l2_buf->type != queue->type ||
+ v4l2_buf->memory != V4L2_MEMORY_MMAP) {
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer type (%u) "
+ "and/or memory (%u).\n", v4l2_buf->type,
+ v4l2_buf->memory);
+ return -EINVAL;
+ }
+
+ mutex_lock(&queue->mutex);
+ if (v4l2_buf->index >= queue->count) {
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Out of range index.\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ buf = &queue->buffer[v4l2_buf->index];
+ if (buf->state != UVC_BUF_STATE_IDLE) {
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state "
+ "(%u).\n", buf->state);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ v4l2_buf->bytesused > buf->buf.length) {
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ buf->buf.bytesused = 0;
+ else
+ buf->buf.bytesused = v4l2_buf->bytesused;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ if (queue->flags & UVC_QUEUE_DISCONNECTED) {
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+ ret = -ENODEV;
+ goto done;
+ }
+ buf->state = UVC_BUF_STATE_QUEUED;
+
+ ret = (queue->flags & UVC_QUEUE_PAUSED) != 0;
+ queue->flags &= ~UVC_QUEUE_PAUSED;
+
+ list_add_tail(&buf->stream, &queue->mainqueue);
+ list_add_tail(&buf->queue, &queue->irqqueue);
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+
+done:
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+
+static int uvc_queue_waiton(struct uvc_buffer *buf, int nonblocking)
+{
+ if (nonblocking) {
+ return (buf->state != UVC_BUF_STATE_QUEUED &&
+ buf->state != UVC_BUF_STATE_ACTIVE)
+ ? 0 : -EAGAIN;
+ }
+
+ return wait_event_interruptible(buf->wait,
+ buf->state != UVC_BUF_STATE_QUEUED &&
+ buf->state != UVC_BUF_STATE_ACTIVE);
+}
+
+/*
+ * Dequeue a video buffer. If nonblocking is false, block until a buffer is
+ * available.
+ */
+int uvc_dequeue_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf, int nonblocking)
+{
+ struct uvc_buffer *buf;
+ int ret = 0;
+
+ if (v4l2_buf->type != queue->type ||
+ v4l2_buf->memory != V4L2_MEMORY_MMAP) {
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer type (%u) "
+ "and/or memory (%u).\n", v4l2_buf->type,
+ v4l2_buf->memory);
+ return -EINVAL;
+ }
+
+ mutex_lock(&queue->mutex);
+ if (list_empty(&queue->mainqueue)) {
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Empty buffer queue.\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ buf = list_first_entry(&queue->mainqueue, struct uvc_buffer, stream);
+ if ((ret = uvc_queue_waiton(buf, nonblocking)) < 0)
+ goto done;
+
+ uvc_trace(UVC_TRACE_CAPTURE, "Dequeuing buffer %u (%u, %u bytes).\n",
+ buf->buf.index, buf->state, buf->buf.bytesused);
+
+ switch (buf->state) {
+ case UVC_BUF_STATE_ERROR:
+ uvc_trace(UVC_TRACE_CAPTURE, "[W] Corrupted data "
+ "(transmission error).\n");
+ ret = -EIO;
+ case UVC_BUF_STATE_DONE:
+ buf->state = UVC_BUF_STATE_IDLE;
+ break;
+
+ case UVC_BUF_STATE_IDLE:
+ case UVC_BUF_STATE_QUEUED:
+ case UVC_BUF_STATE_ACTIVE:
+ default:
+ uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state %u "
+ "(driver bug?).\n", buf->state);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ list_del(&buf->stream);
+ __uvc_query_buffer(buf, v4l2_buf);
+
+done:
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+
+/*
+ * Poll the video queue.
+ *
+ * This function implements video queue polling and is intended to be used by
+ * the device poll handler.
+ */
+unsigned int uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
+ poll_table *wait)
+{
+ struct uvc_buffer *buf;
+ unsigned int mask = 0;
+
+ mutex_lock(&queue->mutex);
+ if (list_empty(&queue->mainqueue))
+ goto done;
+
+ buf = list_first_entry(&queue->mainqueue, struct uvc_buffer, stream);
+
+ poll_wait(file, &buf->wait, wait);
+ if (buf->state == UVC_BUF_STATE_DONE ||
+ buf->state == UVC_BUF_STATE_ERROR)
+ mask |= POLLOUT | POLLWRNORM;
+
+done:
+ mutex_unlock(&queue->mutex);
+ return mask;
+}
+
+/*
+ * VMA operations.
+ */
+static void uvc_vm_open(struct vm_area_struct *vma)
+{
+ struct uvc_buffer *buffer = vma->vm_private_data;
+ buffer->vma_use_count++;
+}
+
+static void uvc_vm_close(struct vm_area_struct *vma)
+{
+ struct uvc_buffer *buffer = vma->vm_private_data;
+ buffer->vma_use_count--;
+}
+
+static struct vm_operations_struct uvc_vm_ops = {
+ .open = uvc_vm_open,
+ .close = uvc_vm_close,
+};
+
+/*
+ * Memory-map a buffer.
+ *
+ * This function implements video buffer memory mapping and is intended to be
+ * used by the device mmap handler.
+ */
+int uvc_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
+{
+ struct uvc_buffer *uninitialized_var(buffer);
+ struct page *page;
+ unsigned long addr, start, size;
+ unsigned int i;
+ int ret = 0;
+
+ start = vma->vm_start;
+ size = vma->vm_end - vma->vm_start;
+
+ mutex_lock(&queue->mutex);
+
+ for (i = 0; i < queue->count; ++i) {
+ buffer = &queue->buffer[i];
+ if ((buffer->buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
+ break;
+ }
+
+ if (i == queue->count || size != queue->buf_size) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * VM_IO marks the area as being an mmaped region for I/O to a
+ * device. It also prevents the region from being core dumped.
+ */
+ vma->vm_flags |= VM_IO;
+
+ addr = (unsigned long)queue->mem + buffer->buf.m.offset;
+ while (size > 0) {
+ page = vmalloc_to_page((void *)addr);
+ if ((ret = vm_insert_page(vma, start, page)) < 0)
+ goto done;
+
+ start += PAGE_SIZE;
+ addr += PAGE_SIZE;
+ size -= PAGE_SIZE;
+ }
+
+ vma->vm_ops = &uvc_vm_ops;
+ vma->vm_private_data = buffer;
+ uvc_vm_open(vma);
+
+done:
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+
+/*
+ * Enable or disable the video buffers queue.
+ *
+ * The queue must be enabled before starting video acquisition and must be
+ * disabled after stopping it. This ensures that the video buffers queue
+ * state can be properly initialized before buffers are accessed from the
+ * interrupt handler.
+ *
+ * Enabling the video queue initializes parameters (such as sequence number,
+ * sync pattern, ...). If the queue is already enabled, return -EBUSY.
+ *
+ * Disabling the video queue cancels the queue and removes all buffers from
+ * the main queue.
+ *
+ * This function can't be called from interrupt context. Use
+ * uvc_queue_cancel() instead.
+ */
+int uvc_queue_enable(struct uvc_video_queue *queue, int enable)
+{
+ unsigned int i;
+ int ret = 0;
+
+ mutex_lock(&queue->mutex);
+ if (enable) {
+ if (uvc_queue_streaming(queue)) {
+ ret = -EBUSY;
+ goto done;
+ }
+ queue->sequence = 0;
+ queue->flags |= UVC_QUEUE_STREAMING;
+ queue->buf_used = 0;
+ } else {
+ uvc_queue_cancel(queue, 0);
+ INIT_LIST_HEAD(&queue->mainqueue);
+
+ for (i = 0; i < queue->count; ++i)
+ queue->buffer[i].state = UVC_BUF_STATE_IDLE;
+
+ queue->flags &= ~UVC_QUEUE_STREAMING;
+ }
+
+done:
+ mutex_unlock(&queue->mutex);
+ return ret;
+}
+
+/*
+ * Cancel the video buffers queue.
+ *
+ * Cancelling the queue marks all buffers on the irq queue as erroneous,
+ * wakes them up and removes them from the queue.
+ *
+ * If the disconnect parameter is set, further calls to uvc_queue_buffer will
+ * fail with -ENODEV.
+ *
+ * This function acquires the irq spinlock and can be called from interrupt
+ * context.
+ */
+void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect)
+{
+ struct uvc_buffer *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ while (!list_empty(&queue->irqqueue)) {
+ buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+ queue);
+ list_del(&buf->queue);
+ buf->state = UVC_BUF_STATE_ERROR;
+ wake_up(&buf->wait);
+ }
+ /* This must be protected by the irqlock spinlock to avoid race
+ * conditions between uvc_queue_buffer and the disconnection event that
+ * could result in an interruptible wait in uvc_dequeue_buffer. Do not
+ * blindly replace this logic by checking for the UVC_DEV_DISCONNECTED
+ * state outside the queue code.
+ */
+ if (disconnect)
+ queue->flags |= UVC_QUEUE_DISCONNECTED;
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
+ struct uvc_buffer *buf)
+{
+ struct uvc_buffer *nextbuf;
+ unsigned long flags;
+
+ if ((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) &&
+ buf->buf.length != buf->buf.bytesused) {
+ buf->state = UVC_BUF_STATE_QUEUED;
+ buf->buf.bytesused = 0;
+ return buf;
+ }
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ list_del(&buf->queue);
+ if (!list_empty(&queue->irqqueue))
+ nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+ queue);
+ else
+ nextbuf = NULL;
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+
+ buf->buf.sequence = queue->sequence++;
+ do_gettimeofday(&buf->buf.timestamp);
+
+ wake_up(&buf->wait);
+ return nextbuf;
+}
+
+struct uvc_buffer *uvc_queue_head(struct uvc_video_queue *queue)
+{
+ struct uvc_buffer *buf = NULL;
+
+ if (!list_empty(&queue->irqqueue))
+ buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+ queue);
+ else
+ queue->flags |= UVC_QUEUE_PAUSED;
+
+ return buf;
+}
+
new file mode 100644
@@ -0,0 +1,90 @@
+#ifndef _UVC_QUEUE_H_
+#define _UVC_QUEUE_H_
+
+#include <linux/kernel.h>
+#include <linux/videodev2.h>
+
+#ifdef __KERNEL__
+
+#include <linux/poll.h>
+
+/* Maximum frame size in bytes, for sanity checking. */
+#define UVC_MAX_FRAME_SIZE (16*1024*1024)
+/* Maximum number of video buffers. */
+#define UVC_MAX_VIDEO_BUFFERS 32
+
+/* ------------------------------------------------------------------------
+ * Structures.
+ */
+
+enum uvc_buffer_state {
+ UVC_BUF_STATE_IDLE = 0,
+ UVC_BUF_STATE_QUEUED = 1,
+ UVC_BUF_STATE_ACTIVE = 2,
+ UVC_BUF_STATE_DONE = 3,
+ UVC_BUF_STATE_ERROR = 4,
+};
+
+struct uvc_buffer {
+ unsigned long vma_use_count;
+ struct list_head stream;
+
+ /* Touched by interrupt handler. */
+ struct v4l2_buffer buf;
+ struct list_head queue;
+ wait_queue_head_t wait;
+ enum uvc_buffer_state state;
+};
+
+#define UVC_QUEUE_STREAMING (1 << 0)
+#define UVC_QUEUE_DISCONNECTED (1 << 1)
+#define UVC_QUEUE_DROP_INCOMPLETE (1 << 2)
+#define UVC_QUEUE_PAUSED (1 << 3)
+
+struct uvc_video_queue {
+ enum v4l2_buf_type type;
+
+ void *mem;
+ unsigned int flags;
+ __u32 sequence;
+
+ unsigned int count;
+ unsigned int buf_size;
+ unsigned int buf_used;
+ struct uvc_buffer buffer[UVC_MAX_VIDEO_BUFFERS];
+ struct mutex mutex; /* protects buffers and mainqueue */
+ spinlock_t irqlock; /* protects irqqueue */
+
+ struct list_head mainqueue;
+ struct list_head irqqueue;
+};
+
+extern void uvc_queue_init(struct uvc_video_queue *queue,
+ enum v4l2_buf_type type);
+extern int uvc_alloc_buffers(struct uvc_video_queue *queue,
+ unsigned int nbuffers, unsigned int buflength);
+extern int uvc_free_buffers(struct uvc_video_queue *queue);
+extern int uvc_query_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf);
+extern int uvc_queue_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf);
+extern int uvc_dequeue_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf, int nonblocking);
+extern int uvc_queue_enable(struct uvc_video_queue *queue, int enable);
+extern void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect);
+extern struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
+ struct uvc_buffer *buf);
+extern unsigned int uvc_queue_poll(struct uvc_video_queue *queue,
+ struct file *file, poll_table *wait);
+extern int uvc_queue_mmap(struct uvc_video_queue *queue,
+ struct vm_area_struct *vma);
+static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
+{
+ return queue->flags & UVC_QUEUE_STREAMING;
+}
+extern struct uvc_buffer *uvc_queue_head(struct uvc_video_queue *queue);
+
+#endif /* __KERNEL__ */
+
+#endif /* _UVC_QUEUE_H_ */
+
new file mode 100644
@@ -0,0 +1,376 @@
+/*
+ * uvc_v4l2.c -- USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009
+ * 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/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+
+#include <media/v4l2-dev.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
+#include <media/v4l2-ioctl.h>
+#endif
+
+#include "uvc.h"
+#include "uvc_queue.h"
+
+/* --------------------------------------------------------------------------
+ * Events handling
+ *
+ */
+
+static int
+uvc_event_read(struct uvc_device *uvc, struct uvc_event *event,
+ int nonblocking)
+{
+ int ret;
+
+ if (nonblocking)
+ ret = kfifo_len(uvc->events) ? 0 : -EAGAIN;
+ else
+ ret = wait_event_interruptible(uvc->event_wait,
+ kfifo_len(uvc->events) != 0);
+
+ if (ret < 0)
+ return ret;
+
+ kfifo_get(uvc->events, (unsigned char *)event, sizeof(*event));
+
+ if (event->type == UVC_EVENT_SETUP) {
+ /* Tell the complete callback to generate an event for the
+ * next request that will be enqueued by uvc_event_write.
+ */
+ uvc->event_setup_out = !(event->req.bRequestType & USB_DIR_IN);
+ uvc->event_length = event->req.wLength;
+ }
+
+ return 0;
+}
+
+static int
+uvc_event_write(struct uvc_device *uvc, struct uvc_event_data *data)
+{
+ struct usb_composite_dev *cdev = uvc->func.config->cdev;
+ struct usb_request *req = uvc->control_req;
+
+ if (data->length < 0)
+ return usb_ep_set_halt(cdev->gadget->ep0);
+
+ req->length = min((int)uvc->event_length, data->length);
+ req->zero = data->length < uvc->event_length;
+ req->dma = DMA_ADDR_INVALID;
+
+ memcpy(req->buf, data->data, data->length);
+
+ return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL);
+}
+
+/* --------------------------------------------------------------------------
+ * V4L2
+ */
+
+struct uvc_format
+{
+ u8 bpp;
+ u32 fcc;
+};
+
+static struct uvc_format uvc_formats[] = {
+ { 16, V4L2_PIX_FMT_YUYV },
+ { 0, V4L2_PIX_FMT_MJPEG },
+};
+
+static int
+uvc_v4l2_get_format(struct uvc_video *video, struct v4l2_format *fmt)
+{
+ fmt->fmt.pix.pixelformat = video->fcc;
+ fmt->fmt.pix.width = video->width;
+ fmt->fmt.pix.height = video->height;
+ fmt->fmt.pix.field = V4L2_FIELD_NONE;
+ fmt->fmt.pix.bytesperline = video->bpp * video->width / 8;
+ fmt->fmt.pix.sizeimage = video->imagesize;
+ fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+ fmt->fmt.pix.priv = 0;
+
+ return 0;
+}
+
+static int
+uvc_v4l2_set_format(struct uvc_video *video, struct v4l2_format *fmt)
+{
+ struct uvc_format *format;
+ unsigned int imagesize;
+ unsigned int bpl;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(uvc_formats); ++i) {
+ format = &uvc_formats[i];
+ if (format->fcc == fmt->fmt.pix.pixelformat)
+ break;
+ }
+
+ if (format == NULL || format->fcc != fmt->fmt.pix.pixelformat) {
+ printk(KERN_INFO "Unsupported format 0x%08x.\n",
+ fmt->fmt.pix.pixelformat);
+ return -EINVAL;
+ }
+
+ bpl = format->bpp * fmt->fmt.pix.width / 8;
+ imagesize = bpl ? bpl * video->height : fmt->fmt.pix.sizeimage;
+
+ video->fcc = format->fcc;
+ video->bpp = format->bpp;
+ video->width = fmt->fmt.pix.width;
+ video->height = fmt->fmt.pix.height;
+ video->imagesize = imagesize;
+
+ fmt->fmt.pix.field = V4L2_FIELD_NONE;
+ fmt->fmt.pix.bytesperline = bpl;
+ fmt->fmt.pix.sizeimage = imagesize;
+ fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+ fmt->fmt.pix.priv = 0;
+
+ return 0;
+}
+
+static int
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
+uvc_v4l2_open(struct inode *inode, struct file *file)
+#else
+uvc_v4l2_open(struct file *file)
+#endif
+{
+ struct video_device *vdev = video_devdata(file);
+ struct uvc_device *uvc = video_get_drvdata(vdev);
+ struct uvc_file_handle *handle;
+
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (handle == NULL)
+ return -ENOMEM;
+
+ handle->device = &uvc->video;
+ file->private_data = handle;
+
+ uvc_function_connect(uvc);
+ return 0;
+}
+
+static int
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+uvc_v4l2_release(struct inode *inode, struct file *file)
+#else
+uvc_v4l2_release(struct file *file)
+#endif
+{
+ struct video_device *vdev = video_devdata(file);
+ struct uvc_device *uvc = video_get_drvdata(vdev);
+ struct uvc_file_handle *handle = file->private_data;
+ struct uvc_video *video = handle->device;
+
+ uvc_function_disconnect(uvc);
+
+ uvc_video_enable(video, 0);
+ mutex_lock(&video->queue.mutex);
+ if (uvc_free_buffers(&video->queue) < 0)
+ printk(KERN_ERR "uvc_v4l2_release: Unable to free "
+ "buffers.\n");
+ mutex_unlock(&video->queue.mutex);
+
+ file->private_data = NULL;
+ kfree(handle);
+ return 0;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
+static int
+uvc_v4l2_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg)
+#else
+static long
+uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg)
+#endif
+{
+ struct video_device *vdev = video_devdata(file);
+ struct uvc_device *uvc = video_get_drvdata(vdev);
+ struct usb_composite_dev *cdev = uvc->func.config->cdev;
+ struct uvc_video *video = &uvc->video;
+ int ret = 0;
+
+ switch (cmd) {
+ /* Query capabilities */
+ case VIDIOC_QUERYCAP:
+ {
+ struct v4l2_capability *cap = arg;
+
+ memset(cap, 0, sizeof *cap);
+ strncpy(cap->driver, "g_uvc", sizeof(cap->driver));
+ strncpy(cap->card, cdev->gadget->name, sizeof(cap->card));
+ strncpy(cap->bus_info, dev_name(&cdev->gadget->dev),
+ sizeof cap->bus_info);
+ cap->version = DRIVER_VERSION_NUMBER;
+ cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+ break;
+ }
+
+ /* Get & Set format */
+ case VIDIOC_G_FMT:
+ {
+ struct v4l2_format *fmt = arg;
+
+ if (fmt->type != video->queue.type)
+ return -EINVAL;
+
+ return uvc_v4l2_get_format(video, fmt);
+ }
+
+ case VIDIOC_S_FMT:
+ {
+ struct v4l2_format *fmt = arg;
+
+ if (fmt->type != video->queue.type)
+ return -EINVAL;
+
+ return uvc_v4l2_set_format(video, fmt);
+ }
+
+ /* Buffers & streaming */
+ case VIDIOC_REQBUFS:
+ {
+ struct v4l2_requestbuffers *rb = arg;
+
+ if (rb->type != video->queue.type ||
+ rb->memory != V4L2_MEMORY_MMAP)
+ return -EINVAL;
+
+ ret = uvc_alloc_buffers(&video->queue, rb->count,
+ video->imagesize);
+ if (ret < 0)
+ return ret;
+
+ rb->count = ret;
+ ret = 0;
+ break;
+ }
+
+ case VIDIOC_QUERYBUF:
+ {
+ struct v4l2_buffer *buf = arg;
+
+ if (buf->type != video->queue.type)
+ return -EINVAL;
+
+ return uvc_query_buffer(&video->queue, buf);
+ }
+
+ case VIDIOC_QBUF:
+ if ((ret = uvc_queue_buffer(&video->queue, arg)) < 0)
+ return ret;
+
+ return uvc_video_pump(video);
+
+ case VIDIOC_DQBUF:
+ return uvc_dequeue_buffer(&video->queue, arg,
+ file->f_flags & O_NONBLOCK);
+
+ case VIDIOC_STREAMON:
+ {
+ int *type = arg;
+
+ if (*type != video->queue.type)
+ return -EINVAL;
+
+ return uvc_video_enable(video, 1);
+ }
+
+ case VIDIOC_STREAMOFF:
+ {
+ int *type = arg;
+
+ if (*type != video->queue.type)
+ return -EINVAL;
+
+ return uvc_video_enable(video, 0);
+ }
+
+ /* Events */
+ case UVCIOC_EVENT_READ:
+ ret = uvc_event_read(uvc, arg, file->f_flags & O_NONBLOCK);
+ break;
+
+ case UVCIOC_EVENT_WRITE:
+ ret = uvc_event_write(uvc, arg);
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return ret;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
+static int
+uvc_v4l2_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, uvc_v4l2_do_ioctl);
+}
+#else
+static long
+uvc_v4l2_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl);
+}
+#endif
+
+static int
+uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct uvc_device *uvc = video_get_drvdata(vdev);
+
+ return uvc_queue_mmap(&uvc->video.queue, vma);
+}
+
+static unsigned int
+uvc_v4l2_poll(struct file *file, poll_table *wait)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct uvc_device *uvc = video_get_drvdata(vdev);
+ unsigned int mask = 0;
+
+ poll_wait(file, &uvc->event_wait, wait);
+ if (kfifo_len(uvc->events) != 0)
+ mask |= POLLPRI;
+
+ mask |= uvc_queue_poll(&uvc->video.queue, file, wait);
+
+ return mask;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
+struct file_operations uvc_v4l2_fops = {
+#else
+struct v4l2_file_operations uvc_v4l2_fops = {
+#endif
+ .owner = THIS_MODULE,
+ .open = uvc_v4l2_open,
+ .release = uvc_v4l2_release,
+ .ioctl = uvc_v4l2_ioctl,
+ .mmap = uvc_v4l2_mmap,
+ .poll = uvc_v4l2_poll,
+};
+
new file mode 100644
@@ -0,0 +1,386 @@
+/*
+ * uvc_video.c -- USB Video Class Gadget driver
+ *
+ * Copyright (C) 2009
+ * 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/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include <media/v4l2-dev.h>
+
+#include "uvc.h"
+#include "uvc_queue.h"
+
+/* --------------------------------------------------------------------------
+ * Video codecs
+ */
+
+static int
+uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
+ u8 *data, int len)
+{
+ data[0] = 2;
+ data[1] = UVC_STREAM_EOH | video->fid;
+
+ if (buf->buf.bytesused - video->queue.buf_used <= len - 2)
+ data[1] |= UVC_STREAM_EOF;
+
+ return 2;
+}
+
+static int
+uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf,
+ u8 *data, int len)
+{
+ struct uvc_video_queue *queue = &video->queue;
+ unsigned int nbytes;
+ void *mem;
+
+ /* Copy video data to the USB buffer. */
+ mem = queue->mem + buf->buf.m.offset + queue->buf_used;
+ nbytes = min((unsigned int)len, buf->buf.bytesused - queue->buf_used);
+
+ memcpy(data, mem, nbytes);
+ queue->buf_used += nbytes;
+
+ return nbytes;
+}
+
+static void
+uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
+ struct uvc_buffer *buf)
+{
+ void *mem = req->buf;
+ int len = video->req_size;
+ int ret;
+
+ /* Add a header at the beginning of the payload. */
+ if (video->payload_size == 0) {
+ ret = uvc_video_encode_header(video, buf, mem, len);
+ video->payload_size += ret;
+ mem += ret;
+ len -= ret;
+ }
+
+ /* Process video data. */
+ len = min((int)(video->max_payload_size - video->payload_size), len);
+ ret = uvc_video_encode_data(video, buf, mem, len);
+
+ video->payload_size += ret;
+ len -= ret;
+
+ req->length = video->req_size - len;
+ req->zero = video->payload_size == video->max_payload_size;
+
+ if (buf->buf.bytesused == video->queue.buf_used) {
+ video->queue.buf_used = 0;
+ buf->state = UVC_BUF_STATE_DONE;
+ uvc_queue_next_buffer(&video->queue, buf);
+ video->fid ^= UVC_STREAM_FID;
+
+ video->payload_size = 0;
+ }
+
+ if (video->payload_size == video->max_payload_size ||
+ buf->buf.bytesused == video->queue.buf_used)
+ video->payload_size = 0;
+}
+
+static void
+uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
+ struct uvc_buffer *buf)
+{
+ void *mem = req->buf;
+ int len = video->req_size;
+ int ret;
+
+ /* Add the header. */
+ ret = uvc_video_encode_header(video, buf, mem, len);
+ mem += ret;
+ len -= ret;
+
+ /* Process video data. */
+ ret = uvc_video_encode_data(video, buf, mem, len);
+ len -= ret;
+
+ req->length = video->req_size - len;
+
+ if (buf->buf.bytesused == video->queue.buf_used) {
+ video->queue.buf_used = 0;
+ buf->state = UVC_BUF_STATE_DONE;
+ uvc_queue_next_buffer(&video->queue, buf);
+ video->fid ^= UVC_STREAM_FID;
+ }
+}
+
+/* --------------------------------------------------------------------------
+ * Request handling
+ */
+
+/*
+ * I somehow feel that synchronisation won't be easy to achieve here. We have
+ * three events that control USB requests submission:
+ *
+ * - USB request completion: the completion handler will resubmit the request
+ * if a video buffer is available.
+ *
+ * - USB interface setting selection: in response to a SET_INTERFACE request,
+ * the handler will start streaming if a video buffer is available and if
+ * video is not currently streaming.
+ *
+ * - V4L2 buffer queueing: the driver will start streaming if video is not
+ * currently streaming.
+ *
+ * Race conditions between those 3 events might lead to deadlocks or other
+ * nasty side effects.
+ *
+ * The "video currently streaming" condition can't be detected by the irqqueue
+ * being empty, as a request can still be in flight. A separate "queue paused"
+ * flag is thus needed.
+ *
+ * The paused flag will be set when we try to retrieve the irqqueue head if the
+ * queue is empty, and cleared when we queue a buffer.
+ *
+ * The USB request completion handler will get the buffer at the irqqueue head
+ * under protection of the queue spinlock. If the queue is empty, the streaming
+ * paused flag will be set. Right after releasing the spinlock a userspace
+ * application can queue a buffer. The flag will then cleared, and the ioctl
+ * handler will restart the video stream.
+ */
+static void
+uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct uvc_video *video = req->context;
+ struct uvc_buffer *buf;
+ unsigned long flags;
+ int ret;
+
+ switch (req->status) {
+ case 0:
+ break;
+
+ case -ESHUTDOWN:
+ printk(KERN_INFO "VS request cancelled.\n");
+ goto requeue;
+
+ default:
+ printk(KERN_INFO "VS request completed with status %d.\n",
+ req->status);
+ goto requeue;
+ }
+
+ spin_lock_irqsave(&video->queue.irqlock, flags);
+ buf = uvc_queue_head(&video->queue);
+ if (buf == NULL) {
+ spin_unlock_irqrestore(&video->queue.irqlock, flags);
+ goto requeue;
+ }
+
+ video->encode(req, video, buf);
+
+ if ((ret = usb_ep_queue(ep, req, GFP_ATOMIC)) < 0) {
+ printk(KERN_INFO "Failed to queue request (%d).\n", ret);
+ usb_ep_set_halt(ep);
+ spin_unlock_irqrestore(&video->queue.irqlock, flags);
+ goto requeue;
+ }
+ spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
+ return;
+
+requeue:
+ spin_lock_irqsave(&video->req_lock, flags);
+ list_add_tail(&req->list, &video->req_free);
+ spin_unlock_irqrestore(&video->req_lock, flags);
+}
+
+static int
+uvc_video_free_requests(struct uvc_video *video)
+{
+ unsigned int i;
+
+ for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
+ if (video->req[i]) {
+ usb_ep_free_request(video->ep, video->req[i]);
+ video->req[i] = NULL;
+ }
+
+ if (video->req_buffer[i]) {
+ kfree(video->req_buffer[i]);
+ video->req_buffer[i] = NULL;
+ }
+ }
+
+ INIT_LIST_HEAD(&video->req_free);
+ video->req_size = 0;
+ return 0;
+}
+
+static int
+uvc_video_alloc_requests(struct uvc_video *video)
+{
+ unsigned int i;
+ int ret = -ENOMEM;
+
+ BUG_ON(video->req_size);
+
+ for (i = 0; i < UVC_NUM_REQUESTS; ++i) {
+ video->req_buffer[i] = kmalloc(video->ep->maxpacket, GFP_KERNEL);
+ if (video->req_buffer[i] == NULL)
+ goto error;
+
+ video->req[i] = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+ if (video->req[i] == NULL)
+ goto error;
+
+ video->req[i]->buf = video->req_buffer[i];
+ video->req[i]->length = 0;
+ video->req[i]->dma = DMA_ADDR_INVALID;
+ video->req[i]->complete = uvc_video_complete;
+ video->req[i]->context = video;
+
+ list_add_tail(&video->req[i]->list, &video->req_free);
+ }
+
+ video->req_size = video->ep->maxpacket;
+ return 0;
+
+error:
+ uvc_video_free_requests(video);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * Video streaming
+ */
+
+/*
+ * uvc_video_pump - Pump video data into the USB requests
+ *
+ * This function fills the available USB requests (listed in req_free) with
+ * video data from the queued buffers.
+ */
+int
+uvc_video_pump(struct uvc_video *video)
+{
+ struct usb_request *req;
+ struct uvc_buffer *buf;
+ unsigned long flags;
+ int ret;
+
+ /* FIXME TODO Race between uvc_video_pump and requests completion
+ * handler ???
+ */
+
+ while (1) {
+ /* Retrieve the first available USB request, protected by the
+ * request lock.
+ */
+ spin_lock_irqsave(&video->req_lock, flags);
+ if (list_empty(&video->req_free)) {
+ spin_unlock_irqrestore(&video->req_lock, flags);
+ return 0;
+ }
+ req = list_first_entry(&video->req_free, struct usb_request,
+ list);
+ list_del(&req->list);
+ spin_unlock_irqrestore(&video->req_lock, flags);
+
+ /* Retrieve the first available video buffer and fill the
+ * request, protected by the video queue irqlock.
+ */
+ spin_lock_irqsave(&video->queue.irqlock, flags);
+ buf = uvc_queue_head(&video->queue);
+ if (buf == NULL) {
+ spin_unlock_irqrestore(&video->queue.irqlock, flags);
+ break;
+ }
+
+ video->encode(req, video, buf);
+
+ /* Queue the USB request */
+ if ((ret = usb_ep_queue(video->ep, req, GFP_KERNEL)) < 0) {
+ printk(KERN_INFO "Failed to queue request (%d)\n", ret);
+ usb_ep_set_halt(video->ep);
+ spin_unlock_irqrestore(&video->queue.irqlock, flags);
+ break;
+ }
+ spin_unlock_irqrestore(&video->queue.irqlock, flags);
+ }
+
+ spin_lock_irqsave(&video->req_lock, flags);
+ list_add_tail(&req->list, &video->req_free);
+ spin_unlock_irqrestore(&video->req_lock, flags);
+ return 0;
+}
+
+/*
+ * Enable or disable the video stream.
+ */
+int
+uvc_video_enable(struct uvc_video *video, int enable)
+{
+ unsigned int i;
+ int ret;
+
+ if (video->ep == NULL) {
+ printk(KERN_INFO "Video enable failed, device is "
+ "uninitialized.\n");
+ return -ENODEV;
+ }
+
+ if (!enable) {
+ for (i = 0; i < UVC_NUM_REQUESTS; ++i)
+ usb_ep_dequeue(video->ep, video->req[i]);
+
+ uvc_video_free_requests(video);
+ uvc_queue_enable(&video->queue, 0);
+ return 0;
+ }
+
+ if ((ret = uvc_queue_enable(&video->queue, 1)) < 0)
+ return ret;
+
+ if ((ret = uvc_video_alloc_requests(video)) < 0)
+ return ret;
+
+ if (video->max_payload_size) {
+ video->encode = uvc_video_encode_bulk;
+ video->payload_size = 0;
+ } else
+ video->encode = uvc_video_encode_isoc;
+
+ return uvc_video_pump(video);
+}
+
+/*
+ * Initialize the UVC video stream.
+ */
+int
+uvc_video_init(struct uvc_video *video)
+{
+ INIT_LIST_HEAD(&video->req_free);
+ spin_lock_init(&video->req_lock);
+
+ video->fcc = V4L2_PIX_FMT_YUYV;
+ video->bpp = 16;
+ video->width = 320;
+ video->height = 240;
+ video->imagesize = 320 * 240 * 2;
+
+ /* Initialize the video buffers queue. */
+ uvc_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+ return 0;
+}
+
This USB video class function driver implements a video capture device from the host's point of view. It creates a V4L2 output device on the gadget's side to transfer data from a userspace application over USB. The UVC-specific descriptors are passed by the gadget driver to the UVC function driver, making them completely configurable without any modification to the function's driver code. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> --- drivers/usb/gadget/f_uvc.c | 673 +++++++++++++++++++++++++++++ drivers/usb/gadget/f_uvc.h | 376 +++++++++++++++++ drivers/usb/gadget/uvc.h | 299 +++++++++++++ drivers/usb/gadget/uvc_gadget.c | 890 +++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/uvc_queue.c | 583 +++++++++++++++++++++++++ drivers/usb/gadget/uvc_queue.h | 90 ++++ drivers/usb/gadget/uvc_v4l2.c | 376 +++++++++++++++++ drivers/usb/gadget/uvc_video.c | 386 +++++++++++++++++ 8 files changed, 3673 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/f_uvc.c create mode 100644 drivers/usb/gadget/f_uvc.h create mode 100644 drivers/usb/gadget/uvc.h create mode 100644 drivers/usb/gadget/uvc_gadget.c create mode 100644 drivers/usb/gadget/uvc_queue.c create mode 100644 drivers/usb/gadget/uvc_queue.h create mode 100644 drivers/usb/gadget/uvc_v4l2.c create mode 100644 drivers/usb/gadget/uvc_video.c