diff options
Diffstat (limited to 'drivers/usb/gadget')
29 files changed, 7240 insertions, 129 deletions
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 11a3e0fa433..649c0c5f715 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -710,6 +710,43 @@ config USB_GADGETFS Say "y" to link the driver statically, or "m" to build a dynamically linked module called "gadgetfs". +config USB_FUNCTIONFS + tristate "Function Filesystem (EXPERIMENTAL)" + depends on EXPERIMENTAL + help + The Function Filesystem (FunctioFS) lets one create USB + composite functions in user space in the same way as GadgetFS + lets one create USB gadgets in user space. This allows creation + of composite gadgets such that some of the functions are + implemented in kernel space (for instance Ethernet, serial or + mass storage) and other are implemented in user space. + + Say "y" to link the driver statically, or "m" to build + a dynamically linked module called "g_ffs". + +config USB_FUNCTIONFS_ETH + bool "Include CDC ECM (Ethernet) function" + depends on USB_FUNCTIONFS && NET + help + Include an CDC ECM (Ethernet) funcion in the CDC ECM (Funcion) + Filesystem. If you also say "y" to the RNDIS query below the + gadget will have two configurations. + +config USB_FUNCTIONFS_RNDIS + bool "Include RNDIS (Ethernet) function" + depends on USB_FUNCTIONFS && NET + help + Include an RNDIS (Ethernet) funcion in the Funcion Filesystem. + If you also say "y" to the CDC ECM query above the gadget will + have two configurations. + +config USB_FUNCTIONFS_GENERIC + bool "Include 'pure' configuration" + depends on USB_FUNCTIONFS && (USB_FUNCTIONFS_ETH || USB_FUNCTIONFS_RNDIS) + help + Include a configuration with FunctionFS and no Ethernet + configuration. + config USB_FILE_STORAGE tristate "File-backed Storage Gadget" depends on BLOCK @@ -863,11 +900,30 @@ config USB_G_MULTI_CDC If unsure, say "y". +config USB_G_HID + tristate "HID Gadget" + help + The HID gadget driver provides generic emulation of USB + Human Interface Devices (HID). + + For more information, see Documentation/usb/gadget_hid.txt which + includes sample code for accessing the device files. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_hid". # put drivers that need isochronous transfer support (for audio # or video class gadget drivers), or specific hardware, here. +config USB_G_WEBCAM + tristate "USB Webcam Gadget" + depends on VIDEO_DEV + help + The Webcam Gadget acts as a composite USB Audio and Video Class + device. It provides a userspace API to process UVC control requests + and stream video data to the host. -# - none yet + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_webcam". endchoice diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 43b51da8d72..9bcde110feb 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -20,7 +20,7 @@ obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o fsl_usb2_udc-objs := fsl_udc_core.o ifeq ($(CONFIG_ARCH_MXC),y) -fsl_usb2_udc-objs += fsl_mx3_udc.o +fsl_usb2_udc-objs += fsl_mxc_udc.o endif obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o @@ -43,18 +43,24 @@ g_mass_storage-objs := mass_storage.o g_printer-objs := printer.o g_cdc-objs := cdc2.o g_multi-objs := multi.o +g_hid-objs := hid.o g_nokia-objs := nokia.o +g_webcam-objs := webcam.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o obj-$(CONFIG_USB_ETH) += g_ether.o obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o +obj-$(CONFIG_USB_FUNCTIONFS) += g_ffs.o +obj-$(CONFIG_USB_ETH_FUNCTIONFS) += g_eth_ffs.o obj-$(CONFIG_USB_FILE_STORAGE) += g_file_storage.o obj-$(CONFIG_USB_MASS_STORAGE) += g_mass_storage.o obj-$(CONFIG_USB_G_SERIAL) += g_serial.o obj-$(CONFIG_USB_G_PRINTER) += g_printer.o obj-$(CONFIG_USB_MIDI_GADGET) += g_midi.o obj-$(CONFIG_USB_CDC_COMPOSITE) += g_cdc.o +obj-$(CONFIG_USB_G_HID) += g_hid.o obj-$(CONFIG_USB_G_MULTI) += g_multi.o obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o +obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o diff --git a/drivers/usb/gadget/at91_udc.c b/drivers/usb/gadget/at91_udc.c index df1bae9b048..eaa79c8a9b8 100644 --- a/drivers/usb/gadget/at91_udc.c +++ b/drivers/usb/gadget/at91_udc.c @@ -366,6 +366,13 @@ rescan: if (is_done) done(ep, req, 0); else if (ep->is_pingpong) { + /* + * One dummy read to delay the code because of a HW glitch: + * CSR returns bad RXCOUNT when read too soon after updating + * RX_DATA_BK flags. + */ + csr = __raw_readl(creg); + bufferspace -= count; buf += count; goto rescan; diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 75a256f3d45..d623c7bda1f 100644 --- a/drivers/usb/gadget/atmel_usba_udc.c +++ b/drivers/usb/gadget/atmel_usba_udc.c @@ -48,10 +48,9 @@ static int queue_dbg_open(struct inode *inode, struct file *file) spin_lock_irq(&ep->udc->lock); list_for_each_entry(req, &ep->queue, queue) { - req_copy = kmalloc(sizeof(*req_copy), GFP_ATOMIC); + req_copy = kmemdup(req, sizeof(*req_copy), GFP_ATOMIC); if (!req_copy) goto fail; - memcpy(req_copy, req, sizeof(*req_copy)); list_add_tail(&req_copy->queue, queue_data); } spin_unlock_irq(&ep->udc->lock); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 09289bb1e20..391d169f8d0 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -36,7 +36,7 @@ */ /* big enough to hold our biggest descriptor */ -#define USB_BUFSIZ 512 +#define USB_BUFSIZ 1024 static struct usb_composite_driver *composite; @@ -85,7 +85,7 @@ MODULE_PARM_DESC(iSerialNumber, "SerialNumber string"); * This function returns the value of the function's bind(), which is * zero for success else a negative errno value. */ -int __init usb_add_function(struct usb_configuration *config, +int usb_add_function(struct usb_configuration *config, struct usb_function *function) { int value = -EINVAL; @@ -215,7 +215,7 @@ int usb_function_activate(struct usb_function *function) * Returns the interface ID which was allocated; or -ENODEV if no * more interface IDs can be allocated. */ -int __init usb_interface_id(struct usb_configuration *config, +int usb_interface_id(struct usb_configuration *config, struct usb_function *function) { unsigned id = config->next_interface_id; @@ -480,7 +480,7 @@ done: * assigns global resources including string IDs, and per-configuration * resources such as interface IDs and endpoints. */ -int __init usb_add_config(struct usb_composite_dev *cdev, +int usb_add_config(struct usb_composite_dev *cdev, struct usb_configuration *config) { int status = -EINVAL; @@ -677,7 +677,7 @@ static int get_string(struct usb_composite_dev *cdev, * ensure that for example different functions don't wrongly assign * different meanings to the same identifier. */ -int __init usb_string_id(struct usb_composite_dev *cdev) +int usb_string_id(struct usb_composite_dev *cdev) { if (cdev->next_string_id < 254) { /* string id 0 is reserved */ @@ -898,7 +898,19 @@ static void composite_disconnect(struct usb_gadget *gadget) /*-------------------------------------------------------------------------*/ -static void /* __init_or_exit */ +static ssize_t composite_show_suspended(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_composite_dev *cdev = get_gadget_data(gadget); + + return sprintf(buf, "%d\n", cdev->suspended); +} + +static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL); + +static void composite_unbind(struct usb_gadget *gadget) { struct usb_composite_dev *cdev = get_gadget_data(gadget); @@ -944,10 +956,11 @@ composite_unbind(struct usb_gadget *gadget) } kfree(cdev); set_gadget_data(gadget, NULL); + device_remove_file(&gadget->dev, &dev_attr_suspended); composite = NULL; } -static void __init +static void string_override_one(struct usb_gadget_strings *tab, u8 id, const char *s) { struct usb_string *str = tab->strings; @@ -960,7 +973,7 @@ string_override_one(struct usb_gadget_strings *tab, u8 id, const char *s) } } -static void __init +static void string_override(struct usb_gadget_strings **tab, u8 id, const char *s) { while (*tab) { @@ -969,7 +982,7 @@ string_override(struct usb_gadget_strings **tab, u8 id, const char *s) } } -static int __init composite_bind(struct usb_gadget *gadget) +static int composite_bind(struct usb_gadget *gadget) { struct usb_composite_dev *cdev; int status = -ENOMEM; @@ -1004,6 +1017,14 @@ static int __init composite_bind(struct usb_gadget *gadget) */ usb_ep_autoconfig_reset(cdev->gadget); + /* standardized runtime overrides for device ID data */ + if (idVendor) + cdev->desc.idVendor = cpu_to_le16(idVendor); + if (idProduct) + cdev->desc.idProduct = cpu_to_le16(idProduct); + if (bcdDevice) + cdev->desc.bcdDevice = cpu_to_le16(bcdDevice); + /* composite gadget needs to assign strings for whole device (like * serial number), register function drivers, potentially update * power state and consumption, etc @@ -1015,14 +1036,6 @@ static int __init composite_bind(struct usb_gadget *gadget) cdev->desc = *composite->dev; cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket; - /* standardized runtime overrides for device ID data */ - if (idVendor) - cdev->desc.idVendor = cpu_to_le16(idVendor); - if (idProduct) - cdev->desc.idProduct = cpu_to_le16(idProduct); - if (bcdDevice) - cdev->desc.bcdDevice = cpu_to_le16(bcdDevice); - /* strings can't be assigned before bind() allocates the * releavnt identifiers */ @@ -1036,6 +1049,10 @@ static int __init composite_bind(struct usb_gadget *gadget) string_override(composite->strings, cdev->desc.iSerialNumber, iSerialNumber); + status = device_create_file(&gadget->dev, &dev_attr_suspended); + if (status) + goto fail; + INFO(cdev, "%s ready\n", composite->name); return 0; @@ -1064,6 +1081,8 @@ composite_suspend(struct usb_gadget *gadget) } if (composite->suspend) composite->suspend(cdev); + + cdev->suspended = 1; } static void @@ -1084,6 +1103,8 @@ composite_resume(struct usb_gadget *gadget) f->resume(f); } } + + cdev->suspended = 0; } /*-------------------------------------------------------------------------*/ @@ -1092,7 +1113,6 @@ static struct usb_gadget_driver composite_driver = { .speed = USB_SPEED_HIGH, .bind = composite_bind, - /* .unbind = __exit_p(composite_unbind), */ .unbind = composite_unbind, .setup = composite_setup, @@ -1121,7 +1141,7 @@ static struct usb_gadget_driver composite_driver = { * while it was binding. That would usually be done in order to wait for * some userspace participation. */ -int __init usb_composite_register(struct usb_composite_driver *driver) +int usb_composite_register(struct usb_composite_driver *driver) { if (!driver || !driver->dev || !driver->bind || composite) return -EINVAL; @@ -1142,7 +1162,7 @@ int __init usb_composite_register(struct usb_composite_driver *driver) * This function is used to unregister drivers using the composite * driver framework. */ -void /* __exit */ usb_composite_unregister(struct usb_composite_driver *driver) +void usb_composite_unregister(struct usb_composite_driver *driver) { if (composite != driver) return; diff --git a/drivers/usb/gadget/config.c b/drivers/usb/gadget/config.c index 47e8e722682..09084fd646a 100644 --- a/drivers/usb/gadget/config.c +++ b/drivers/usb/gadget/config.c @@ -128,7 +128,7 @@ int usb_gadget_config_buf( * with identifiers (for interfaces, strings, endpoints, and more) * as needed by a given function instance. */ -struct usb_descriptor_header **__init +struct usb_descriptor_header ** usb_copy_descriptors(struct usb_descriptor_header **src) { struct usb_descriptor_header **tmp; @@ -175,7 +175,7 @@ usb_copy_descriptors(struct usb_descriptor_header **src) * intended use is to help remembering the endpoint descriptor to use * when enabling a given endpoint. */ -struct usb_endpoint_descriptor *__init +struct usb_endpoint_descriptor * usb_find_endpoint( struct usb_descriptor_header **src, struct usb_descriptor_header **copy, diff --git a/drivers/usb/gadget/dummy_hcd.c b/drivers/usb/gadget/dummy_hcd.c index 5e096648518..4f9e578cde9 100644 --- a/drivers/usb/gadget/dummy_hcd.c +++ b/drivers/usb/gadget/dummy_hcd.c @@ -47,6 +47,7 @@ #include <linux/platform_device.h> #include <linux/usb.h> #include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> #include <asm/byteorder.h> #include <asm/io.h> @@ -55,9 +56,6 @@ #include <asm/unaligned.h> -#include "../core/hcd.h" - - #define DRIVER_DESC "USB Host+Gadget Emulator" #define DRIVER_VERSION "02 May 2005" diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 3568de210f7..8a832488ccd 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -34,12 +34,12 @@ /* we must assign addresses for configurable endpoints (like net2280) */ -static __initdata unsigned epnum; +static unsigned epnum; // #define MANY_ENDPOINTS #ifdef MANY_ENDPOINTS /* more than 15 configurable endpoints */ -static __initdata unsigned in_epnum; +static unsigned in_epnum; #endif @@ -59,7 +59,7 @@ static __initdata unsigned in_epnum; * NOTE: each endpoint is unidirectional, as specified by its USB * descriptor; and isn't specific to a configuration or altsetting. */ -static int __init +static int ep_matches ( struct usb_gadget *gadget, struct usb_ep *ep, @@ -187,7 +187,7 @@ ep_matches ( return 1; } -static struct usb_ep * __init +static struct usb_ep * find_ep (struct usb_gadget *gadget, const char *name) { struct usb_ep *ep; @@ -229,7 +229,7 @@ find_ep (struct usb_gadget *gadget, const char *name) * * On failure, this returns a null endpoint descriptor. */ -struct usb_ep * __init usb_ep_autoconfig ( +struct usb_ep *usb_ep_autoconfig ( struct usb_gadget *gadget, struct usb_endpoint_descriptor *desc ) @@ -304,7 +304,7 @@ struct usb_ep * __init usb_ep_autoconfig ( * state such as ep->driver_data and the record of assigned endpoints * used by usb_ep_autoconfig(). */ -void __init usb_ep_autoconfig_reset (struct usb_gadget *gadget) +void usb_ep_autoconfig_reset (struct usb_gadget *gadget) { struct usb_ep *ep; diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index 400e1ebe697..d47a123f15a 100644 --- a/drivers/usb/gadget/f_acm.c +++ b/drivers/usb/gadget/f_acm.c @@ -116,7 +116,7 @@ acm_iad_descriptor = { }; -static struct usb_interface_descriptor acm_control_interface_desc __initdata = { +static struct usb_interface_descriptor acm_control_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ @@ -127,7 +127,7 @@ static struct usb_interface_descriptor acm_control_interface_desc __initdata = { /* .iInterface = DYNAMIC */ }; -static struct usb_interface_descriptor acm_data_interface_desc __initdata = { +static struct usb_interface_descriptor acm_data_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ @@ -138,7 +138,7 @@ static struct usb_interface_descriptor acm_data_interface_desc __initdata = { /* .iInterface = DYNAMIC */ }; -static struct usb_cdc_header_desc acm_header_desc __initdata = { +static struct usb_cdc_header_desc acm_header_desc = { .bLength = sizeof(acm_header_desc), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_HEADER_TYPE, @@ -146,7 +146,7 @@ static struct usb_cdc_header_desc acm_header_desc __initdata = { }; static struct usb_cdc_call_mgmt_descriptor -acm_call_mgmt_descriptor __initdata = { +acm_call_mgmt_descriptor = { .bLength = sizeof(acm_call_mgmt_descriptor), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, @@ -154,14 +154,14 @@ acm_call_mgmt_descriptor __initdata = { /* .bDataInterface = DYNAMIC */ }; -static struct usb_cdc_acm_descriptor acm_descriptor __initdata = { +static struct usb_cdc_acm_descriptor acm_descriptor = { .bLength = sizeof(acm_descriptor), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_ACM_TYPE, .bmCapabilities = USB_CDC_CAP_LINE, }; -static struct usb_cdc_union_desc acm_union_desc __initdata = { +static struct usb_cdc_union_desc acm_union_desc = { .bLength = sizeof(acm_union_desc), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_UNION_TYPE, @@ -171,7 +171,7 @@ static struct usb_cdc_union_desc acm_union_desc __initdata = { /* full speed support: */ -static struct usb_endpoint_descriptor acm_fs_notify_desc __initdata = { +static struct usb_endpoint_descriptor acm_fs_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, @@ -180,21 +180,21 @@ static struct usb_endpoint_descriptor acm_fs_notify_desc __initdata = { .bInterval = 1 << GS_LOG2_NOTIFY_INTERVAL, }; -static struct usb_endpoint_descriptor acm_fs_in_desc __initdata = { +static struct usb_endpoint_descriptor acm_fs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_endpoint_descriptor acm_fs_out_desc __initdata = { +static struct usb_endpoint_descriptor acm_fs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_descriptor_header *acm_fs_function[] __initdata = { +static struct usb_descriptor_header *acm_fs_function[] = { (struct usb_descriptor_header *) &acm_iad_descriptor, (struct usb_descriptor_header *) &acm_control_interface_desc, (struct usb_descriptor_header *) &acm_header_desc, @@ -210,7 +210,7 @@ static struct usb_descriptor_header *acm_fs_function[] __initdata = { /* high speed support: */ -static struct usb_endpoint_descriptor acm_hs_notify_desc __initdata = { +static struct usb_endpoint_descriptor acm_hs_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, @@ -219,21 +219,21 @@ static struct usb_endpoint_descriptor acm_hs_notify_desc __initdata = { .bInterval = GS_LOG2_NOTIFY_INTERVAL+4, }; -static struct usb_endpoint_descriptor acm_hs_in_desc __initdata = { +static struct usb_endpoint_descriptor acm_hs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_endpoint_descriptor acm_hs_out_desc __initdata = { +static struct usb_endpoint_descriptor acm_hs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_descriptor_header *acm_hs_function[] __initdata = { +static struct usb_descriptor_header *acm_hs_function[] = { (struct usb_descriptor_header *) &acm_iad_descriptor, (struct usb_descriptor_header *) &acm_control_interface_desc, (struct usb_descriptor_header *) &acm_header_desc, @@ -571,7 +571,7 @@ static int acm_send_break(struct gserial *port, int duration) /*-------------------------------------------------------------------------*/ /* ACM function driver setup/binding */ -static int __init +static int acm_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; @@ -719,7 +719,7 @@ static inline bool can_support_cdc(struct usb_configuration *c) * handle all the ones it binds. Caller is also responsible * for calling @gserial_cleanup() before module unload. */ -int __init acm_bind_config(struct usb_configuration *c, u8 port_num) +int acm_bind_config(struct usb_configuration *c, u8 port_num) { struct f_acm *acm; int status; diff --git a/drivers/usb/gadget/f_ecm.c b/drivers/usb/gadget/f_ecm.c index 4e595324c61..544257a89ed 100644 --- a/drivers/usb/gadget/f_ecm.c +++ b/drivers/usb/gadget/f_ecm.c @@ -113,7 +113,7 @@ static inline unsigned ecm_bitrate(struct usb_gadget *g) /* interface descriptor: */ -static struct usb_interface_descriptor ecm_control_intf __initdata = { +static struct usb_interface_descriptor ecm_control_intf = { .bLength = sizeof ecm_control_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -126,7 +126,7 @@ static struct usb_interface_descriptor ecm_control_intf __initdata = { /* .iInterface = DYNAMIC */ }; -static struct usb_cdc_header_desc ecm_header_desc __initdata = { +static struct usb_cdc_header_desc ecm_header_desc = { .bLength = sizeof ecm_header_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_HEADER_TYPE, @@ -134,7 +134,7 @@ static struct usb_cdc_header_desc ecm_header_desc __initdata = { .bcdCDC = cpu_to_le16(0x0110), }; -static struct usb_cdc_union_desc ecm_union_desc __initdata = { +static struct usb_cdc_union_desc ecm_union_desc = { .bLength = sizeof(ecm_union_desc), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_UNION_TYPE, @@ -142,7 +142,7 @@ static struct usb_cdc_union_desc ecm_union_desc __initdata = { /* .bSlaveInterface0 = DYNAMIC */ }; -static struct usb_cdc_ether_desc ecm_desc __initdata = { +static struct usb_cdc_ether_desc ecm_desc = { .bLength = sizeof ecm_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, @@ -157,7 +157,7 @@ static struct usb_cdc_ether_desc ecm_desc __initdata = { /* the default data interface has no endpoints ... */ -static struct usb_interface_descriptor ecm_data_nop_intf __initdata = { +static struct usb_interface_descriptor ecm_data_nop_intf = { .bLength = sizeof ecm_data_nop_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -172,7 +172,7 @@ static struct usb_interface_descriptor ecm_data_nop_intf __initdata = { /* ... but the "real" data interface has two bulk endpoints */ -static struct usb_interface_descriptor ecm_data_intf __initdata = { +static struct usb_interface_descriptor ecm_data_intf = { .bLength = sizeof ecm_data_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -187,7 +187,7 @@ static struct usb_interface_descriptor ecm_data_intf __initdata = { /* full speed support: */ -static struct usb_endpoint_descriptor fs_ecm_notify_desc __initdata = { +static struct usb_endpoint_descriptor fs_ecm_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -197,7 +197,7 @@ static struct usb_endpoint_descriptor fs_ecm_notify_desc __initdata = { .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, }; -static struct usb_endpoint_descriptor fs_ecm_in_desc __initdata = { +static struct usb_endpoint_descriptor fs_ecm_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -205,7 +205,7 @@ static struct usb_endpoint_descriptor fs_ecm_in_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_endpoint_descriptor fs_ecm_out_desc __initdata = { +static struct usb_endpoint_descriptor fs_ecm_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -213,7 +213,7 @@ static struct usb_endpoint_descriptor fs_ecm_out_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_descriptor_header *ecm_fs_function[] __initdata = { +static struct usb_descriptor_header *ecm_fs_function[] = { /* CDC ECM control descriptors */ (struct usb_descriptor_header *) &ecm_control_intf, (struct usb_descriptor_header *) &ecm_header_desc, @@ -231,7 +231,7 @@ static struct usb_descriptor_header *ecm_fs_function[] __initdata = { /* high speed support: */ -static struct usb_endpoint_descriptor hs_ecm_notify_desc __initdata = { +static struct usb_endpoint_descriptor hs_ecm_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -240,7 +240,7 @@ static struct usb_endpoint_descriptor hs_ecm_notify_desc __initdata = { .wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT), .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, }; -static struct usb_endpoint_descriptor hs_ecm_in_desc __initdata = { +static struct usb_endpoint_descriptor hs_ecm_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -249,7 +249,7 @@ static struct usb_endpoint_descriptor hs_ecm_in_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_endpoint_descriptor hs_ecm_out_desc __initdata = { +static struct usb_endpoint_descriptor hs_ecm_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -258,7 +258,7 @@ static struct usb_endpoint_descriptor hs_ecm_out_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_descriptor_header *ecm_hs_function[] __initdata = { +static struct usb_descriptor_header *ecm_hs_function[] = { /* CDC ECM control descriptors */ (struct usb_descriptor_header *) &ecm_control_intf, (struct usb_descriptor_header *) &ecm_header_desc, @@ -597,7 +597,7 @@ static void ecm_close(struct gether *geth) /* ethernet function driver setup/binding */ -static int __init +static int ecm_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; @@ -763,7 +763,8 @@ ecm_unbind(struct usb_configuration *c, struct usb_function *f) * Caller must have called @gether_setup(). Caller is also responsible * for calling @gether_cleanup() before module unload. */ -int __init ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +int +ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) { struct f_ecm *ecm; int status; diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c new file mode 100644 index 00000000000..d69eccf5f19 --- /dev/null +++ b/drivers/usb/gadget/f_fs.c @@ -0,0 +1,2442 @@ +/* + * f_fs.c -- user mode filesystem api for usb composite funtcion controllers + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz <m.nazarewicz@samsung.com> + * + * Based on inode.c (GadgetFS): + * Copyright (C) 2003-2004 David Brownell + * Copyright (C) 2003 Agilent Technologies + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/blkdev.h> +#include <linux/pagemap.h> +#include <asm/unaligned.h> +#include <linux/smp_lock.h> + +#include <linux/usb/composite.h> +#include <linux/usb/functionfs.h> + + +#define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */ + + +/* Debuging *****************************************************************/ + +#define ffs_printk(level, fmt, args...) printk(level "f_fs: " fmt "\n", ## args) + +#define FERR(...) ffs_printk(KERN_ERR, __VA_ARGS__) +#define FINFO(...) ffs_printk(KERN_INFO, __VA_ARGS__) + +#ifdef DEBUG +# define FDBG(...) ffs_printk(KERN_DEBUG, __VA_ARGS__) +#else +# define FDBG(...) do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +# define FVDBG FDBG +#else +# define FVDBG(...) do { } while (0) +#endif /* VERBOSE_DEBUG */ + +#define ENTER() FVDBG("%s()", __func__) + +#ifdef VERBOSE_DEBUG +# define ffs_dump_mem(prefix, ptr, len) \ + print_hex_dump_bytes("f_fs" prefix ": ", DUMP_PREFIX_NONE, ptr, len) +#else +# define ffs_dump_mem(prefix, ptr, len) do { } while (0) +#endif + + +/* The data structure and setup file ****************************************/ + +enum ffs_state { + /* Waiting for descriptors and strings. */ + /* In this state no open(2), read(2) or write(2) on epfiles + * may succeed (which should not be the problem as there + * should be no such files opened in the firts place). */ + FFS_READ_DESCRIPTORS, + FFS_READ_STRINGS, + + /* We've got descriptors and strings. We are or have called + * functionfs_ready_callback(). functionfs_bind() may have + * been called but we don't know. */ + /* This is the only state in which operations on epfiles may + * succeed. */ + FFS_ACTIVE, + + /* All endpoints have been closed. This state is also set if + * we encounter an unrecoverable error. The only + * unrecoverable error is situation when after reading strings + * from user space we fail to initialise EP files or + * functionfs_ready_callback() returns with error (<0). */ + /* In this state no open(2), read(2) or write(2) (both on ep0 + * as well as epfile) may succeed (at this point epfiles are + * unlinked and all closed so this is not a problem; ep0 is + * also closed but ep0 file exists and so open(2) on ep0 must + * fail). */ + FFS_CLOSING +}; + + +enum ffs_setup_state { + /* There is no setup request pending. */ + FFS_NO_SETUP, + /* User has read events and there was a setup request event + * there. The next read/write on ep0 will handle the + * request. */ + FFS_SETUP_PENDING, + /* There was event pending but before user space handled it + * some other event was introduced which canceled existing + * setup. If this state is set read/write on ep0 return + * -EIDRM. This state is only set when adding event. */ + FFS_SETUP_CANCELED +}; + + + +struct ffs_epfile; +struct ffs_function; + +struct ffs_data { + struct usb_gadget *gadget; + + /* Protect access read/write operations, only one read/write + * at a time. As a consequence protects ep0req and company. + * While setup request is being processed (queued) this is + * held. */ + struct mutex mutex; + + /* Protect access to enpoint related structures (basically + * usb_ep_queue(), usb_ep_dequeue(), etc. calls) except for + * endpint zero. */ + spinlock_t eps_lock; + + /* XXX REVISIT do we need our own request? Since we are not + * handling setup requests immidiatelly user space may be so + * slow that another setup will be sent to the gadget but this + * time not to us but another function and then there could be + * a race. Is taht the case? Or maybe we can use cdev->req + * after all, maybe we just need some spinlock for that? */ + struct usb_request *ep0req; /* P: mutex */ + struct completion ep0req_completion; /* P: mutex */ + int ep0req_status; /* P: mutex */ + + /* reference counter */ + atomic_t ref; + /* how many files are opened (EP0 and others) */ + atomic_t opened; + + /* EP0 state */ + enum ffs_state state; + + /* + * Possible transations: + * + FFS_NO_SETUP -> FFS_SETUP_PENDING -- P: ev.waitq.lock + * happens only in ep0 read which is P: mutex + * + FFS_SETUP_PENDING -> FFS_NO_SETUP -- P: ev.waitq.lock + * happens only in ep0 i/o which is P: mutex + * + FFS_SETUP_PENDING -> FFS_SETUP_CANCELED -- P: ev.waitq.lock + * + FFS_SETUP_CANCELED -> FFS_NO_SETUP -- cmpxchg + */ + enum ffs_setup_state setup_state; + +#define FFS_SETUP_STATE(ffs) \ + ((enum ffs_setup_state)cmpxchg(&(ffs)->setup_state, \ + FFS_SETUP_CANCELED, FFS_NO_SETUP)) + + /* Events & such. */ + struct { + u8 types[4]; + unsigned short count; + /* XXX REVISIT need to update it in some places, or do we? */ + unsigned short can_stall; + struct usb_ctrlrequest setup; + + wait_queue_head_t waitq; + } ev; /* the whole structure, P: ev.waitq.lock */ + + /* Flags */ + unsigned long flags; +#define FFS_FL_CALL_CLOSED_CALLBACK 0 +#define FFS_FL_BOUND 1 + + /* Active function */ + struct ffs_function *func; + + /* Device name, write once when file system is mounted. + * Intendet for user to read if she wants. */ + const char *dev_name; + /* Private data for our user (ie. gadget). Managed by + * user. */ + void *private_data; + + /* filled by __ffs_data_got_descs() */ + /* real descriptors are 16 bytes after raw_descs (so you need + * to skip 16 bytes (ie. ffs->raw_descs + 16) to get to the + * first full speed descriptor). raw_descs_length and + * raw_fs_descs_length do not have those 16 bytes added. */ + const void *raw_descs; + unsigned raw_descs_length; + unsigned raw_fs_descs_length; + unsigned fs_descs_count; + unsigned hs_descs_count; + + unsigned short strings_count; + unsigned short interfaces_count; + unsigned short eps_count; + unsigned short _pad1; + + /* filled by __ffs_data_got_strings() */ + /* ids in stringtabs are set in functionfs_bind() */ + const void *raw_strings; + struct usb_gadget_strings **stringtabs; + + /* File system's super block, write once when file system is mounted. */ + struct super_block *sb; + + /* File permissions, written once when fs is mounted*/ + struct ffs_file_perms { + umode_t mode; + uid_t uid; + gid_t gid; + } file_perms; + + /* The endpoint files, filled by ffs_epfiles_create(), + * destroyed by ffs_epfiles_destroy(). */ + struct ffs_epfile *epfiles; +}; + +/* Reference counter handling */ +static void ffs_data_get(struct ffs_data *ffs); +static void ffs_data_put(struct ffs_data *ffs); +/* Creates new ffs_data object. */ +static struct ffs_data *__must_check ffs_data_new(void) __attribute__((malloc)); + +/* Opened counter handling. */ +static void ffs_data_opened(struct ffs_data *ffs); +static void ffs_data_closed(struct ffs_data *ffs); + +/* Called with ffs->mutex held; take over ownerrship of data. */ +static int __must_check +__ffs_data_got_descs(struct ffs_data *ffs, char *data, size_t len); +static int __must_check +__ffs_data_got_strings(struct ffs_data *ffs, char *data, size_t len); + + +/* The function structure ***************************************************/ + +struct ffs_ep; + +struct ffs_function { + struct usb_configuration *conf; + struct usb_gadget *gadget; + struct ffs_data *ffs; + + struct ffs_ep *eps; + u8 eps_revmap[16]; + short *interfaces_nums; + + struct usb_function function; +}; + + +static struct ffs_function *ffs_func_from_usb(struct usb_function *f) +{ + return container_of(f, struct ffs_function, function); +} + +static void ffs_func_free(struct ffs_function *func); + + +static void ffs_func_eps_disable(struct ffs_function *func); +static int __must_check ffs_func_eps_enable(struct ffs_function *func); + + +static int ffs_func_bind(struct usb_configuration *, + struct usb_function *); +static void ffs_func_unbind(struct usb_configuration *, + struct usb_function *); +static int ffs_func_set_alt(struct usb_function *, unsigned, unsigned); +static void ffs_func_disable(struct usb_function *); +static int ffs_func_setup(struct usb_function *, + const struct usb_ctrlrequest *); +static void ffs_func_suspend(struct usb_function *); +static void ffs_func_resume(struct usb_function *); + + +static int ffs_func_revmap_ep(struct ffs_function *func, u8 num); +static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf); + + + +/* The endpoints structures *************************************************/ + +struct ffs_ep { + struct usb_ep *ep; /* P: ffs->eps_lock */ + struct usb_request *req; /* P: epfile->mutex */ + + /* [0]: full speed, [1]: high speed */ + struct usb_endpoint_descriptor *descs[2]; + + u8 num; + + int status; /* P: epfile->mutex */ +}; + +struct ffs_epfile { + /* Protects ep->ep and ep->req. */ + struct mutex mutex; + wait_queue_head_t wait; + + struct ffs_data *ffs; + struct ffs_ep *ep; /* P: ffs->eps_lock */ + + struct dentry *dentry; + + char name[5]; + + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + + unsigned char _pad; +}; + + +static int __must_check ffs_epfiles_create(struct ffs_data *ffs); +static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count); + +static struct inode *__must_check +ffs_sb_create_file(struct super_block *sb, const char *name, void *data, + const struct file_operations *fops, + struct dentry **dentry_p); + + +/* Misc helper functions ****************************************************/ + +static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock) + __attribute__((warn_unused_result, nonnull)); +static char *ffs_prepare_buffer(const char * __user buf, size_t len) + __attribute__((warn_unused_result, nonnull)); + + +/* Control file aka ep0 *****************************************************/ + +static void ffs_ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct ffs_data *ffs = req->context; + + complete_all(&ffs->ep0req_completion); +} + + +static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len) +{ + struct usb_request *req = ffs->ep0req; + int ret; + + req->zero = len < le16_to_cpu(ffs->ev.setup.wLength); + + spin_unlock_irq(&ffs->ev.waitq.lock); + + req->buf = data; + req->length = len; + + INIT_COMPLETION(ffs->ep0req_completion); + + ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC); + if (unlikely(ret < 0)) + return ret; + + ret = wait_for_completion_interruptible(&ffs->ep0req_completion); + if (unlikely(ret)) { + usb_ep_dequeue(ffs->gadget->ep0, req); + return -EINTR; + } + + ffs->setup_state = FFS_NO_SETUP; + return ffs->ep0req_status; +} + +static int __ffs_ep0_stall(struct ffs_data *ffs) +{ + if (ffs->ev.can_stall) { + FVDBG("ep0 stall\n"); + usb_ep_set_halt(ffs->gadget->ep0); + ffs->setup_state = FFS_NO_SETUP; + return -EL2HLT; + } else { + FDBG("bogus ep0 stall!\n"); + return -ESRCH; + } +} + + +static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, + size_t len, loff_t *ptr) +{ + struct ffs_data *ffs = file->private_data; + ssize_t ret; + char *data; + + ENTER(); + + /* Fast check if setup was canceled */ + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) + return -EIDRM; + + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (unlikely(ret < 0)) + return ret; + + + /* Check state */ + switch (ffs->state) { + case FFS_READ_DESCRIPTORS: + case FFS_READ_STRINGS: + /* Copy data */ + if (unlikely(len < 16)) { + ret = -EINVAL; + break; + } + + data = ffs_prepare_buffer(buf, len); + if (unlikely(IS_ERR(data))) { + ret = PTR_ERR(data); + break; + } + + /* Handle data */ + if (ffs->state == FFS_READ_DESCRIPTORS) { + FINFO("read descriptors"); + ret = __ffs_data_got_descs(ffs, data, len); + if (unlikely(ret < 0)) + break; + + ffs->state = FFS_READ_STRINGS; + ret = len; + } else { + FINFO("read strings"); + ret = __ffs_data_got_strings(ffs, data, len); + if (unlikely(ret < 0)) + break; + + ret = ffs_epfiles_create(ffs); + if (unlikely(ret)) { + ffs->state = FFS_CLOSING; + break; + } + + ffs->state = FFS_ACTIVE; + mutex_unlock(&ffs->mutex); + + ret = functionfs_ready_callback(ffs); + if (unlikely(ret < 0)) { + ffs->state = FFS_CLOSING; + return ret; + } + + set_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags); + return len; + } + break; + + + case FFS_ACTIVE: + data = NULL; + /* We're called from user space, we can use _irq + * rather then _irqsave */ + spin_lock_irq(&ffs->ev.waitq.lock); + switch (FFS_SETUP_STATE(ffs)) { + case FFS_SETUP_CANCELED: + ret = -EIDRM; + goto done_spin; + + case FFS_NO_SETUP: + ret = -ESRCH; + goto done_spin; + + case FFS_SETUP_PENDING: + break; + } + + /* FFS_SETUP_PENDING */ + if (!(ffs->ev.setup.bRequestType & USB_DIR_IN)) { + spin_unlock_irq(&ffs->ev.waitq.lock); + ret = __ffs_ep0_stall(ffs); + break; + } + + /* FFS_SETUP_PENDING and not stall */ + len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength)); + + spin_unlock_irq(&ffs->ev.waitq.lock); + + data = ffs_prepare_buffer(buf, len); + if (unlikely(IS_ERR(data))) { + ret = PTR_ERR(data); + break; + } + + spin_lock_irq(&ffs->ev.waitq.lock); + + /* We are guaranteed to be still in FFS_ACTIVE state + * but the state of setup could have changed from + * FFS_SETUP_PENDING to FFS_SETUP_CANCELED so we need + * to check for that. If that happened we copied data + * from user space in vain but it's unlikely. */ + /* For sure we are not in FFS_NO_SETUP since this is + * the only place FFS_SETUP_PENDING -> FFS_NO_SETUP + * transition can be performed and it's protected by + * mutex. */ + + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) { + ret = -EIDRM; +done_spin: + spin_unlock_irq(&ffs->ev.waitq.lock); + } else { + /* unlocks spinlock */ + ret = __ffs_ep0_queue_wait(ffs, data, len); + } + kfree(data); + break; + + + default: + ret = -EBADFD; + break; + } + + + mutex_unlock(&ffs->mutex); + return ret; +} + + + +static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, + size_t n) +{ + /* We are holding ffs->ev.waitq.lock and ffs->mutex and we need + * to release them. */ + + struct usb_functionfs_event events[n]; + unsigned i = 0; + + memset(events, 0, sizeof events); + + do { + events[i].type = ffs->ev.types[i]; + if (events[i].type == FUNCTIONFS_SETUP) { + events[i].u.setup = ffs->ev.setup; + ffs->setup_state = FFS_SETUP_PENDING; + } + } while (++i < n); + + if (n < ffs->ev.count) { + ffs->ev.count -= n; + memmove(ffs->ev.types, ffs->ev.types + n, + ffs->ev.count * sizeof *ffs->ev.types); + } else { + ffs->ev.count = 0; + } + + spin_unlock_irq(&ffs->ev.waitq.lock); + mutex_unlock(&ffs->mutex); + + return unlikely(__copy_to_user(buf, events, sizeof events)) + ? -EFAULT : sizeof events; +} + + +static ssize_t ffs_ep0_read(struct file *file, char __user *buf, + size_t len, loff_t *ptr) +{ + struct ffs_data *ffs = file->private_data; + char *data = NULL; + size_t n; + int ret; + + ENTER(); + + /* Fast check if setup was canceled */ + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) + return -EIDRM; + + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (unlikely(ret < 0)) + return ret; + + + /* Check state */ + if (ffs->state != FFS_ACTIVE) { + ret = -EBADFD; + goto done_mutex; + } + + + /* We're called from user space, we can use _irq rather then + * _irqsave */ + spin_lock_irq(&ffs->ev.waitq.lock); + + switch (FFS_SETUP_STATE(ffs)) { + case FFS_SETUP_CANCELED: + ret = -EIDRM; + break; + + case FFS_NO_SETUP: + n = len / sizeof(struct usb_functionfs_event); + if (unlikely(!n)) { + ret = -EINVAL; + break; + } + + if ((file->f_flags & O_NONBLOCK) && !ffs->ev.count) { + ret = -EAGAIN; + break; + } + + if (unlikely(wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq, ffs->ev.count))) { + ret = -EINTR; + break; + } + + return __ffs_ep0_read_events(ffs, buf, + min(n, (size_t)ffs->ev.count)); + + + case FFS_SETUP_PENDING: + if (ffs->ev.setup.bRequestType & USB_DIR_IN) { + spin_unlock_irq(&ffs->ev.waitq.lock); + ret = __ffs_ep0_stall(ffs); + goto done_mutex; + } + + len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength)); + + spin_unlock_irq(&ffs->ev.waitq.lock); + + if (likely(len)) { + data = kmalloc(len, GFP_KERNEL); + if (unlikely(!data)) { + ret = -ENOMEM; + goto done_mutex; + } + } + + spin_lock_irq(&ffs->ev.waitq.lock); + + /* See ffs_ep0_write() */ + if (FFS_SETUP_STATE(ffs) == FFS_SETUP_CANCELED) { + ret = -EIDRM; + break; + } + + /* unlocks spinlock */ + ret = __ffs_ep0_queue_wait(ffs, data, len); + if (likely(ret > 0) && unlikely(__copy_to_user(buf, data, len))) + ret = -EFAULT; + goto done_mutex; + + default: + ret = -EBADFD; + break; + } + + spin_unlock_irq(&ffs->ev.waitq.lock); +done_mutex: + mutex_unlock(&ffs->mutex); + kfree(data); + return ret; +} + + + +static int ffs_ep0_open(struct inode *inode, struct file *file) +{ + struct ffs_data *ffs = inode->i_private; + + ENTER(); + + if (unlikely(ffs->state == FFS_CLOSING)) + return -EBUSY; + + file->private_data = ffs; + ffs_data_opened(ffs); + + return 0; +} + + +static int ffs_ep0_release(struct inode *inode, struct file *file) +{ + struct ffs_data *ffs = file->private_data; + + ENTER(); + + ffs_data_closed(ffs); + + return 0; +} + + +static long ffs_ep0_ioctl(struct file *file, unsigned code, unsigned long value) +{ + struct ffs_data *ffs = file->private_data; + struct usb_gadget *gadget = ffs->gadget; + long ret; + + ENTER(); + + if (code == FUNCTIONFS_INTERFACE_REVMAP) { + struct ffs_function *func = ffs->func; + ret = func ? ffs_func_revmap_intf(func, value) : -ENODEV; + } else if (gadget->ops->ioctl) { + lock_kernel(); + ret = gadget->ops->ioctl(gadget, code, value); + unlock_kernel(); + } else { + ret = -ENOTTY; + } + + return ret; +} + + +static const struct file_operations ffs_ep0_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .open = ffs_ep0_open, + .write = ffs_ep0_write, + .read = ffs_ep0_read, + .release = ffs_ep0_release, + .unlocked_ioctl = ffs_ep0_ioctl, +}; + + +/* "Normal" endpoints operations ********************************************/ + + +static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) +{ + ENTER(); + if (likely(req->context)) { + struct ffs_ep *ep = _ep->driver_data; + ep->status = req->status ? req->status : req->actual; + complete(req->context); + } +} + + +static ssize_t ffs_epfile_io(struct file *file, + char __user *buf, size_t len, int read) +{ + struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + char *data = NULL; + ssize_t ret; + int halt; + + goto first_try; + do { + spin_unlock_irq(&epfile->ffs->eps_lock); + mutex_unlock(&epfile->mutex); + +first_try: + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) { + ret = -ENODEV; + goto error; + } + + /* Wait for endpoint to be enabled */ + ep = epfile->ep; + if (!ep) { + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto error; + } + + if (unlikely(wait_event_interruptible + (epfile->wait, (ep = epfile->ep)))) { + ret = -EINTR; + goto error; + } + } + + /* Do we halt? */ + halt = !read == !epfile->in; + if (halt && epfile->isoc) { + ret = -EINVAL; + goto error; + } + + /* Allocate & copy */ + if (!halt && !data) { + data = kzalloc(len, GFP_KERNEL); + if (unlikely(!data)) + return -ENOMEM; + + if (!read && + unlikely(__copy_from_user(data, buf, len))) { + ret = -EFAULT; + goto error; + } + } + + /* We will be using request */ + ret = ffs_mutex_lock(&epfile->mutex, + file->f_flags & O_NONBLOCK); + if (unlikely(ret)) + goto error; + + /* We're called from user space, we can use _irq rather then + * _irqsave */ + spin_lock_irq(&epfile->ffs->eps_lock); + + /* While we were acquiring mutex endpoint got disabled + * or changed? */ + } while (unlikely(epfile->ep != ep)); + + /* Halt */ + if (unlikely(halt)) { + if (likely(epfile->ep == ep) && !WARN_ON(!ep->ep)) + usb_ep_set_halt(ep->ep); + spin_unlock_irq(&epfile->ffs->eps_lock); + ret = -EBADMSG; + } else { + /* Fire the request */ + DECLARE_COMPLETION_ONSTACK(done); + + struct usb_request *req = ep->req; + req->context = &done; + req->complete = ffs_epfile_io_complete; + req->buf = data; + req->length = len; + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + + spin_unlock_irq(&epfile->ffs->eps_lock); + + if (unlikely(ret < 0)) { + /* nop */ + } else if (unlikely(wait_for_completion_interruptible(&done))) { + ret = -EINTR; + usb_ep_dequeue(ep->ep, req); + } else { + ret = ep->status; + if (read && ret > 0 && + unlikely(copy_to_user(buf, data, ret))) + ret = -EFAULT; + } + } + + mutex_unlock(&epfile->mutex); +error: + kfree(data); + return ret; +} + + +static ssize_t +ffs_epfile_write(struct file *file, const char __user *buf, size_t len, + loff_t *ptr) +{ + ENTER(); + + return ffs_epfile_io(file, (char __user *)buf, len, 0); +} + +static ssize_t +ffs_epfile_read(struct file *file, char __user *buf, size_t len, loff_t *ptr) +{ + ENTER(); + + return ffs_epfile_io(file, buf, len, 1); +} + +static int +ffs_epfile_open(struct inode *inode, struct file *file) +{ + struct ffs_epfile *epfile = inode->i_private; + + ENTER(); + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + file->private_data = epfile; + ffs_data_opened(epfile->ffs); + + return 0; +} + +static int +ffs_epfile_release(struct inode *inode, struct file *file) +{ + struct ffs_epfile *epfile = inode->i_private; + + ENTER(); + + ffs_data_closed(epfile->ffs); + + return 0; +} + + +static long ffs_epfile_ioctl(struct file *file, unsigned code, + unsigned long value) +{ + struct ffs_epfile *epfile = file->private_data; + int ret; + + ENTER(); + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + spin_lock_irq(&epfile->ffs->eps_lock); + if (likely(epfile->ep)) { + switch (code) { + case FUNCTIONFS_FIFO_STATUS: + ret = usb_ep_fifo_status(epfile->ep->ep); + break; + case FUNCTIONFS_FIFO_FLUSH: + usb_ep_fifo_flush(epfile->ep->ep); + ret = 0; + break; + case FUNCTIONFS_CLEAR_HALT: + ret = usb_ep_clear_halt(epfile->ep->ep); + break; + case FUNCTIONFS_ENDPOINT_REVMAP: + ret = epfile->ep->num; + break; + default: + ret = -ENOTTY; + } + } else { + ret = -ENODEV; + } + spin_unlock_irq(&epfile->ffs->eps_lock); + + return ret; +} + + +static const struct file_operations ffs_epfile_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .open = ffs_epfile_open, + .write = ffs_epfile_write, + .read = ffs_epfile_read, + .release = ffs_epfile_release, + .unlocked_ioctl = ffs_epfile_ioctl, +}; + + + +/* File system and super block operations ***********************************/ + +/* + * Mounting the filesystem creates a controller file, used first for + * function configuration then later for event monitoring. + */ + + +static struct inode *__must_check +ffs_sb_make_inode(struct super_block *sb, void *data, + const struct file_operations *fops, + const struct inode_operations *iops, + struct ffs_file_perms *perms) +{ + struct inode *inode; + + ENTER(); + + inode = new_inode(sb); + + if (likely(inode)) { + struct timespec current_time = CURRENT_TIME; + + inode->i_mode = perms->mode; + inode->i_uid = perms->uid; + inode->i_gid = perms->gid; + inode->i_atime = current_time; + inode->i_mtime = current_time; + inode->i_ctime = current_time; + inode->i_private = data; + if (fops) + inode->i_fop = fops; + if (iops) + inode->i_op = iops; + } + + return inode; +} + + +/* Create "regular" file */ + +static struct inode *ffs_sb_create_file(struct super_block *sb, + const char *name, void *data, + const struct file_operations *fops, + struct dentry **dentry_p) +{ + struct ffs_data *ffs = sb->s_fs_info; + struct dentry *dentry; + struct inode *inode; + + ENTER(); + + dentry = d_alloc_name(sb->s_root, name); + if (unlikely(!dentry)) + return NULL; + + inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms); + if (unlikely(!inode)) { + dput(dentry); + return NULL; + } + + d_add(dentry, inode); + if (dentry_p) + *dentry_p = dentry; + + return inode; +} + + +/* Super block */ + +static const struct super_operations ffs_sb_operations = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, +}; + +struct ffs_sb_fill_data { + struct ffs_file_perms perms; + umode_t root_mode; + const char *dev_name; +}; + +static int ffs_sb_fill(struct super_block *sb, void *_data, int silent) +{ + struct ffs_sb_fill_data *data = _data; + struct inode *inode; + struct dentry *d; + struct ffs_data *ffs; + + ENTER(); + + /* Initialize data */ + ffs = ffs_data_new(); + if (unlikely(!ffs)) + goto enomem0; + + ffs->sb = sb; + ffs->dev_name = data->dev_name; + ffs->file_perms = data->perms; + + sb->s_fs_info = ffs; + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = FUNCTIONFS_MAGIC; + sb->s_op = &ffs_sb_operations; + sb->s_time_gran = 1; + + /* Root inode */ + data->perms.mode = data->root_mode; + inode = ffs_sb_make_inode(sb, NULL, + &simple_dir_operations, + &simple_dir_inode_operations, + &data->perms); + if (unlikely(!inode)) + goto enomem1; + d = d_alloc_root(inode); + if (unlikely(!d)) + goto enomem2; + sb->s_root = d; + + /* EP0 file */ + if (unlikely(!ffs_sb_create_file(sb, "ep0", ffs, + &ffs_ep0_operations, NULL))) + goto enomem3; + + return 0; + +enomem3: + dput(d); +enomem2: + iput(inode); +enomem1: + ffs_data_put(ffs); +enomem0: + return -ENOMEM; +} + + +static int ffs_fs_parse_opts(struct ffs_sb_fill_data *data, char *opts) +{ + ENTER(); + + if (!opts || !*opts) + return 0; + + for (;;) { + char *end, *eq, *comma; + unsigned long value; + + /* Option limit */ + comma = strchr(opts, ','); + if (comma) + *comma = 0; + + /* Value limit */ + eq = strchr(opts, '='); + if (unlikely(!eq)) { + FERR("'=' missing in %s", opts); + return -EINVAL; + } + *eq = 0; + + /* Parse value */ + value = simple_strtoul(eq + 1, &end, 0); + if (unlikely(*end != ',' && *end != 0)) { + FERR("%s: invalid value: %s", opts, eq + 1); + return -EINVAL; + } + + /* Interpret option */ + switch (eq - opts) { + case 5: + if (!memcmp(opts, "rmode", 5)) + data->root_mode = (value & 0555) | S_IFDIR; + else if (!memcmp(opts, "fmode", 5)) + data->perms.mode = (value & 0666) | S_IFREG; + else + goto invalid; + break; + + case 4: + if (!memcmp(opts, "mode", 4)) { + data->root_mode = (value & 0555) | S_IFDIR; + data->perms.mode = (value & 0666) | S_IFREG; + } else { + goto invalid; + } + break; + + case 3: + if (!memcmp(opts, "uid", 3)) + data->perms.uid = value; + else if (!memcmp(opts, "gid", 3)) + data->perms.gid = value; + else + goto invalid; + break; + + default: +invalid: + FERR("%s: invalid option", opts); + return -EINVAL; + } + + /* Next iteration */ + if (!comma) + break; + opts = comma + 1; + } + + return 0; +} + + +/* "mount -t functionfs dev_name /dev/function" ends up here */ + +static int +ffs_fs_get_sb(struct file_system_type *t, int flags, + const char *dev_name, void *opts, struct vfsmount *mnt) +{ + struct ffs_sb_fill_data data = { + .perms = { + .mode = S_IFREG | 0600, + .uid = 0, + .gid = 0 + }, + .root_mode = S_IFDIR | 0500, + }; + int ret; + + ENTER(); + + ret = functionfs_check_dev_callback(dev_name); + if (unlikely(ret < 0)) + return ret; + + ret = ffs_fs_parse_opts(&data, opts); + if (unlikely(ret < 0)) + return ret; + + data.dev_name = dev_name; + return get_sb_single(t, flags, &data, ffs_sb_fill, mnt); +} + +static void +ffs_fs_kill_sb(struct super_block *sb) +{ + void *ptr; + + ENTER(); + + kill_litter_super(sb); + ptr = xchg(&sb->s_fs_info, NULL); + if (ptr) + ffs_data_put(ptr); +} + +static struct file_system_type ffs_fs_type = { + .owner = THIS_MODULE, + .name = "functionfs", + .get_sb = ffs_fs_get_sb, + .kill_sb = ffs_fs_kill_sb, +}; + + + +/* Driver's main init/cleanup functions *************************************/ + + +static int functionfs_init(void) +{ + int ret; + + ENTER(); + + ret = register_filesystem(&ffs_fs_type); + if (likely(!ret)) + FINFO("file system registered"); + else + FERR("failed registering file system (%d)", ret); + + return ret; +} + +static void functionfs_cleanup(void) +{ + ENTER(); + + FINFO("unloading"); + unregister_filesystem(&ffs_fs_type); +} + + + +/* ffs_data and ffs_function construction and destruction code **************/ + +static void ffs_data_clear(struct ffs_data *ffs); +static void ffs_data_reset(struct ffs_data *ffs); + + +static void ffs_data_get(struct ffs_data *ffs) +{ + ENTER(); + + atomic_inc(&ffs->ref); +} + +static void ffs_data_opened(struct ffs_data *ffs) +{ + ENTER(); + + atomic_inc(&ffs->ref); + atomic_inc(&ffs->opened); +} + +static void ffs_data_put(struct ffs_data *ffs) +{ + ENTER(); + + if (unlikely(atomic_dec_and_test(&ffs->ref))) { + FINFO("%s(): freeing", __func__); + ffs_data_clear(ffs); + BUG_ON(mutex_is_locked(&ffs->mutex) || + spin_is_locked(&ffs->ev.waitq.lock) || + waitqueue_active(&ffs->ev.waitq) || + waitqueue_active(&ffs->ep0req_completion.wait)); + kfree(ffs); + } +} + + + +static void ffs_data_closed(struct ffs_data *ffs) +{ + ENTER(); + + if (atomic_dec_and_test(&ffs->opened)) { + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + } + + ffs_data_put(ffs); +} + + +static struct ffs_data *ffs_data_new(void) +{ + struct ffs_data *ffs = kzalloc(sizeof *ffs, GFP_KERNEL); + if (unlikely(!ffs)) + return 0; + + ENTER(); + + atomic_set(&ffs->ref, 1); + atomic_set(&ffs->opened, 0); + ffs->state = FFS_READ_DESCRIPTORS; + mutex_init(&ffs->mutex); + spin_lock_init(&ffs->eps_lock); + init_waitqueue_head(&ffs->ev.waitq); + init_completion(&ffs->ep0req_completion); + + /* XXX REVISIT need to update it in some places, or do we? */ + ffs->ev.can_stall = 1; + + return ffs; +} + + +static void ffs_data_clear(struct ffs_data *ffs) +{ + ENTER(); + + if (test_and_clear_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags)) + functionfs_closed_callback(ffs); + + BUG_ON(ffs->gadget); + + if (ffs->epfiles) + ffs_epfiles_destroy(ffs->epfiles, ffs->eps_count); + + kfree(ffs->raw_descs); + kfree(ffs->raw_strings); + kfree(ffs->stringtabs); +} + + +static void ffs_data_reset(struct ffs_data *ffs) +{ + ENTER(); + + ffs_data_clear(ffs); + + ffs->epfiles = NULL; + ffs->raw_descs = NULL; + ffs->raw_strings = NULL; + ffs->stringtabs = NULL; + + ffs->raw_descs_length = 0; + ffs->raw_fs_descs_length = 0; + ffs->fs_descs_count = 0; + ffs->hs_descs_count = 0; + + ffs->strings_count = 0; + ffs->interfaces_count = 0; + ffs->eps_count = 0; + + ffs->ev.count = 0; + + ffs->state = FFS_READ_DESCRIPTORS; + ffs->setup_state = FFS_NO_SETUP; + ffs->flags = 0; +} + + +static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev) +{ + unsigned i, count; + + ENTER(); + + if (WARN_ON(ffs->state != FFS_ACTIVE + || test_and_set_bit(FFS_FL_BOUND, &ffs->flags))) + return -EBADFD; + + ffs_data_get(ffs); + + ffs->ep0req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); + if (unlikely(!ffs->ep0req)) + return -ENOMEM; + ffs->ep0req->complete = ffs_ep0_complete; + ffs->ep0req->context = ffs; + + /* Get strings identifiers */ + for (count = ffs->strings_count, i = 0; i < count; ++i) { + struct usb_gadget_strings **lang; + + int id = usb_string_id(cdev); + if (unlikely(id < 0)) { + usb_ep_free_request(cdev->gadget->ep0, ffs->ep0req); + ffs->ep0req = NULL; + return id; + } + + lang = ffs->stringtabs; + do { + (*lang)->strings[i].id = id; + ++lang; + } while (*lang); + } + + ffs->gadget = cdev->gadget; + return 0; +} + + +static void functionfs_unbind(struct ffs_data *ffs) +{ + ENTER(); + + if (!WARN_ON(!ffs->gadget)) { + usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req); + ffs->ep0req = NULL; + ffs->gadget = NULL; + ffs_data_put(ffs); + } +} + + +static int ffs_epfiles_create(struct ffs_data *ffs) +{ + struct ffs_epfile *epfile, *epfiles; + unsigned i, count; + + ENTER(); + + count = ffs->eps_count; + epfiles = kzalloc(count * sizeof *epfiles, GFP_KERNEL); + if (!epfiles) + return -ENOMEM; + + epfile = epfiles; + for (i = 1; i <= count; ++i, ++epfile) { + epfile->ffs = ffs; + mutex_init(&epfile->mutex); + init_waitqueue_head(&epfile->wait); + sprintf(epfiles->name, "ep%u", i); + if (!unlikely(ffs_sb_create_file(ffs->sb, epfiles->name, epfile, + &ffs_epfile_operations, + &epfile->dentry))) { + ffs_epfiles_destroy(epfiles, i - 1); + return -ENOMEM; + } + } + + ffs->epfiles = epfiles; + return 0; +} + + +static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) +{ + struct ffs_epfile *epfile = epfiles; + + ENTER(); + + for (; count; --count, ++epfile) { + BUG_ON(mutex_is_locked(&epfile->mutex) || + waitqueue_active(&epfile->wait)); + if (epfile->dentry) { + d_delete(epfile->dentry); + dput(epfile->dentry); + epfile->dentry = NULL; + } + } + + kfree(epfiles); +} + + +static int functionfs_add(struct usb_composite_dev *cdev, + struct usb_configuration *c, + struct ffs_data *ffs) +{ + struct ffs_function *func; + int ret; + + ENTER(); + + func = kzalloc(sizeof *func, GFP_KERNEL); + if (unlikely(!func)) + return -ENOMEM; + + func->function.name = "Function FS Gadget"; + func->function.strings = ffs->stringtabs; + + func->function.bind = ffs_func_bind; + func->function.unbind = ffs_func_unbind; + func->function.set_alt = ffs_func_set_alt; + /*func->function.get_alt = ffs_func_get_alt;*/ + func->function.disable = ffs_func_disable; + func->function.setup = ffs_func_setup; + func->function.suspend = ffs_func_suspend; + func->function.resume = ffs_func_resume; + + func->conf = c; + func->gadget = cdev->gadget; + func->ffs = ffs; + ffs_data_get(ffs); + + ret = usb_add_function(c, &func->function); + if (unlikely(ret)) + ffs_func_free(func); + + return ret; +} + +static void ffs_func_free(struct ffs_function *func) +{ + ENTER(); + + ffs_data_put(func->ffs); + + kfree(func->eps); + /* eps and interfaces_nums are allocated in the same chunk so + * only one free is required. Descriptors are also allocated + * in the same chunk. */ + + kfree(func); +} + + +static void ffs_func_eps_disable(struct ffs_function *func) +{ + struct ffs_ep *ep = func->eps; + struct ffs_epfile *epfile = func->ffs->epfiles; + unsigned count = func->ffs->eps_count; + unsigned long flags; + + spin_lock_irqsave(&func->ffs->eps_lock, flags); + do { + /* pending requests get nuked */ + if (likely(ep->ep)) + usb_ep_disable(ep->ep); + epfile->ep = NULL; + + ++ep; + ++epfile; + } while (--count); + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); +} + +static int ffs_func_eps_enable(struct ffs_function *func) +{ + struct ffs_data *ffs = func->ffs; + struct ffs_ep *ep = func->eps; + struct ffs_epfile *epfile = ffs->epfiles; + unsigned count = ffs->eps_count; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&func->ffs->eps_lock, flags); + do { + struct usb_endpoint_descriptor *ds; + ds = ep->descs[ep->descs[1] ? 1 : 0]; + + ep->ep->driver_data = ep; + ret = usb_ep_enable(ep->ep, ds); + if (likely(!ret)) { + epfile->ep = ep; + epfile->in = usb_endpoint_dir_in(ds); + epfile->isoc = usb_endpoint_xfer_isoc(ds); + } else { + break; + } + + wake_up(&epfile->wait); + + ++ep; + ++epfile; + } while (--count); + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); + + return ret; +} + + +/* Parsing and building descriptors and strings *****************************/ + + +/* This validates if data pointed by data is a valid USB descriptor as + * well as record how many interfaces, endpoints and strings are + * required by given configuration. Returns address afther the + * descriptor or NULL if data is invalid. */ + +enum ffs_entity_type { + FFS_DESCRIPTOR, FFS_INTERFACE, FFS_STRING, FFS_ENDPOINT +}; + +typedef int (*ffs_entity_callback)(enum ffs_entity_type entity, + u8 *valuep, + struct usb_descriptor_header *desc, + void *priv); + +static int __must_check ffs_do_desc(char *data, unsigned len, + ffs_entity_callback entity, void *priv) +{ + struct usb_descriptor_header *_ds = (void *)data; + u8 length; + int ret; + + ENTER(); + + /* At least two bytes are required: length and type */ + if (len < 2) { + FVDBG("descriptor too short"); + return -EINVAL; + } + + /* If we have at least as many bytes as the descriptor takes? */ + length = _ds->bLength; + if (len < length) { + FVDBG("descriptor longer then available data"); + return -EINVAL; + } + +#define __entity_check_INTERFACE(val) 1 +#define __entity_check_STRING(val) (val) +#define __entity_check_ENDPOINT(val) ((val) & USB_ENDPOINT_NUMBER_MASK) +#define __entity(type, val) do { \ + FVDBG("entity " #type "(%02x)", (val)); \ + if (unlikely(!__entity_check_ ##type(val))) { \ + FVDBG("invalid entity's value"); \ + return -EINVAL; \ + } \ + ret = entity(FFS_ ##type, &val, _ds, priv); \ + if (unlikely(ret < 0)) { \ + FDBG("entity " #type "(%02x); ret = %d", \ + (val), ret); \ + return ret; \ + } \ + } while (0) + + /* Parse descriptor depending on type. */ + switch (_ds->bDescriptorType) { + case USB_DT_DEVICE: + case USB_DT_CONFIG: + case USB_DT_STRING: + case USB_DT_DEVICE_QUALIFIER: + /* function can't have any of those */ + FVDBG("descriptor reserved for gadget: %d", _ds->bDescriptorType); + return -EINVAL; + + case USB_DT_INTERFACE: { + struct usb_interface_descriptor *ds = (void *)_ds; + FVDBG("interface descriptor"); + if (length != sizeof *ds) + goto inv_length; + + __entity(INTERFACE, ds->bInterfaceNumber); + if (ds->iInterface) + __entity(STRING, ds->iInterface); + } + break; + + case USB_DT_ENDPOINT: { + struct usb_endpoint_descriptor *ds = (void *)_ds; + FVDBG("endpoint descriptor"); + if (length != USB_DT_ENDPOINT_SIZE && + length != USB_DT_ENDPOINT_AUDIO_SIZE) + goto inv_length; + __entity(ENDPOINT, ds->bEndpointAddress); + } + break; + + case USB_DT_OTG: + if (length != sizeof(struct usb_otg_descriptor)) + goto inv_length; + break; + + case USB_DT_INTERFACE_ASSOCIATION: { + struct usb_interface_assoc_descriptor *ds = (void *)_ds; + FVDBG("interface association descriptor"); + if (length != sizeof *ds) + goto inv_length; + if (ds->iFunction) + __entity(STRING, ds->iFunction); + } + break; + + case USB_DT_OTHER_SPEED_CONFIG: + case USB_DT_INTERFACE_POWER: + case USB_DT_DEBUG: + case USB_DT_SECURITY: + case USB_DT_CS_RADIO_CONTROL: + /* TODO */ + FVDBG("unimplemented descriptor: %d", _ds->bDescriptorType); + return -EINVAL; + + default: + /* We should never be here */ + FVDBG("unknown descriptor: %d", _ds->bDescriptorType); + return -EINVAL; + + inv_length: + FVDBG("invalid length: %d (descriptor %d)", + _ds->bLength, _ds->bDescriptorType); + return -EINVAL; + } + +#undef __entity +#undef __entity_check_DESCRIPTOR +#undef __entity_check_INTERFACE +#undef __entity_check_STRING +#undef __entity_check_ENDPOINT + + return length; +} + + +static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len, + ffs_entity_callback entity, void *priv) +{ + const unsigned _len = len; + unsigned long num = 0; + + ENTER(); + + for (;;) { + int ret; + + if (num == count) + data = NULL; + + /* Record "descriptor" entitny */ + ret = entity(FFS_DESCRIPTOR, (u8 *)num, (void *)data, priv); + if (unlikely(ret < 0)) { + FDBG("entity DESCRIPTOR(%02lx); ret = %d", num, ret); + return ret; + } + + if (!data) + return _len - len; + + ret = ffs_do_desc(data, len, entity, priv); + if (unlikely(ret < 0)) { + FDBG("%s returns %d", __func__, ret); + return ret; + } + + len -= ret; + data += ret; + ++num; + } +} + + +static int __ffs_data_do_entity(enum ffs_entity_type type, + u8 *valuep, struct usb_descriptor_header *desc, + void *priv) +{ + struct ffs_data *ffs = priv; + + ENTER(); + + switch (type) { + case FFS_DESCRIPTOR: + break; + + case FFS_INTERFACE: + /* Interfaces are indexed from zero so if we + * encountered interface "n" then there are at least + * "n+1" interfaces. */ + if (*valuep >= ffs->interfaces_count) + ffs->interfaces_count = *valuep + 1; + break; + + case FFS_STRING: + /* Strings are indexed from 1 (0 is magic ;) reserved + * for languages list or some such) */ + if (*valuep > ffs->strings_count) + ffs->strings_count = *valuep; + break; + + case FFS_ENDPOINT: + /* Endpoints are indexed from 1 as well. */ + if ((*valuep & USB_ENDPOINT_NUMBER_MASK) > ffs->eps_count) + ffs->eps_count = (*valuep & USB_ENDPOINT_NUMBER_MASK); + break; + } + + return 0; +} + + +static int __ffs_data_got_descs(struct ffs_data *ffs, + char *const _data, size_t len) +{ + unsigned fs_count, hs_count; + int fs_len, ret = -EINVAL; + char *data = _data; + + ENTER(); + + if (unlikely(get_unaligned_le32(data) != FUNCTIONFS_DESCRIPTORS_MAGIC || + get_unaligned_le32(data + 4) != len)) + goto error; + fs_count = get_unaligned_le32(data + 8); + hs_count = get_unaligned_le32(data + 12); + + if (!fs_count && !hs_count) + goto einval; + + data += 16; + len -= 16; + + if (likely(fs_count)) { + fs_len = ffs_do_descs(fs_count, data, len, + __ffs_data_do_entity, ffs); + if (unlikely(fs_len < 0)) { + ret = fs_len; + goto error; + } + + data += fs_len; + len -= fs_len; + } else { + fs_len = 0; + } + + if (likely(hs_count)) { + ret = ffs_do_descs(hs_count, data, len, + __ffs_data_do_entity, ffs); + if (unlikely(ret < 0)) + goto error; + } else { + ret = 0; + } + + if (unlikely(len != ret)) + goto einval; + + ffs->raw_fs_descs_length = fs_len; + ffs->raw_descs_length = fs_len + ret; + ffs->raw_descs = _data; + ffs->fs_descs_count = fs_count; + ffs->hs_descs_count = hs_count; + + return 0; + +einval: + ret = -EINVAL; +error: + kfree(_data); + return ret; +} + + + +static int __ffs_data_got_strings(struct ffs_data *ffs, + char *const _data, size_t len) +{ + u32 str_count, needed_count, lang_count; + struct usb_gadget_strings **stringtabs, *t; + struct usb_string *strings, *s; + const char *data = _data; + + ENTER(); + + if (unlikely(get_unaligned_le32(data) != FUNCTIONFS_STRINGS_MAGIC || + get_unaligned_le32(data + 4) != len)) + goto error; + str_count = get_unaligned_le32(data + 8); + lang_count = get_unaligned_le32(data + 12); + + /* if one is zero the other must be zero */ + if (unlikely(!str_count != !lang_count)) + goto error; + + /* Do we have at least as many strings as descriptors need? */ + needed_count = ffs->strings_count; + if (unlikely(str_count < needed_count)) + goto error; + + /* If we don't need any strings just return and free all + * memory */ + if (!needed_count) { + kfree(_data); + return 0; + } + + /* Allocate */ + { + /* Allocate everything in one chunk so there's less + * maintanance. */ + struct { + struct usb_gadget_strings *stringtabs[lang_count + 1]; + struct usb_gadget_strings stringtab[lang_count]; + struct usb_string strings[lang_count*(needed_count+1)]; + } *d; + unsigned i = 0; + + d = kmalloc(sizeof *d, GFP_KERNEL); + if (unlikely(!d)) { + kfree(_data); + return -ENOMEM; + } + + stringtabs = d->stringtabs; + t = d->stringtab; + i = lang_count; + do { + *stringtabs++ = t++; + } while (--i); + *stringtabs = NULL; + + stringtabs = d->stringtabs; + t = d->stringtab; + s = d->strings; + strings = s; + } + + /* For each language */ + data += 16; + len -= 16; + + do { /* lang_count > 0 so we can use do-while */ + unsigned needed = needed_count; + + if (unlikely(len < 3)) + goto error_free; + t->language = get_unaligned_le16(data); + t->strings = s; + ++t; + + data += 2; + len -= 2; + + /* For each string */ + do { /* str_count > 0 so we can use do-while */ + size_t length = strnlen(data, len); + + if (unlikely(length == len)) + goto error_free; + + /* user may provide more strings then we need, + * if that's the case we simply ingore the + * rest */ + if (likely(needed)) { + /* s->id will be set while adding + * function to configuration so for + * now just leave garbage here. */ + s->s = data; + --needed; + ++s; + } + + data += length + 1; + len -= length + 1; + } while (--str_count); + + s->id = 0; /* terminator */ + s->s = NULL; + ++s; + + } while (--lang_count); + + /* Some garbage left? */ + if (unlikely(len)) + goto error_free; + + /* Done! */ + ffs->stringtabs = stringtabs; + ffs->raw_strings = _data; + + return 0; + +error_free: + kfree(stringtabs); +error: + kfree(_data); + return -EINVAL; +} + + + + +/* Events handling and management *******************************************/ + +static void __ffs_event_add(struct ffs_data *ffs, + enum usb_functionfs_event_type type) +{ + enum usb_functionfs_event_type rem_type1, rem_type2 = type; + int neg = 0; + + /* Abort any unhandled setup */ + /* We do not need to worry about some cmpxchg() changing value + * of ffs->setup_state without holding the lock because when + * state is FFS_SETUP_PENDING cmpxchg() in several places in + * the source does nothing. */ + if (ffs->setup_state == FFS_SETUP_PENDING) + ffs->setup_state = FFS_SETUP_CANCELED; + + switch (type) { + case FUNCTIONFS_RESUME: + rem_type2 = FUNCTIONFS_SUSPEND; + /* FALL THGOUTH */ + case FUNCTIONFS_SUSPEND: + case FUNCTIONFS_SETUP: + rem_type1 = type; + /* discard all similar events */ + break; + + case FUNCTIONFS_BIND: + case FUNCTIONFS_UNBIND: + case FUNCTIONFS_DISABLE: + case FUNCTIONFS_ENABLE: + /* discard everything other then power management. */ + rem_type1 = FUNCTIONFS_SUSPEND; + rem_type2 = FUNCTIONFS_RESUME; + neg = 1; + break; + + default: + BUG(); + } + + { + u8 *ev = ffs->ev.types, *out = ev; + unsigned n = ffs->ev.count; + for (; n; --n, ++ev) + if ((*ev == rem_type1 || *ev == rem_type2) == neg) + *out++ = *ev; + else + FVDBG("purging event %d", *ev); + ffs->ev.count = out - ffs->ev.types; + } + + FVDBG("adding event %d", type); + ffs->ev.types[ffs->ev.count++] = type; + wake_up_locked(&ffs->ev.waitq); +} + +static void ffs_event_add(struct ffs_data *ffs, + enum usb_functionfs_event_type type) +{ + unsigned long flags; + spin_lock_irqsave(&ffs->ev.waitq.lock, flags); + __ffs_event_add(ffs, type); + spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags); +} + + +/* Bind/unbind USB function hooks *******************************************/ + +static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, + struct usb_descriptor_header *desc, + void *priv) +{ + struct usb_endpoint_descriptor *ds = (void *)desc; + struct ffs_function *func = priv; + struct ffs_ep *ffs_ep; + + /* If hs_descriptors is not NULL then we are reading hs + * descriptors now */ + const int isHS = func->function.hs_descriptors != NULL; + unsigned idx; + + if (type != FFS_DESCRIPTOR) + return 0; + + if (isHS) + func->function.hs_descriptors[(long)valuep] = desc; + else + func->function.descriptors[(long)valuep] = desc; + + if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT) + return 0; + + idx = (ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) - 1; + ffs_ep = func->eps + idx; + + if (unlikely(ffs_ep->descs[isHS])) { + FVDBG("two %sspeed descriptors for EP %d", + isHS ? "high" : "full", + ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + return -EINVAL; + } + ffs_ep->descs[isHS] = ds; + + ffs_dump_mem(": Original ep desc", ds, ds->bLength); + if (ffs_ep->ep) { + ds->bEndpointAddress = ffs_ep->descs[0]->bEndpointAddress; + if (!ds->wMaxPacketSize) + ds->wMaxPacketSize = ffs_ep->descs[0]->wMaxPacketSize; + } else { + struct usb_request *req; + struct usb_ep *ep; + + FVDBG("autoconfig"); + ep = usb_ep_autoconfig(func->gadget, ds); + if (unlikely(!ep)) + return -ENOTSUPP; + ep->driver_data = func->eps + idx;; + + req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (unlikely(!req)) + return -ENOMEM; + + ffs_ep->ep = ep; + ffs_ep->req = req; + func->eps_revmap[ds->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK] = idx + 1; + } + ffs_dump_mem(": Rewritten ep desc", ds, ds->bLength); + + return 0; +} + + +static int __ffs_func_bind_do_nums(enum ffs_entity_type type, u8 *valuep, + struct usb_descriptor_header *desc, + void *priv) +{ + struct ffs_function *func = priv; + unsigned idx; + u8 newValue; + + switch (type) { + default: + case FFS_DESCRIPTOR: + /* Handled in previous pass by __ffs_func_bind_do_descs() */ + return 0; + + case FFS_INTERFACE: + idx = *valuep; + if (func->interfaces_nums[idx] < 0) { + int id = usb_interface_id(func->conf, &func->function); + if (unlikely(id < 0)) + return id; + func->interfaces_nums[idx] = id; + } + newValue = func->interfaces_nums[idx]; + break; + + case FFS_STRING: + /* String' IDs are allocated when fsf_data is bound to cdev */ + newValue = func->ffs->stringtabs[0]->strings[*valuep - 1].id; + break; + + case FFS_ENDPOINT: + /* USB_DT_ENDPOINT are handled in + * __ffs_func_bind_do_descs(). */ + if (desc->bDescriptorType == USB_DT_ENDPOINT) + return 0; + + idx = (*valuep & USB_ENDPOINT_NUMBER_MASK) - 1; + if (unlikely(!func->eps[idx].ep)) + return -EINVAL; + + { + struct usb_endpoint_descriptor **descs; + descs = func->eps[idx].descs; + newValue = descs[descs[0] ? 0 : 1]->bEndpointAddress; + } + break; + } + + FVDBG("%02x -> %02x", *valuep, newValue); + *valuep = newValue; + return 0; +} + +static int ffs_func_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + + const int full = !!func->ffs->fs_descs_count; + const int high = gadget_is_dualspeed(func->gadget) && + func->ffs->hs_descs_count; + + int ret; + + /* Make it a single chunk, less management later on */ + struct { + struct ffs_ep eps[ffs->eps_count]; + struct usb_descriptor_header + *fs_descs[full ? ffs->fs_descs_count + 1 : 0]; + struct usb_descriptor_header + *hs_descs[high ? ffs->hs_descs_count + 1 : 0]; + short inums[ffs->interfaces_count]; + char raw_descs[high ? ffs->raw_descs_length + : ffs->raw_fs_descs_length]; + } *data; + + ENTER(); + + /* Only high speed but not supported by gadget? */ + if (unlikely(!(full | high))) + return -ENOTSUPP; + + /* Allocate */ + data = kmalloc(sizeof *data, GFP_KERNEL); + if (unlikely(!data)) + return -ENOMEM; + + /* Zero */ + memset(data->eps, 0, sizeof data->eps); + memcpy(data->raw_descs, ffs->raw_descs + 16, sizeof data->raw_descs); + memset(data->inums, 0xff, sizeof data->inums); + for (ret = ffs->eps_count; ret; --ret) + data->eps[ret].num = -1; + + /* Save pointers */ + func->eps = data->eps; + func->interfaces_nums = data->inums; + + /* Go throught all the endpoint descriptors and allocate + * endpoints first, so that later we can rewrite the endpoint + * numbers without worying that it may be described later on. */ + if (likely(full)) { + func->function.descriptors = data->fs_descs; + ret = ffs_do_descs(ffs->fs_descs_count, + data->raw_descs, + sizeof data->raw_descs, + __ffs_func_bind_do_descs, func); + if (unlikely(ret < 0)) + goto error; + } else { + ret = 0; + } + + if (likely(high)) { + func->function.hs_descriptors = data->hs_descs; + ret = ffs_do_descs(ffs->hs_descs_count, + data->raw_descs + ret, + (sizeof data->raw_descs) - ret, + __ffs_func_bind_do_descs, func); + } + + /* Now handle interface numbers allocation and interface and + * enpoint numbers rewritting. We can do that in one go + * now. */ + ret = ffs_do_descs(ffs->fs_descs_count + + (high ? ffs->hs_descs_count : 0), + data->raw_descs, sizeof data->raw_descs, + __ffs_func_bind_do_nums, func); + if (unlikely(ret < 0)) + goto error; + + /* And we're done */ + ffs_event_add(ffs, FUNCTIONFS_BIND); + return 0; + +error: + /* XXX Do we need to release all claimed endpoints here? */ + return ret; +} + + +/* Other USB function hooks *************************************************/ + +static void ffs_func_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + + ENTER(); + + if (ffs->func == func) { + ffs_func_eps_disable(func); + ffs->func = NULL; + } + + ffs_event_add(ffs, FUNCTIONFS_UNBIND); + + ffs_func_free(func); +} + + +static int ffs_func_set_alt(struct usb_function *f, + unsigned interface, unsigned alt) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + int ret = 0, intf; + + if (alt != (unsigned)-1) { + intf = ffs_func_revmap_intf(func, interface); + if (unlikely(intf < 0)) + return intf; + } + + if (ffs->func) + ffs_func_eps_disable(ffs->func); + + if (ffs->state != FFS_ACTIVE) + return -ENODEV; + + if (alt == (unsigned)-1) { + ffs->func = NULL; + ffs_event_add(ffs, FUNCTIONFS_DISABLE); + return 0; + } + + ffs->func = func; + ret = ffs_func_eps_enable(func); + if (likely(ret >= 0)) + ffs_event_add(ffs, FUNCTIONFS_ENABLE); + return ret; +} + +static void ffs_func_disable(struct usb_function *f) +{ + ffs_func_set_alt(f, 0, (unsigned)-1); +} + +static int ffs_func_setup(struct usb_function *f, + const struct usb_ctrlrequest *creq) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + unsigned long flags; + int ret; + + ENTER(); + + FVDBG("creq->bRequestType = %02x", creq->bRequestType); + FVDBG("creq->bRequest = %02x", creq->bRequest); + FVDBG("creq->wValue = %04x", le16_to_cpu(creq->wValue)); + FVDBG("creq->wIndex = %04x", le16_to_cpu(creq->wIndex)); + FVDBG("creq->wLength = %04x", le16_to_cpu(creq->wLength)); + + /* Most requests directed to interface go throught here + * (notable exceptions are set/get interface) so we need to + * handle them. All other either handled by composite or + * passed to usb_configuration->setup() (if one is set). No + * matter, we will handle requests directed to endpoint here + * as well (as it's straightforward) but what to do with any + * other request? */ + + if (ffs->state != FFS_ACTIVE) + return -ENODEV; + + switch (creq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + ret = ffs_func_revmap_intf(func, le16_to_cpu(creq->wIndex)); + if (unlikely(ret < 0)) + return ret; + break; + + case USB_RECIP_ENDPOINT: + ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex)); + if (unlikely(ret < 0)) + return ret; + break; + + default: + return -EOPNOTSUPP; + } + + spin_lock_irqsave(&ffs->ev.waitq.lock, flags); + ffs->ev.setup = *creq; + ffs->ev.setup.wIndex = cpu_to_le16(ret); + __ffs_event_add(ffs, FUNCTIONFS_SETUP); + spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags); + + return 0; +} + +static void ffs_func_suspend(struct usb_function *f) +{ + ENTER(); + ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_SUSPEND); +} + +static void ffs_func_resume(struct usb_function *f) +{ + ENTER(); + ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_RESUME); +} + + + +/* Enpoint and interface numbers reverse mapping ****************************/ + +static int ffs_func_revmap_ep(struct ffs_function *func, u8 num) +{ + num = func->eps_revmap[num & USB_ENDPOINT_NUMBER_MASK]; + return num ? num : -EDOM; +} + +static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf) +{ + short *nums = func->interfaces_nums; + unsigned count = func->ffs->interfaces_count; + + for (; count; --count, ++nums) { + if (*nums >= 0 && *nums == intf) + return nums - func->interfaces_nums; + } + + return -EDOM; +} + + +/* Misc helper functions ****************************************************/ + +static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock) +{ + return nonblock + ? likely(mutex_trylock(mutex)) ? 0 : -EAGAIN + : mutex_lock_interruptible(mutex); +} + + +static char *ffs_prepare_buffer(const char * __user buf, size_t len) +{ + char *data; + + if (unlikely(!len)) + return NULL; + + data = kmalloc(len, GFP_KERNEL); + if (unlikely(!data)) + return ERR_PTR(-ENOMEM); + + if (unlikely(__copy_from_user(data, buf, len))) { + kfree(data); + return ERR_PTR(-EFAULT); + } + + FVDBG("Buffer from user space:"); + ffs_dump_mem("", data, len); + + return data; +} diff --git a/drivers/usb/gadget/f_hid.c b/drivers/usb/gadget/f_hid.c new file mode 100644 index 00000000000..1e00ff9866a --- /dev/null +++ b/drivers/usb/gadget/f_hid.c @@ -0,0 +1,673 @@ +/* + * f_hid.c -- USB HID function driver + * + * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/module.h> +#include <linux/hid.h> +#include <linux/cdev.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/smp_lock.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/usb/g_hid.h> + +static int major, minors; +static struct class *hidg_class; + +/*-------------------------------------------------------------------------*/ +/* HID gadget struct */ + +struct f_hidg { + /* configuration */ + unsigned char bInterfaceSubClass; + unsigned char bInterfaceProtocol; + unsigned short report_desc_length; + char *report_desc; + unsigned short report_length; + + /* recv report */ + char *set_report_buff; + unsigned short set_report_length; + spinlock_t spinlock; + wait_queue_head_t read_queue; + + /* send report */ + struct mutex lock; + bool write_pending; + wait_queue_head_t write_queue; + struct usb_request *req; + + int minor; + struct cdev cdev; + struct usb_function func; + struct usb_ep *in_ep; + struct usb_endpoint_descriptor *fs_in_ep_desc; + struct usb_endpoint_descriptor *hs_in_ep_desc; +}; + +static inline struct f_hidg *func_to_hidg(struct usb_function *f) +{ + return container_of(f, struct f_hidg, func); +} + +/*-------------------------------------------------------------------------*/ +/* Static descriptors */ + +static struct usb_interface_descriptor hidg_interface_desc = { + .bLength = sizeof hidg_interface_desc, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + /* .bInterfaceSubClass = DYNAMIC */ + /* .bInterfaceProtocol = DYNAMIC */ + /* .iInterface = DYNAMIC */ +}; + +static struct hid_descriptor hidg_desc = { + .bLength = sizeof hidg_desc, + .bDescriptorType = HID_DT_HID, + .bcdHID = 0x0101, + .bCountryCode = 0x00, + .bNumDescriptors = 0x1, + /*.desc[0].bDescriptorType = DYNAMIC */ + /*.desc[0].wDescriptorLenght = DYNAMIC */ +}; + +/* High-Speed Support */ + +static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 4, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_descriptor_header *hidg_hs_descriptors[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_hs_in_ep_desc, + NULL, +}; + +/* Full-Speed Support */ + +static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + .bInterval = 10, /* FIXME: Add this field in the + * HID gadget configuration? + * (struct hidg_func_descriptor) + */ +}; + +static struct usb_descriptor_header *hidg_fs_descriptors[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_fs_in_ep_desc, + NULL, +}; + +/*-------------------------------------------------------------------------*/ +/* Char Device */ + +static ssize_t f_hidg_read(struct file *file, char __user *buffer, + size_t count, loff_t *ptr) +{ + struct f_hidg *hidg = (struct f_hidg *)file->private_data; + char *tmp_buff = NULL; + unsigned long flags; + + if (!count) + return 0; + + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + + spin_lock_irqsave(&hidg->spinlock, flags); + +#define READ_COND (hidg->set_report_buff != NULL) + + while (!READ_COND) { + spin_unlock_irqrestore(&hidg->spinlock, flags); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(hidg->read_queue, READ_COND)) + return -ERESTARTSYS; + + spin_lock_irqsave(&hidg->spinlock, flags); + } + + + count = min_t(unsigned, count, hidg->set_report_length); + tmp_buff = hidg->set_report_buff; + hidg->set_report_buff = NULL; + + spin_unlock_irqrestore(&hidg->spinlock, flags); + + if (tmp_buff != NULL) { + /* copy to user outside spinlock */ + count -= copy_to_user(buffer, tmp_buff, count); + kfree(tmp_buff); + } else + count = -ENOMEM; + + return count; +} + +static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *)ep->driver_data; + + if (req->status != 0) { + ERROR(hidg->func.config->cdev, + "End Point Request ERROR: %d\n", req->status); + } + + hidg->write_pending = 0; + wake_up(&hidg->write_queue); +} + +static ssize_t f_hidg_write(struct file *file, const char __user *buffer, + size_t count, loff_t *offp) +{ + struct f_hidg *hidg = (struct f_hidg *)file->private_data; + ssize_t status = -ENOMEM; + + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + + mutex_lock(&hidg->lock); + +#define WRITE_COND (!hidg->write_pending) + + /* write queue */ + while (!WRITE_COND) { + mutex_unlock(&hidg->lock); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible_exclusive( + hidg->write_queue, WRITE_COND)) + return -ERESTARTSYS; + + mutex_lock(&hidg->lock); + } + + count = min_t(unsigned, count, hidg->report_length); + status = copy_from_user(hidg->req->buf, buffer, count); + + if (status != 0) { + ERROR(hidg->func.config->cdev, + "copy_from_user error\n"); + mutex_unlock(&hidg->lock); + return -EINVAL; + } + + hidg->req->status = 0; + hidg->req->zero = 0; + hidg->req->length = count; + hidg->req->complete = f_hidg_req_complete; + hidg->req->context = hidg; + hidg->write_pending = 1; + + status = usb_ep_queue(hidg->in_ep, hidg->req, GFP_ATOMIC); + if (status < 0) { + ERROR(hidg->func.config->cdev, + "usb_ep_queue error on int endpoint %zd\n", status); + hidg->write_pending = 0; + wake_up(&hidg->write_queue); + } else { + status = count; + } + + mutex_unlock(&hidg->lock); + + return status; +} + +static unsigned int f_hidg_poll(struct file *file, poll_table *wait) +{ + struct f_hidg *hidg = (struct f_hidg *)file->private_data; + unsigned int ret = 0; + + poll_wait(file, &hidg->read_queue, wait); + poll_wait(file, &hidg->write_queue, wait); + + if (WRITE_COND) + ret |= POLLOUT | POLLWRNORM; + + if (READ_COND) + ret |= POLLIN | POLLRDNORM; + + return ret; +} + +#undef WRITE_COND +#undef READ_COND + +static int f_hidg_release(struct inode *inode, struct file *fd) +{ + fd->private_data = NULL; + return 0; +} + +static int f_hidg_open(struct inode *inode, struct file *fd) +{ + struct f_hidg *hidg = + container_of(inode->i_cdev, struct f_hidg, cdev); + + fd->private_data = hidg; + + return 0; +} + +/*-------------------------------------------------------------------------*/ +/* usb_function */ + +static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *)req->context; + + if (req->status != 0 || req->buf == NULL || req->actual == 0) { + ERROR(hidg->func.config->cdev, "%s FAILED\n", __func__); + return; + } + + spin_lock(&hidg->spinlock); + + hidg->set_report_buff = krealloc(hidg->set_report_buff, + req->actual, GFP_ATOMIC); + + if (hidg->set_report_buff == NULL) { + spin_unlock(&hidg->spinlock); + return; + } + hidg->set_report_length = req->actual; + memcpy(hidg->set_report_buff, req->buf, req->actual); + + spin_unlock(&hidg->spinlock); + + wake_up(&hidg->read_queue); + + return; +} + +static int hidg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_hidg *hidg = func_to_hidg(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int status = 0; + __u16 value, length; + + value = __le16_to_cpu(ctrl->wValue); + length = __le16_to_cpu(ctrl->wLength); + + VDBG(cdev, "hid_setup crtl_request : bRequestType:0x%x bRequest:0x%x " + "Value:0x%x\n", ctrl->bRequestType, ctrl->bRequest, value); + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_REPORT): + VDBG(cdev, "get_report\n"); + + /* send an empty report */ + length = min_t(unsigned, length, hidg->report_length); + memset(req->buf, 0x0, length); + + goto respond; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_PROTOCOL): + VDBG(cdev, "get_protocol\n"); + goto stall; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_REPORT): + VDBG(cdev, "set_report | wLenght=%d\n", ctrl->wLength); + req->context = hidg; + req->complete = hidg_set_report_complete; + goto respond; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_PROTOCOL): + VDBG(cdev, "set_protocol\n"); + goto stall; + break; + + case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8 + | USB_REQ_GET_DESCRIPTOR): + switch (value >> 8) { + case HID_DT_REPORT: + VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n"); + length = min_t(unsigned short, length, + hidg->report_desc_length); + memcpy(req->buf, hidg->report_desc, length); + goto respond; + break; + + default: + VDBG(cdev, "Unknown decriptor request 0x%x\n", + value >> 8); + goto stall; + break; + } + break; + + default: + VDBG(cdev, "Unknown request 0x%x\n", + ctrl->bRequest); + goto stall; + break; + } + +stall: + return -EOPNOTSUPP; + +respond: + req->zero = 0; + req->length = length; + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (status < 0) + ERROR(cdev, "usb_ep_queue error on ep0 %d\n", value); + return status; +} + +static void hidg_disable(struct usb_function *f) +{ + struct f_hidg *hidg = func_to_hidg(f); + + usb_ep_disable(hidg->in_ep); + hidg->in_ep->driver_data = NULL; + + return; +} + +static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct f_hidg *hidg = func_to_hidg(f); + const struct usb_endpoint_descriptor *ep_desc; + int status = 0; + + VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt); + + if (hidg->in_ep != NULL) { + /* restart endpoint */ + if (hidg->in_ep->driver_data != NULL) + usb_ep_disable(hidg->in_ep); + + ep_desc = ep_choose(f->config->cdev->gadget, + hidg->hs_in_ep_desc, hidg->fs_in_ep_desc); + status = usb_ep_enable(hidg->in_ep, ep_desc); + if (status < 0) { + ERROR(cdev, "Enable endpoint FAILED!\n"); + goto fail; + } + hidg->in_ep->driver_data = hidg; + } +fail: + return status; +} + +const struct file_operations f_hidg_fops = { + .owner = THIS_MODULE, + .open = f_hidg_open, + .release = f_hidg_release, + .write = f_hidg_write, + .read = f_hidg_read, + .poll = f_hidg_poll, +}; + +static int __init hidg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_ep *ep; + struct f_hidg *hidg = func_to_hidg(f); + int status; + dev_t dev; + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + hidg_interface_desc.bInterfaceNumber = status; + + + /* allocate instance-specific endpoints */ + status = -ENODEV; + ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_in_ep_desc); + if (!ep) + goto fail; + ep->driver_data = c->cdev; /* claim */ + hidg->in_ep = ep; + + /* preallocate request and buffer */ + status = -ENOMEM; + hidg->req = usb_ep_alloc_request(hidg->in_ep, GFP_KERNEL); + if (!hidg->req) + goto fail; + + + hidg->req->buf = kmalloc(hidg->report_length, GFP_KERNEL); + if (!hidg->req->buf) + goto fail; + + /* set descriptor dynamic values */ + hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass; + hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; + hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT; + hidg_desc.desc[0].wDescriptorLength = + cpu_to_le16(hidg->report_desc_length); + + hidg->set_report_buff = NULL; + + /* copy descriptors */ + f->descriptors = usb_copy_descriptors(hidg_fs_descriptors); + if (!f->descriptors) + goto fail; + + hidg->fs_in_ep_desc = usb_find_endpoint(hidg_fs_descriptors, + f->descriptors, + &hidg_fs_in_ep_desc); + + if (gadget_is_dualspeed(c->cdev->gadget)) { + hidg_hs_in_ep_desc.bEndpointAddress = + hidg_fs_in_ep_desc.bEndpointAddress; + f->hs_descriptors = usb_copy_descriptors(hidg_hs_descriptors); + if (!f->hs_descriptors) + goto fail; + hidg->hs_in_ep_desc = usb_find_endpoint(hidg_hs_descriptors, + f->hs_descriptors, + &hidg_hs_in_ep_desc); + } else { + hidg->hs_in_ep_desc = NULL; + } + + mutex_init(&hidg->lock); + spin_lock_init(&hidg->spinlock); + init_waitqueue_head(&hidg->write_queue); + init_waitqueue_head(&hidg->read_queue); + + /* create char device */ + cdev_init(&hidg->cdev, &f_hidg_fops); + dev = MKDEV(major, hidg->minor); + status = cdev_add(&hidg->cdev, dev, 1); + if (status) + goto fail; + + device_create(hidg_class, NULL, dev, NULL, "%s%d", "hidg", hidg->minor); + + return 0; + +fail: + ERROR(f->config->cdev, "hidg_bind FAILED\n"); + if (hidg->req != NULL) { + kfree(hidg->req->buf); + if (hidg->in_ep != NULL) + usb_ep_free_request(hidg->in_ep, hidg->req); + } + + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + + return status; +} + +static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_hidg *hidg = func_to_hidg(f); + + device_destroy(hidg_class, MKDEV(major, hidg->minor)); + cdev_del(&hidg->cdev); + + /* disable/free request and end point */ + usb_ep_disable(hidg->in_ep); + usb_ep_dequeue(hidg->in_ep, hidg->req); + kfree(hidg->req->buf); + usb_ep_free_request(hidg->in_ep, hidg->req); + + /* free descriptors copies */ + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + + kfree(hidg->report_desc); + kfree(hidg->set_report_buff); + kfree(hidg); +} + +/*-------------------------------------------------------------------------*/ +/* Strings */ + +#define CT_FUNC_HID_IDX 0 + +static struct usb_string ct_func_string_defs[] = { + [CT_FUNC_HID_IDX].s = "HID Interface", + {}, /* end of list */ +}; + +static struct usb_gadget_strings ct_func_string_table = { + .language = 0x0409, /* en-US */ + .strings = ct_func_string_defs, +}; + +static struct usb_gadget_strings *ct_func_strings[] = { + &ct_func_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ +/* usb_configuration */ + +int __init hidg_bind_config(struct usb_configuration *c, + struct hidg_func_descriptor *fdesc, int index) +{ + struct f_hidg *hidg; + int status; + + if (index >= minors) + return -ENOENT; + + /* maybe allocate device-global string IDs, and patch descriptors */ + if (ct_func_string_defs[CT_FUNC_HID_IDX].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ct_func_string_defs[CT_FUNC_HID_IDX].id = status; + hidg_interface_desc.iInterface = status; + } + + /* allocate and initialize one new instance */ + hidg = kzalloc(sizeof *hidg, GFP_KERNEL); + if (!hidg) + return -ENOMEM; + + hidg->minor = index; + hidg->bInterfaceSubClass = fdesc->subclass; + hidg->bInterfaceProtocol = fdesc->protocol; + hidg->report_length = fdesc->report_length; + hidg->report_desc_length = fdesc->report_desc_length; + hidg->report_desc = kmemdup(fdesc->report_desc, + fdesc->report_desc_length, + GFP_KERNEL); + if (!hidg->report_desc) { + kfree(hidg); + return -ENOMEM; + } + + hidg->func.name = "hid"; + hidg->func.strings = ct_func_strings; + hidg->func.bind = hidg_bind; + hidg->func.unbind = hidg_unbind; + hidg->func.set_alt = hidg_set_alt; + hidg->func.disable = hidg_disable; + hidg->func.setup = hidg_setup; + + status = usb_add_function(c, &hidg->func); + if (status) + kfree(hidg); + + return status; +} + +int __init ghid_setup(struct usb_gadget *g, int count) +{ + int status; + dev_t dev; + + hidg_class = class_create(THIS_MODULE, "hidg"); + + status = alloc_chrdev_region(&dev, 0, count, "hidg"); + if (!status) { + major = MAJOR(dev); + minors = count; + } + + return status; +} + +void ghid_cleanup(void) +{ + if (major) { + unregister_chrdev_region(MKDEV(major, 0), minors); + major = minors = 0; + } + + class_destroy(hidg_class); + hidg_class = NULL; +} diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index f4911c09022..7d05a0be5c6 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -163,6 +163,10 @@ * ro setting are not allowed when the medium is loaded or if CD-ROM * emulation is being used. * + * When a LUN receive an "eject" SCSI request (Start/Stop Unit), + * if the LUN is removable, the backing file is released to simulate + * ejection. + * * * This function is heavily based on "File-backed Storage Gadget" by * Alan Stern which in turn is heavily based on "Gadget Zero" by David @@ -302,7 +306,6 @@ static const char fsg_string_interface[] = "Mass Storage"; #define FSG_NO_INTR_EP 1 -#define FSG_BUFFHD_STATIC_BUFFER 1 #define FSG_NO_DEVICE_STRINGS 1 #define FSG_NO_OTG 1 #define FSG_NO_INTR_EP 1 @@ -1385,12 +1388,50 @@ static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) static int do_start_stop(struct fsg_common *common) { - if (!common->curlun) { + struct fsg_lun *curlun = common->curlun; + int loej, start; + + if (!curlun) { return -EINVAL; - } else if (!common->curlun->removable) { - common->curlun->sense_data = SS_INVALID_COMMAND; + } else if (!curlun->removable) { + curlun->sense_data = SS_INVALID_COMMAND; return -EINVAL; } + + loej = common->cmnd[4] & 0x02; + start = common->cmnd[4] & 0x01; + + /* eject code from file_storage.c:do_start_stop() */ + + if ((common->cmnd[1] & ~0x01) != 0 || /* Mask away Immed */ + (common->cmnd[4] & ~0x03) != 0) { /* Mask LoEj, Start */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (!start) { + /* Are we allowed to unload the media? */ + if (curlun->prevent_medium_removal) { + LDBG(curlun, "unload attempt prevented\n"); + curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED; + return -EINVAL; + } + if (loej) { /* Simulate an unload/eject */ + up_read(&common->filesem); + down_write(&common->filesem); + fsg_lun_close(curlun); + up_write(&common->filesem); + down_read(&common->filesem); + } + } else { + + /* Our emulation doesn't support mounting; the medium is + * available for use as soon as it is loaded. */ + if (!fsg_lun_is_open(curlun)) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + } return 0; } @@ -2701,10 +2742,8 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, /* Maybe allocate device-global string IDs, and patch descriptors */ if (fsg_strings[FSG_STRING_INTERFACE].id == 0) { rc = usb_string_id(cdev); - if (rc < 0) { - kfree(common); - return ERR_PTR(rc); - } + if (unlikely(rc < 0)) + goto error_release; fsg_strings[FSG_STRING_INTERFACE].id = rc; fsg_intf_desc.iInterface = rc; } @@ -2712,9 +2751,9 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, /* Create the LUNs, open their backing files, and register the * LUN devices in sysfs. */ curlun = kzalloc(nluns * sizeof *curlun, GFP_KERNEL); - if (!curlun) { - kfree(common); - return ERR_PTR(-ENOMEM); + if (unlikely(!curlun)) { + rc = -ENOMEM; + goto error_release; } common->luns = curlun; @@ -2762,13 +2801,19 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, /* Data buffers cyclic list */ - /* Buffers in buffhds are static -- no need for additional - * allocation. */ bh = common->buffhds; - i = FSG_NUM_BUFFERS - 1; + i = FSG_NUM_BUFFERS; + goto buffhds_first_it; do { bh->next = bh + 1; - } while (++bh, --i); + ++bh; +buffhds_first_it: + bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL); + if (unlikely(!bh->buf)) { + rc = -ENOMEM; + goto error_release; + } + } while (--i); bh->next = common->buffhds; @@ -2867,10 +2912,7 @@ error_release: static void fsg_common_release(struct kref *ref) { - struct fsg_common *common = - container_of(ref, struct fsg_common, ref); - unsigned i = common->nluns; - struct fsg_lun *lun = common->luns; + struct fsg_common *common = container_of(ref, struct fsg_common, ref); /* If the thread isn't already dead, tell it to exit now */ if (common->state != FSG_STATE_TERMINATED) { @@ -2881,17 +2923,29 @@ static void fsg_common_release(struct kref *ref) complete(&common->thread_notifier); } - /* Beware tempting for -> do-while optimization: when in error - * recovery nluns may be zero. */ + if (likely(common->luns)) { + struct fsg_lun *lun = common->luns; + unsigned i = common->nluns; + + /* In error recovery common->nluns may be zero. */ + for (; i; --i, ++lun) { + device_remove_file(&lun->dev, &dev_attr_ro); + device_remove_file(&lun->dev, &dev_attr_file); + fsg_lun_close(lun); + device_unregister(&lun->dev); + } + + kfree(common->luns); + } - for (; i; --i, ++lun) { - device_remove_file(&lun->dev, &dev_attr_ro); - device_remove_file(&lun->dev, &dev_attr_file); - fsg_lun_close(lun); - device_unregister(&lun->dev); + { + struct fsg_buffhd *bh = common->buffhds; + unsigned i = FSG_NUM_BUFFERS; + do { + kfree(bh->buf); + } while (++bh, --i); } - kfree(common->luns); if (common->free_storage_on_release) kfree(common); } @@ -2906,11 +2960,13 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) DBG(fsg, "unbind\n"); fsg_common_put(fsg->common); + usb_free_descriptors(fsg->function.descriptors); + usb_free_descriptors(fsg->function.hs_descriptors); kfree(fsg); } -static int __init fsg_bind(struct usb_configuration *c, struct usb_function *f) +static int fsg_bind(struct usb_configuration *c, struct usb_function *f) { struct fsg_dev *fsg = fsg_from_func(f); struct usb_gadget *gadget = c->cdev->gadget; @@ -2946,7 +3002,9 @@ static int __init fsg_bind(struct usb_configuration *c, struct usb_function *f) fsg_fs_bulk_in_desc.bEndpointAddress; fsg_hs_bulk_out_desc.bEndpointAddress = fsg_fs_bulk_out_desc.bEndpointAddress; - f->hs_descriptors = fsg_hs_function; + f->hs_descriptors = usb_copy_descriptors(fsg_hs_function); + if (unlikely(!f->hs_descriptors)) + return -ENOMEM; } return 0; @@ -2978,7 +3036,11 @@ static int fsg_add(struct usb_composite_dev *cdev, fsg->function.name = FSG_DRIVER_DESC; fsg->function.strings = fsg_strings_array; - fsg->function.descriptors = fsg_fs_function; + fsg->function.descriptors = usb_copy_descriptors(fsg_fs_function); + if (unlikely(!fsg->function.descriptors)) { + rc = -ENOMEM; + goto error_free_fsg; + } fsg->function.bind = fsg_bind; fsg->function.unbind = fsg_unbind; fsg->function.setup = fsg_setup; @@ -2993,11 +3055,19 @@ static int fsg_add(struct usb_composite_dev *cdev, * call to usb_add_function() was successful. */ rc = usb_add_function(c, &fsg->function); + if (unlikely(rc)) + goto error_free_all; - if (likely(rc == 0)) - fsg_common_get(fsg->common); - else - kfree(fsg); + fsg_common_get(fsg->common); + return 0; + +error_free_all: + usb_free_descriptors(fsg->function.descriptors); + /* fsg_bind() might have copied those; or maybe not? who cares + * -- free it just in case. */ + usb_free_descriptors(fsg->function.hs_descriptors); +error_free_fsg: + kfree(fsg); return rc; } diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index 56b022150f2..882484a4039 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -122,7 +122,7 @@ static unsigned int bitrate(struct usb_gadget *g) /* interface descriptor: */ -static struct usb_interface_descriptor rndis_control_intf __initdata = { +static struct usb_interface_descriptor rndis_control_intf = { .bLength = sizeof rndis_control_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -135,7 +135,7 @@ static struct usb_interface_descriptor rndis_control_intf __initdata = { /* .iInterface = DYNAMIC */ }; -static struct usb_cdc_header_desc header_desc __initdata = { +static struct usb_cdc_header_desc header_desc = { .bLength = sizeof header_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_HEADER_TYPE, @@ -143,7 +143,7 @@ static struct usb_cdc_header_desc header_desc __initdata = { .bcdCDC = cpu_to_le16(0x0110), }; -static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor __initdata = { +static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = { .bLength = sizeof call_mgmt_descriptor, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, @@ -152,7 +152,7 @@ static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor __initdata = { .bDataInterface = 0x01, }; -static struct usb_cdc_acm_descriptor rndis_acm_descriptor __initdata = { +static struct usb_cdc_acm_descriptor rndis_acm_descriptor = { .bLength = sizeof rndis_acm_descriptor, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_ACM_TYPE, @@ -160,7 +160,7 @@ static struct usb_cdc_acm_descriptor rndis_acm_descriptor __initdata = { .bmCapabilities = 0x00, }; -static struct usb_cdc_union_desc rndis_union_desc __initdata = { +static struct usb_cdc_union_desc rndis_union_desc = { .bLength = sizeof(rndis_union_desc), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_UNION_TYPE, @@ -170,7 +170,7 @@ static struct usb_cdc_union_desc rndis_union_desc __initdata = { /* the data interface has two bulk endpoints */ -static struct usb_interface_descriptor rndis_data_intf __initdata = { +static struct usb_interface_descriptor rndis_data_intf = { .bLength = sizeof rndis_data_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -198,7 +198,7 @@ rndis_iad_descriptor = { /* full speed support: */ -static struct usb_endpoint_descriptor fs_notify_desc __initdata = { +static struct usb_endpoint_descriptor fs_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -208,7 +208,7 @@ static struct usb_endpoint_descriptor fs_notify_desc __initdata = { .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, }; -static struct usb_endpoint_descriptor fs_in_desc __initdata = { +static struct usb_endpoint_descriptor fs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -216,7 +216,7 @@ static struct usb_endpoint_descriptor fs_in_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_endpoint_descriptor fs_out_desc __initdata = { +static struct usb_endpoint_descriptor fs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -224,7 +224,7 @@ static struct usb_endpoint_descriptor fs_out_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_descriptor_header *eth_fs_function[] __initdata = { +static struct usb_descriptor_header *eth_fs_function[] = { (struct usb_descriptor_header *) &rndis_iad_descriptor, /* control interface matches ACM, not Ethernet */ (struct usb_descriptor_header *) &rndis_control_intf, @@ -242,7 +242,7 @@ static struct usb_descriptor_header *eth_fs_function[] __initdata = { /* high speed support: */ -static struct usb_endpoint_descriptor hs_notify_desc __initdata = { +static struct usb_endpoint_descriptor hs_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -251,7 +251,7 @@ static struct usb_endpoint_descriptor hs_notify_desc __initdata = { .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT), .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, }; -static struct usb_endpoint_descriptor hs_in_desc __initdata = { +static struct usb_endpoint_descriptor hs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -260,7 +260,7 @@ static struct usb_endpoint_descriptor hs_in_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_endpoint_descriptor hs_out_desc __initdata = { +static struct usb_endpoint_descriptor hs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -269,7 +269,7 @@ static struct usb_endpoint_descriptor hs_out_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_descriptor_header *eth_hs_function[] __initdata = { +static struct usb_descriptor_header *eth_hs_function[] = { (struct usb_descriptor_header *) &rndis_iad_descriptor, /* control interface matches ACM, not Ethernet */ (struct usb_descriptor_header *) &rndis_control_intf, @@ -594,7 +594,7 @@ static void rndis_close(struct gether *geth) /* ethernet function driver setup/binding */ -static int __init +static int rndis_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; @@ -786,7 +786,8 @@ static inline bool can_support_rndis(struct usb_configuration *c) * Caller must have called @gether_setup(). Caller is also responsible * for calling @gether_cleanup() before module unload. */ -int __init rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +int +rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) { struct f_rndis *rndis; int status; diff --git a/drivers/usb/gadget/f_uvc.c b/drivers/usb/gadget/f_uvc.c new file mode 100644 index 00000000000..fc2611f8b32 --- /dev/null +++ b/drivers/usb/gadget/f_uvc.c @@ -0,0 +1,661 @@ +/* + * uvc_gadget.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/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/usb/video.h> +#include <linux/vmalloc.h> +#include <linux/wait.h> + +#include <media/v4l2-dev.h> +#include <media/v4l2-event.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(512), + .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 v4l2_event v4l2_event; + struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; + + if (uvc->event_setup_out) { + uvc->event_setup_out = 0; + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_DATA; + uvc_event->data.length = req->actual; + memcpy(&uvc_event->data.data, req->buf, req->actual); + v4l2_event_queue(uvc->vdev, &v4l2_event); + } +} + +static int +uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct uvc_device *uvc = to_uvc(f); + struct v4l2_event v4l2_event; + struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; + + /* 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(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_SETUP; + memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req)); + v4l2_event_queue(uvc->vdev, &v4l2_event); + + return 0; +} + +static int +uvc_function_get_alt(struct usb_function *f, unsigned interface) +{ + struct uvc_device *uvc = to_uvc(f); + + INFO(f->config->cdev, "uvc_function_get_alt(%u)\n", interface); + + if (interface == uvc->control_intf) + return 0; + else if (interface != uvc->streaming_intf) + return -EINVAL; + else + return uvc->state == UVC_STATE_STREAMING ? 1 : 0; +} + +static int +uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) +{ + struct uvc_device *uvc = to_uvc(f); + struct v4l2_event v4l2_event; + struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; + + 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(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_CONNECT; + uvc_event->speed = f->config->cdev->gadget->speed; + v4l2_event_queue(uvc->vdev, &v4l2_event); + + 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(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_STREAMOFF; + v4l2_event_queue(uvc->vdev, &v4l2_event); + + 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(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_STREAMON; + v4l2_event_queue(uvc->vdev, &v4l2_event); + + 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 v4l2_event v4l2_event; + + INFO(f->config->cdev, "uvc_function_disable\n"); + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_DISCONNECT; + v4l2_event_queue(uvc->vdev, &v4l2_event); + + 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; + + video->parent = &cdev->gadget->dev; + 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); + + 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; + + /* 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.get_alt = uvc_function_get_alt; + 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"); + diff --git a/drivers/usb/gadget/f_uvc.h b/drivers/usb/gadget/f_uvc.h new file mode 100644 index 00000000000..8a5db7c4fe7 --- /dev/null +++ b/drivers/usb/gadget/f_uvc.h @@ -0,0 +1,376 @@ +/* + * f_uvc.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#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_ */ + diff --git a/drivers/usb/gadget/fsl_mx3_udc.c b/drivers/usb/gadget/fsl_mxc_udc.c index 20a802ecaa1..d0b8bde59e5 100644 --- a/drivers/usb/gadget/fsl_mx3_udc.c +++ b/drivers/usb/gadget/fsl_mxc_udc.c @@ -50,12 +50,14 @@ int fsl_udc_clk_init(struct platform_device *pdev) goto egusb; } - freq = clk_get_rate(mxc_usb_clk); - if (pdata->phy_mode != FSL_USB2_PHY_ULPI && - (freq < 59999000 || freq > 60001000)) { - dev_err(&pdev->dev, "USB_CLK=%lu, should be 60MHz\n", freq); - ret = -EINVAL; - goto eclkrate; + if (!cpu_is_mx51()) { + freq = clk_get_rate(mxc_usb_clk); + if (pdata->phy_mode != FSL_USB2_PHY_ULPI && + (freq < 59999000 || freq > 60001000)) { + dev_err(&pdev->dev, "USB_CLK=%lu, should be 60MHz\n", freq); + ret = -EINVAL; + goto eclkrate; + } } ret = clk_enable(mxc_usb_clk); diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c index fa3d142ba64..08a9a62a39e 100644 --- a/drivers/usb/gadget/fsl_udc_core.c +++ b/drivers/usb/gadget/fsl_udc_core.c @@ -489,7 +489,7 @@ static int fsl_ep_enable(struct usb_ep *_ep, case USB_ENDPOINT_XFER_ISOC: /* Calculate transactions needed for high bandwidth iso */ mult = (unsigned char)(1 + ((max >> 11) & 0x03)); - max = max & 0x8ff; /* bit 0~10 */ + max = max & 0x7ff; /* bit 0~10 */ /* 3 transactions at most */ if (mult > 3) goto en_done; diff --git a/drivers/usb/gadget/g_ffs.c b/drivers/usb/gadget/g_ffs.c new file mode 100644 index 00000000000..4b0e4a040d6 --- /dev/null +++ b/drivers/usb/gadget/g_ffs.c @@ -0,0 +1,426 @@ +#include <linux/module.h> +#include <linux/utsname.h> + + +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ + +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS +# if defined USB_ETH_RNDIS +# undef USB_ETH_RNDIS +# endif +# ifdef CONFIG_USB_FUNCTIONFS_RNDIS +# define USB_ETH_RNDIS y +# endif + +# include "f_ecm.c" +# include "f_subset.c" +# ifdef USB_ETH_RNDIS +# include "f_rndis.c" +# include "rndis.c" +# endif +# include "u_ether.c" + +static u8 gfs_hostaddr[ETH_ALEN]; +#else +# if !defined CONFIG_USB_FUNCTIONFS_GENERIC +# define CONFIG_USB_FUNCTIONFS_GENERIC +# endif +# define gether_cleanup() do { } while (0) +# define gether_setup(gadget, hostaddr) ((int)0) +#endif + +#include "f_fs.c" + + +#define DRIVER_NAME "g_ffs" +#define DRIVER_DESC "USB Function Filesystem" +#define DRIVER_VERSION "24 Aug 2004" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Michal Nazarewicz"); +MODULE_LICENSE("GPL"); + + +static unsigned short gfs_vendor_id = 0x0525; /* XXX NetChip */ +static unsigned short gfs_product_id = 0xa4ac; /* XXX */ + +static struct usb_device_descriptor gfs_dev_desc = { + .bLength = sizeof gfs_dev_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + /* Vendor and product id can be overridden by module parameters. */ + /* .idVendor = cpu_to_le16(gfs_vendor_id), */ + /* .idProduct = cpu_to_le16(gfs_product_id), */ + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +#define GFS_MODULE_PARAM_DESC(name, field) \ + MODULE_PARM_DESC(name, "Value of the " #field " field of the device descriptor sent to the host. Takes effect only prior to the user-space driver registering to the FunctionFS.") + +module_param_named(usb_class, gfs_dev_desc.bDeviceClass, byte, 0644); +GFS_MODULE_PARAM_DESC(usb_class, bDeviceClass); +module_param_named(usb_subclass, gfs_dev_desc.bDeviceSubClass, byte, 0644); +GFS_MODULE_PARAM_DESC(usb_subclass, bDeviceSubClass); +module_param_named(usb_protocol, gfs_dev_desc.bDeviceProtocol, byte, 0644); +GFS_MODULE_PARAM_DESC(usb_protocol, bDeviceProtocol); +module_param_named(usb_vendor, gfs_vendor_id, ushort, 0644); +GFS_MODULE_PARAM_DESC(usb_vendor, idVendor); +module_param_named(usb_product, gfs_product_id, ushort, 0644); +GFS_MODULE_PARAM_DESC(usb_product, idProduct); + + + +static const struct usb_descriptor_header *gfs_otg_desc[] = { + (const struct usb_descriptor_header *) + &(const struct usb_otg_descriptor) { + .bLength = sizeof(struct usb_otg_descriptor), + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, + }, + + NULL +}; + +/* string IDs are assigned dynamically */ + +enum { + GFS_STRING_MANUFACTURER_IDX, + GFS_STRING_PRODUCT_IDX, +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + GFS_STRING_RNDIS_CONFIG_IDX, +#endif +#ifdef CONFIG_USB_FUNCTIONFS_ETH + GFS_STRING_ECM_CONFIG_IDX, +#endif +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC + GFS_STRING_GENERIC_CONFIG_IDX, +#endif +}; + +static char gfs_manufacturer[50]; +static const char gfs_driver_desc[] = DRIVER_DESC; +static const char gfs_short_name[] = DRIVER_NAME; + +static struct usb_string gfs_strings[] = { + [GFS_STRING_MANUFACTURER_IDX].s = gfs_manufacturer, + [GFS_STRING_PRODUCT_IDX].s = gfs_driver_desc, +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + [GFS_STRING_RNDIS_CONFIG_IDX].s = "FunctionFS + RNDIS", +#endif +#ifdef CONFIG_USB_FUNCTIONFS_ETH + [GFS_STRING_ECM_CONFIG_IDX].s = "FunctionFS + ECM", +#endif +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC + [GFS_STRING_GENERIC_CONFIG_IDX].s = "FunctionFS", +#endif + { } /* end of list */ +}; + +static struct usb_gadget_strings *gfs_dev_strings[] = { + &(struct usb_gadget_strings) { + .language = 0x0409, /* en-us */ + .strings = gfs_strings, + }, + NULL, +}; + + +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS +static int gfs_do_rndis_config(struct usb_configuration *c); + +static struct usb_configuration gfs_rndis_config_driver = { + .label = "FunctionFS + RNDIS", + .bind = gfs_do_rndis_config, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; +# define gfs_add_rndis_config(cdev) \ + usb_add_config(cdev, &gfs_rndis_config_driver) +#else +# define gfs_add_rndis_config(cdev) 0 +#endif + + +#ifdef CONFIG_USB_FUNCTIONFS_ETH +static int gfs_do_ecm_config(struct usb_configuration *c); + +static struct usb_configuration gfs_ecm_config_driver = { + .label = "FunctionFS + ECM", + .bind = gfs_do_ecm_config, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; +# define gfs_add_ecm_config(cdev) \ + usb_add_config(cdev, &gfs_ecm_config_driver) +#else +# define gfs_add_ecm_config(cdev) 0 +#endif + + +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC +static int gfs_do_generic_config(struct usb_configuration *c); + +static struct usb_configuration gfs_generic_config_driver = { + .label = "FunctionFS", + .bind = gfs_do_generic_config, + .bConfigurationValue = 2, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; +# define gfs_add_generic_config(cdev) \ + usb_add_config(cdev, &gfs_generic_config_driver) +#else +# define gfs_add_generic_config(cdev) 0 +#endif + + +static int gfs_bind(struct usb_composite_dev *cdev); +static int gfs_unbind(struct usb_composite_dev *cdev); + +static struct usb_composite_driver gfs_driver = { + .name = gfs_short_name, + .dev = &gfs_dev_desc, + .strings = gfs_dev_strings, + .bind = gfs_bind, + .unbind = gfs_unbind, +}; + + +static struct ffs_data *gfs_ffs_data; +static unsigned long gfs_registered; + + +static int gfs_init(void) +{ + ENTER(); + + return functionfs_init(); +} +module_init(gfs_init); + +static void gfs_exit(void) +{ + ENTER(); + + if (test_and_clear_bit(0, &gfs_registered)) + usb_composite_unregister(&gfs_driver); + + functionfs_cleanup(); +} +module_exit(gfs_exit); + + +static int functionfs_ready_callback(struct ffs_data *ffs) +{ + int ret; + + ENTER(); + + if (WARN_ON(test_and_set_bit(0, &gfs_registered))) + return -EBUSY; + + gfs_ffs_data = ffs; + ret = usb_composite_register(&gfs_driver); + if (unlikely(ret < 0)) + clear_bit(0, &gfs_registered); + return ret; +} + +static void functionfs_closed_callback(struct ffs_data *ffs) +{ + ENTER(); + + if (test_and_clear_bit(0, &gfs_registered)) + usb_composite_unregister(&gfs_driver); +} + + +static int functionfs_check_dev_callback(const char *dev_name) +{ + return 0; +} + + + +static int gfs_bind(struct usb_composite_dev *cdev) +{ + int ret; + + ENTER(); + + if (WARN_ON(!gfs_ffs_data)) + return -ENODEV; + + ret = gether_setup(cdev->gadget, gfs_hostaddr); + if (unlikely(ret < 0)) + goto error_quick; + + gfs_dev_desc.idVendor = cpu_to_le16(gfs_vendor_id); + gfs_dev_desc.idProduct = cpu_to_le16(gfs_product_id); + + snprintf(gfs_manufacturer, sizeof gfs_manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + cdev->gadget->name); + ret = usb_string_id(cdev); + if (unlikely(ret < 0)) + goto error; + gfs_strings[GFS_STRING_MANUFACTURER_IDX].id = ret; + gfs_dev_desc.iManufacturer = ret; + + ret = usb_string_id(cdev); + if (unlikely(ret < 0)) + goto error; + gfs_strings[GFS_STRING_PRODUCT_IDX].id = ret; + gfs_dev_desc.iProduct = ret; + +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + ret = usb_string_id(cdev); + if (unlikely(ret < 0)) + goto error; + gfs_strings[GFS_STRING_RNDIS_CONFIG_IDX].id = ret; + gfs_rndis_config_driver.iConfiguration = ret; +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_ETH + ret = usb_string_id(cdev); + if (unlikely(ret < 0)) + goto error; + gfs_strings[GFS_STRING_ECM_CONFIG_IDX].id = ret; + gfs_ecm_config_driver.iConfiguration = ret; +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC + ret = usb_string_id(cdev); + if (unlikely(ret < 0)) + goto error; + gfs_strings[GFS_STRING_GENERIC_CONFIG_IDX].id = ret; + gfs_generic_config_driver.iConfiguration = ret; +#endif + + ret = functionfs_bind(gfs_ffs_data, cdev); + if (unlikely(ret < 0)) + goto error; + + ret = gfs_add_rndis_config(cdev); + if (unlikely(ret < 0)) + goto error_unbind; + + ret = gfs_add_ecm_config(cdev); + if (unlikely(ret < 0)) + goto error_unbind; + + ret = gfs_add_generic_config(cdev); + if (unlikely(ret < 0)) + goto error_unbind; + + return 0; + +error_unbind: + functionfs_unbind(gfs_ffs_data); +error: + gether_cleanup(); +error_quick: + gfs_ffs_data = NULL; + return ret; +} + +static int gfs_unbind(struct usb_composite_dev *cdev) +{ + ENTER(); + + /* We may have been called in an error recovery frem + * composite_bind() after gfs_unbind() failure so we need to + * check if gfs_ffs_data is not NULL since gfs_bind() handles + * all error recovery itself. I'd rather we werent called + * from composite on orror recovery, but what you're gonna + * do...? */ + + if (gfs_ffs_data) { + gether_cleanup(); + functionfs_unbind(gfs_ffs_data); + gfs_ffs_data = NULL; + } + + return 0; +} + + +static int __gfs_do_config(struct usb_configuration *c, + int (*eth)(struct usb_configuration *c, u8 *ethaddr), + u8 *ethaddr) +{ + int ret; + + if (WARN_ON(!gfs_ffs_data)) + return -ENODEV; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = gfs_otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + if (eth) { + ret = eth(c, ethaddr); + if (unlikely(ret < 0)) + return ret; + } + + ret = functionfs_add(c->cdev, c, gfs_ffs_data); + if (unlikely(ret < 0)) + return ret; + + return 0; +} + +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS +static int gfs_do_rndis_config(struct usb_configuration *c) +{ + ENTER(); + + return __gfs_do_config(c, rndis_bind_config, gfs_hostaddr); +} +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_ETH +static int gfs_do_ecm_config(struct usb_configuration *c) +{ + ENTER(); + + return __gfs_do_config(c, + can_support_ecm(c->cdev->gadget) + ? ecm_bind_config : geth_bind_config, + gfs_hostaddr); +} +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC +static int gfs_do_generic_config(struct usb_configuration *c) +{ + ENTER(); + + return __gfs_do_config(c, NULL, NULL); +} +#endif diff --git a/drivers/usb/gadget/hid.c b/drivers/usb/gadget/hid.c new file mode 100644 index 00000000000..775722686ed --- /dev/null +++ b/drivers/usb/gadget/hid.c @@ -0,0 +1,298 @@ +/* + * hid.c -- HID Composite driver + * + * Based on multi.c + * + * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/list.h> + +#define DRIVER_DESC "HID Gadget" +#define DRIVER_VERSION "2010/03/16" + +/*-------------------------------------------------------------------------*/ + +#define HIDG_VENDOR_NUM 0x0525 /* XXX NetChip */ +#define HIDG_PRODUCT_NUM 0xa4ac /* Linux-USB HID gadget */ + +/*-------------------------------------------------------------------------*/ + +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ + +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#include "f_hid.c" + + +struct hidg_func_node { + struct list_head node; + struct hidg_func_descriptor *func; +}; + +static LIST_HEAD(hidg_func_list); + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + + /* .bDeviceClass = USB_CLASS_COMM, */ + /* .bDeviceSubClass = 0, */ + /* .bDeviceProtocol = 0, */ + .bDeviceClass = 0xEF, + .bDeviceSubClass = 2, + .bDeviceProtocol = 1, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id can be overridden by module parameters. */ + .idVendor = cpu_to_le16(HIDG_VENDOR_NUM), + .idProduct = cpu_to_le16(HIDG_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 + +static char manufacturer[50]; + +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer, + [STRING_PRODUCT_IDX].s = DRIVER_DESC, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + + + +/****************************** Configurations ******************************/ + +static int __init do_config(struct usb_configuration *c) +{ + struct hidg_func_node *e; + int func = 0, status = 0; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + list_for_each_entry(e, &hidg_func_list, node) { + status = hidg_bind_config(c, e->func, func++); + if (status) + break; + } + + return status; +} + +static struct usb_configuration config_driver = { + .label = "HID Gadget", + .bind = do_config, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/****************************** Gadget Bind ******************************/ + +static int __init hid_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct list_head *tmp; + int status, gcnum, funcs = 0; + + list_for_each(tmp, &hidg_func_list) + funcs++; + + if (!funcs) + return -ENODEV; + + /* set up HID */ + status = ghid_setup(cdev->gadget, funcs); + if (status < 0) + return status; + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum); + else + device_desc.bcdDevice = cpu_to_le16(0x0300 | 0x0099); + + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + /* device descriptor strings: manufacturer, product */ + snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + gadget->name); + status = usb_string_id(cdev); + if (status < 0) + return status; + strings_dev[STRING_MANUFACTURER_IDX].id = status; + device_desc.iManufacturer = status; + + status = usb_string_id(cdev); + if (status < 0) + return status; + strings_dev[STRING_PRODUCT_IDX].id = status; + device_desc.iProduct = status; + + /* register our configuration */ + status = usb_add_config(cdev, &config_driver); + if (status < 0) + return status; + + dev_info(&gadget->dev, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + + return 0; +} + +static int __exit hid_unbind(struct usb_composite_dev *cdev) +{ + ghid_cleanup(); + return 0; +} + +static int __init hidg_plat_driver_probe(struct platform_device *pdev) +{ + struct hidg_func_descriptor *func = pdev->dev.platform_data; + struct hidg_func_node *entry; + + if (!func) { + dev_err(&pdev->dev, "Platform data missing\n"); + return -ENODEV; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->func = func; + list_add_tail(&entry->node, &hidg_func_list); + + return 0; +} + +static int __devexit hidg_plat_driver_remove(struct platform_device *pdev) +{ + struct hidg_func_node *e, *n; + + list_for_each_entry_safe(e, n, &hidg_func_list, node) { + list_del(&e->node); + kfree(e); + } + + return 0; +} + + +/****************************** Some noise ******************************/ + + +static struct usb_composite_driver hidg_driver = { + .name = "g_hid", + .dev = &device_desc, + .strings = dev_strings, + .bind = hid_bind, + .unbind = __exit_p(hid_unbind), +}; + +static struct platform_driver hidg_plat_driver = { + .remove = __devexit_p(hidg_plat_driver_remove), + .driver = { + .owner = THIS_MODULE, + .name = "hidg", + }, +}; + + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Fabien Chouteau, Peter Korsgaard"); +MODULE_LICENSE("GPL"); + +static int __init hidg_init(void) +{ + int status; + + status = platform_driver_probe(&hidg_plat_driver, + hidg_plat_driver_probe); + if (status < 0) + return status; + + status = usb_composite_register(&hidg_driver); + if (status < 0) + platform_driver_unregister(&hidg_plat_driver); + + return status; +} +module_init(hidg_init); + +static void __exit hidg_cleanup(void) +{ + platform_driver_unregister(&hidg_plat_driver); + usb_composite_unregister(&hidg_driver); +} +module_exit(hidg_cleanup); diff --git a/drivers/usb/gadget/pxa27x_udc.h b/drivers/usb/gadget/pxa27x_udc.h index ff61e4866e8..cd16231d8c7 100644 --- a/drivers/usb/gadget/pxa27x_udc.h +++ b/drivers/usb/gadget/pxa27x_udc.h @@ -360,7 +360,7 @@ struct pxa_ep { * Specific pxa endpoint data, needed for hardware initialization */ unsigned dir_in:1; - unsigned addr:3; + unsigned addr:4; unsigned config:2; unsigned interface:3; unsigned alternate:3; diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c index 124a8ccfdcd..1f73b485732 100644 --- a/drivers/usb/gadget/s3c-hsotg.c +++ b/drivers/usb/gadget/s3c-hsotg.c @@ -2145,6 +2145,7 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, u32 epctrl; u32 mps; int dir_in; + int ret = 0; dev_dbg(hsotg->dev, "%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n", @@ -2196,7 +2197,8 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { case USB_ENDPOINT_XFER_ISOC: dev_err(hsotg->dev, "no current ISOC support\n"); - return -EINVAL; + ret = -EINVAL; + goto out; case USB_ENDPOINT_XFER_BULK: epctrl |= S3C_DxEPCTL_EPType_Bulk; @@ -2235,8 +2237,9 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, /* enable the endpoint interrupt */ s3c_hsotg_ctrl_epint(hsotg, index, dir_in, 1); +out: spin_unlock_irqrestore(&hs_ep->lock, flags); - return 0; + return ret; } static int s3c_hsotg_ep_disable(struct usb_ep *ep) diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index 07f4178ad17..1da755a1c85 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -715,7 +715,7 @@ static u8 __init nibble(unsigned char c) return 0; } -static int __init get_ether_addr(const char *str, u8 *dev_addr) +static int get_ether_addr(const char *str, u8 *dev_addr) { if (str) { unsigned i; @@ -764,7 +764,7 @@ static struct device_type gadget_type = { * * Returns negative errno, or zero on success */ -int __init gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) +int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) { struct eth_dev *dev; struct net_device *net; diff --git a/drivers/usb/gadget/uvc.h b/drivers/usb/gadget/uvc.h new file mode 100644 index 00000000000..0a705e63c93 --- /dev/null +++ b/drivers/usb/gadget/uvc.h @@ -0,0 +1,241 @@ +/* + * uvc_gadget.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#ifndef _UVC_GADGET_H_ +#define _UVC_GADGET_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> +#include <linux/usb/ch9.h> + +#define UVC_EVENT_FIRST (V4L2_EVENT_PRIVATE_START + 0) +#define UVC_EVENT_CONNECT (V4L2_EVENT_PRIVATE_START + 0) +#define UVC_EVENT_DISCONNECT (V4L2_EVENT_PRIVATE_START + 1) +#define UVC_EVENT_STREAMON (V4L2_EVENT_PRIVATE_START + 2) +#define UVC_EVENT_STREAMOFF (V4L2_EVENT_PRIVATE_START + 3) +#define UVC_EVENT_SETUP (V4L2_EVENT_PRIVATE_START + 4) +#define UVC_EVENT_DATA (V4L2_EVENT_PRIVATE_START + 5) +#define UVC_EVENT_LAST (V4L2_EVENT_PRIVATE_START + 5) + +struct uvc_request_data +{ + unsigned int length; + __u8 data[60]; +}; + +struct uvc_event +{ + union { + enum usb_device_speed speed; + struct usb_ctrlrequest req; + struct uvc_request_data data; + }; +}; + +#define UVCIOC_SEND_RESPONSE _IOW('U', 1, struct uvc_request_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/usb.h> /* For usb_endpoint_* */ +#include <linux/usb/gadget.h> +#include <linux/videodev2.h> +#include <media/v4l2-fh.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 */ + 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 v4l2_fh vfh; + struct uvc_video *device; +}; + +#define to_uvc_file_handle(handle) \ + container_of(handle, struct uvc_file_handle, vfh) + +extern struct v4l2_file_operations uvc_v4l2_fops; + +/* ------------------------------------------------------------------------ + * 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_ */ + diff --git a/drivers/usb/gadget/uvc_queue.c b/drivers/usb/gadget/uvc_queue.c new file mode 100644 index 00000000000..43891991bf2 --- /dev/null +++ b/drivers/usb/gadget/uvc_queue.c @@ -0,0 +1,583 @@ +/* + * uvc_queue.c -- USB Video Class driver - Buffers management + * + * Copyright (C) 2005-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/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; +} + diff --git a/drivers/usb/gadget/uvc_queue.h b/drivers/usb/gadget/uvc_queue.h new file mode 100644 index 00000000000..7f5a33fe7ae --- /dev/null +++ b/drivers/usb/gadget/uvc_queue.h @@ -0,0 +1,89 @@ +#ifndef _UVC_QUEUE_H_ +#define _UVC_QUEUE_H_ + +#ifdef __KERNEL__ + +#include <linux/kernel.h> +#include <linux/poll.h> +#include <linux/videodev2.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_ */ + diff --git a/drivers/usb/gadget/uvc_v4l2.c b/drivers/usb/gadget/uvc_v4l2.c new file mode 100644 index 00000000000..a7989f29837 --- /dev/null +++ b/drivers/usb/gadget/uvc_v4l2.c @@ -0,0 +1,374 @@ +/* + * uvc_v4l2.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/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> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> + +#include "uvc.h" +#include "uvc_queue.h" + +/* -------------------------------------------------------------------------- + * Requests handling + */ + +static int +uvc_send_response(struct uvc_device *uvc, struct uvc_request_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(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 * fmt->fmt.pix.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 +uvc_v4l2_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_file_handle *handle; + int ret; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (handle == NULL) + return -ENOMEM; + + ret = v4l2_fh_init(&handle->vfh, vdev); + if (ret < 0) + goto error; + + ret = v4l2_event_init(&handle->vfh); + if (ret < 0) + goto error; + + ret = v4l2_event_alloc(&handle->vfh, 8); + if (ret < 0) + goto error; + + v4l2_fh_add(&handle->vfh); + + handle->device = &uvc->video; + file->private_data = &handle->vfh; + + uvc_function_connect(uvc); + return 0; + +error: + v4l2_fh_exit(&handle->vfh); + return ret; +} + +static int +uvc_v4l2_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_file_handle *handle = to_uvc_file_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; + v4l2_fh_del(&handle->vfh); + v4l2_fh_exit(&handle->vfh); + kfree(handle); + return 0; +} + +static long +uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data); + 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 VIDIOC_DQEVENT: + { + struct v4l2_event *event = arg; + + ret = v4l2_event_dequeue(&handle->vfh, event, + file->f_flags & O_NONBLOCK); + if (ret == 0 && event->type == UVC_EVENT_SETUP) { + struct uvc_event *uvc_event = (void *)&event->u.data; + + /* Tell the complete callback to generate an event for + * the next request that will be enqueued by + * uvc_event_write. + */ + uvc->event_setup_out = + !(uvc_event->req.bRequestType & USB_DIR_IN); + uvc->event_length = uvc_event->req.wLength; + } + + return ret; + } + + case VIDIOC_SUBSCRIBE_EVENT: + { + struct v4l2_event_subscription *sub = arg; + + if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST) + return -EINVAL; + + return v4l2_event_subscribe(&handle->vfh, arg); + } + + case VIDIOC_UNSUBSCRIBE_EVENT: + return v4l2_event_unsubscribe(&handle->vfh, arg); + + case UVCIOC_SEND_RESPONSE: + ret = uvc_send_response(uvc, arg); + break; + + default: + return -ENOIOCTLCMD; + } + + return ret; +} + +static long +uvc_v4l2_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return video_usercopy(file, cmd, arg, uvc_v4l2_do_ioctl); +} + +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); + struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data); + unsigned int mask = 0; + + poll_wait(file, &handle->vfh.events->wait, wait); + if (v4l2_event_pending(&handle->vfh)) + mask |= POLLPRI; + + mask |= uvc_queue_poll(&uvc->video.queue, file, wait); + + return mask; +} + +struct v4l2_file_operations uvc_v4l2_fops = { + .owner = THIS_MODULE, + .open = uvc_v4l2_open, + .release = uvc_v4l2_release, + .ioctl = uvc_v4l2_ioctl, + .mmap = uvc_v4l2_mmap, + .poll = uvc_v4l2_poll, +}; + diff --git a/drivers/usb/gadget/uvc_video.c b/drivers/usb/gadget/uvc_video.c new file mode 100644 index 00000000000..de8cbc46518 --- /dev/null +++ b/drivers/usb/gadget/uvc_video.c @@ -0,0 +1,386 @@ +/* + * uvc_video.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/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; +} + diff --git a/drivers/usb/gadget/webcam.c b/drivers/usb/gadget/webcam.c new file mode 100644 index 00000000000..417fd688769 --- /dev/null +++ b/drivers/usb/gadget/webcam.c @@ -0,0 +1,399 @@ +/* + * webcam.c -- USB webcam gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/usb/video.h> + +#include "f_uvc.h" + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#include "f_uvc.c" +#include "uvc_queue.c" +#include "uvc_v4l2.c" +#include "uvc_video.c" + +/* -------------------------------------------------------------------------- + * Device descriptor + */ + +#define WEBCAM_VENDOR_ID 0x1d6b /* Linux Foundation */ +#define WEBCAM_PRODUCT_ID 0x0102 /* Webcam A/V gadget */ +#define WEBCAM_DEVICE_BCD 0x0010 /* 0.10 */ + +static char webcam_vendor_label[] = "Linux Foundation"; +static char webcam_product_label[] = "Webcam gadget"; +static char webcam_config_label[] = "Video"; + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_DESCRIPTION_IDX 2 + +static struct usb_string webcam_strings[] = { + [STRING_MANUFACTURER_IDX].s = webcam_vendor_label, + [STRING_PRODUCT_IDX].s = webcam_product_label, + [STRING_DESCRIPTION_IDX].s = webcam_config_label, + { } +}; + +static struct usb_gadget_strings webcam_stringtab = { + .language = 0x0409, /* en-us */ + .strings = webcam_strings, +}; + +static struct usb_gadget_strings *webcam_device_strings[] = { + &webcam_stringtab, + NULL, +}; + +static struct usb_device_descriptor webcam_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, /* dynamic */ + .idVendor = cpu_to_le16(WEBCAM_VENDOR_ID), + .idProduct = cpu_to_le16(WEBCAM_PRODUCT_ID), + .bcdDevice = cpu_to_le16(WEBCAM_DEVICE_BCD), + .iManufacturer = 0, /* dynamic */ + .iProduct = 0, /* dynamic */ + .iSerialNumber = 0, /* dynamic */ + .bNumConfigurations = 0, /* dynamic */ +}; + +DECLARE_UVC_HEADER_DESCRIPTOR(1); + +static const struct UVC_HEADER_DESCRIPTOR(1) uvc_control_header = { + .bLength = UVC_DT_HEADER_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_HEADER, + .bcdUVC = cpu_to_le16(0x0100), + .wTotalLength = 0, /* dynamic */ + .dwClockFrequency = cpu_to_le32(48000000), + .bInCollection = 0, /* dynamic */ + .baInterfaceNr[0] = 0, /* dynamic */ +}; + +static const struct uvc_camera_terminal_descriptor uvc_camera_terminal = { + .bLength = UVC_DT_CAMERA_TERMINAL_SIZE(3), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_INPUT_TERMINAL, + .bTerminalID = 1, + .wTerminalType = cpu_to_le16(0x0201), + .bAssocTerminal = 0, + .iTerminal = 0, + .wObjectiveFocalLengthMin = cpu_to_le16(0), + .wObjectiveFocalLengthMax = cpu_to_le16(0), + .wOcularFocalLength = cpu_to_le16(0), + .bControlSize = 3, + .bmControls[0] = 2, + .bmControls[1] = 0, + .bmControls[2] = 0, +}; + +static const struct uvc_processing_unit_descriptor uvc_processing = { + .bLength = UVC_DT_PROCESSING_UNIT_SIZE(2), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_PROCESSING_UNIT, + .bUnitID = 2, + .bSourceID = 1, + .wMaxMultiplier = cpu_to_le16(16*1024), + .bControlSize = 2, + .bmControls[0] = 1, + .bmControls[1] = 0, + .iProcessing = 0, +}; + +static const struct uvc_output_terminal_descriptor uvc_output_terminal = { + .bLength = UVC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_OUTPUT_TERMINAL, + .bTerminalID = 3, + .wTerminalType = cpu_to_le16(0x0101), + .bAssocTerminal = 0, + .bSourceID = 2, + .iTerminal = 0, +}; + +DECLARE_UVC_INPUT_HEADER_DESCRIPTOR(1, 2); + +static const struct UVC_INPUT_HEADER_DESCRIPTOR(1, 2) uvc_input_header = { + .bLength = UVC_DT_INPUT_HEADER_SIZE(1, 2), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_INPUT_HEADER, + .bNumFormats = 2, + .wTotalLength = 0, /* dynamic */ + .bEndpointAddress = 0, /* dynamic */ + .bmInfo = 0, + .bTerminalLink = 3, + .bStillCaptureMethod = 0, + .bTriggerSupport = 0, + .bTriggerUsage = 0, + .bControlSize = 1, + .bmaControls[0][0] = 0, + .bmaControls[1][0] = 4, +}; + +static const struct uvc_format_uncompressed uvc_format_yuv = { + .bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_FORMAT_UNCOMPRESSED, + .bFormatIndex = 1, + .bNumFrameDescriptors = 2, + .guidFormat = + { 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}, + .bBitsPerPixel = 16, + .bDefaultFrameIndex = 1, + .bAspectRatioX = 0, + .bAspectRatioY = 0, + .bmInterfaceFlags = 0, + .bCopyProtect = 0, +}; + +DECLARE_UVC_FRAME_UNCOMPRESSED(1); +DECLARE_UVC_FRAME_UNCOMPRESSED(3); + +static const struct UVC_FRAME_UNCOMPRESSED(3) uvc_frame_yuv_360p = { + .bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(3), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_FRAME_UNCOMPRESSED, + .bFrameIndex = 1, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(640), + .wHeight = cpu_to_le16(360), + .dwMinBitRate = cpu_to_le32(18432000), + .dwMaxBitRate = cpu_to_le32(55296000), + .dwMaxVideoFrameBufferSize = cpu_to_le32(460800), + .dwDefaultFrameInterval = cpu_to_le32(666666), + .bFrameIntervalType = 3, + .dwFrameInterval[0] = cpu_to_le32(666666), + .dwFrameInterval[1] = cpu_to_le32(1000000), + .dwFrameInterval[2] = cpu_to_le32(5000000), +}; + +static const struct UVC_FRAME_UNCOMPRESSED(1) uvc_frame_yuv_720p = { + .bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_FRAME_UNCOMPRESSED, + .bFrameIndex = 2, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(1280), + .wHeight = cpu_to_le16(720), + .dwMinBitRate = cpu_to_le32(29491200), + .dwMaxBitRate = cpu_to_le32(29491200), + .dwMaxVideoFrameBufferSize = cpu_to_le32(1843200), + .dwDefaultFrameInterval = cpu_to_le32(5000000), + .bFrameIntervalType = 1, + .dwFrameInterval[0] = cpu_to_le32(5000000), +}; + +static const struct uvc_format_mjpeg uvc_format_mjpg = { + .bLength = UVC_DT_FORMAT_MJPEG_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_FORMAT_MJPEG, + .bFormatIndex = 2, + .bNumFrameDescriptors = 2, + .bmFlags = 0, + .bDefaultFrameIndex = 1, + .bAspectRatioX = 0, + .bAspectRatioY = 0, + .bmInterfaceFlags = 0, + .bCopyProtect = 0, +}; + +DECLARE_UVC_FRAME_MJPEG(1); +DECLARE_UVC_FRAME_MJPEG(3); + +static const struct UVC_FRAME_MJPEG(3) uvc_frame_mjpg_360p = { + .bLength = UVC_DT_FRAME_MJPEG_SIZE(3), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_FRAME_MJPEG, + .bFrameIndex = 1, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(640), + .wHeight = cpu_to_le16(360), + .dwMinBitRate = cpu_to_le32(18432000), + .dwMaxBitRate = cpu_to_le32(55296000), + .dwMaxVideoFrameBufferSize = cpu_to_le32(460800), + .dwDefaultFrameInterval = cpu_to_le32(666666), + .bFrameIntervalType = 3, + .dwFrameInterval[0] = cpu_to_le32(666666), + .dwFrameInterval[1] = cpu_to_le32(1000000), + .dwFrameInterval[2] = cpu_to_le32(5000000), +}; + +static const struct UVC_FRAME_MJPEG(1) uvc_frame_mjpg_720p = { + .bLength = UVC_DT_FRAME_MJPEG_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_FRAME_MJPEG, + .bFrameIndex = 2, + .bmCapabilities = 0, + .wWidth = cpu_to_le16(1280), + .wHeight = cpu_to_le16(720), + .dwMinBitRate = cpu_to_le32(29491200), + .dwMaxBitRate = cpu_to_le32(29491200), + .dwMaxVideoFrameBufferSize = cpu_to_le32(1843200), + .dwDefaultFrameInterval = cpu_to_le32(5000000), + .bFrameIntervalType = 1, + .dwFrameInterval[0] = cpu_to_le32(5000000), +}; + +static const struct uvc_color_matching_descriptor uvc_color_matching = { + .bLength = UVC_DT_COLOR_MATCHING_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_DT_COLOR_MATCHING, + .bColorPrimaries = 1, + .bTransferCharacteristics = 1, + .bMatrixCoefficients = 4, +}; + +static const struct uvc_descriptor_header * const uvc_control_cls[] = { + (const struct uvc_descriptor_header *) &uvc_control_header, + (const struct uvc_descriptor_header *) &uvc_camera_terminal, + (const struct uvc_descriptor_header *) &uvc_processing, + (const struct uvc_descriptor_header *) &uvc_output_terminal, + NULL, +}; + +static const struct uvc_descriptor_header * const uvc_fs_streaming_cls[] = { + (const struct uvc_descriptor_header *) &uvc_input_header, + (const struct uvc_descriptor_header *) &uvc_format_yuv, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvc_format_mjpg, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvc_color_matching, + NULL, +}; + +static const struct uvc_descriptor_header * const uvc_hs_streaming_cls[] = { + (const struct uvc_descriptor_header *) &uvc_input_header, + (const struct uvc_descriptor_header *) &uvc_format_yuv, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvc_format_mjpg, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvc_color_matching, + NULL, +}; + +/* -------------------------------------------------------------------------- + * USB configuration + */ + +static int __init +webcam_config_bind(struct usb_configuration *c) +{ + return uvc_bind_config(c, uvc_control_cls, uvc_fs_streaming_cls, + uvc_hs_streaming_cls); +} + +static struct usb_configuration webcam_config_driver = { + .label = webcam_config_label, + .bind = webcam_config_bind, + .bConfigurationValue = 1, + .iConfiguration = 0, /* dynamic */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = CONFIG_USB_GADGET_VBUS_DRAW / 2, +}; + +static int /* __init_or_exit */ +webcam_unbind(struct usb_composite_dev *cdev) +{ + return 0; +} + +static int __init +webcam_bind(struct usb_composite_dev *cdev) +{ + int ret; + + /* Allocate string descriptor numbers ... note that string contents + * can be overridden by the composite_dev glue. + */ + if ((ret = usb_string_id(cdev)) < 0) + goto error; + webcam_strings[STRING_MANUFACTURER_IDX].id = ret; + webcam_device_descriptor.iManufacturer = ret; + + if ((ret = usb_string_id(cdev)) < 0) + goto error; + webcam_strings[STRING_PRODUCT_IDX].id = ret; + webcam_device_descriptor.iProduct = ret; + + if ((ret = usb_string_id(cdev)) < 0) + goto error; + webcam_strings[STRING_DESCRIPTION_IDX].id = ret; + webcam_config_driver.iConfiguration = ret; + + /* Register our configuration. */ + if ((ret = usb_add_config(cdev, &webcam_config_driver)) < 0) + goto error; + + INFO(cdev, "Webcam Video Gadget\n"); + return 0; + +error: + webcam_unbind(cdev); + return ret; +} + +/* -------------------------------------------------------------------------- + * Driver + */ + +static struct usb_composite_driver webcam_driver = { + .name = "g_webcam", + .dev = &webcam_device_descriptor, + .strings = webcam_device_strings, + .bind = webcam_bind, + .unbind = webcam_unbind, +}; + +static int __init +webcam_init(void) +{ + return usb_composite_register(&webcam_driver); +} + +static void __exit +webcam_cleanup(void) +{ + usb_composite_unregister(&webcam_driver); +} + +module_init(webcam_init); +module_exit(webcam_cleanup); + +MODULE_AUTHOR("Laurent Pinchart"); +MODULE_DESCRIPTION("Webcam Video Gadget"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1.0"); + |