From 19c190f9e0fe926db28122a804111a7538dc3498 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Mon, 29 Mar 2010 17:36:44 +0200 Subject: USB: gadget: s3c-hsotg: Add missing unlock In an error handling case the lock is not unlocked. The return is converted to a goto, to share the unlock at the end of the function. A simplified version of the semantic patch that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @r exists@ expression E1; identifier f; @@ f (...) { <+... * spin_lock_irqsave (E1,...); ... when != E1 * return ...; ...+> } // Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/s3c-hsotg.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/s3c-hsotg.c b/drivers/usb/gadget/s3c-hsotg.c index 124a8ccfdcda..1f73b485732d 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) -- cgit v1.2.1 From 762253741942d8ddbaca1c1ee62d7ed57544332b Mon Sep 17 00:00:00 2001 From: Harro Haan Date: Mon, 1 Mar 2010 17:54:55 +0100 Subject: ARM: 5966/1: at91_udc HW glitch Add some delay to avoid reading CSR TXCOUNT too early after updating it. For more info about this HW glitch see: http://lists.arm.linux.org.uk/lurker/message/20090325.150843.f515c02f.en.html http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20100129/10ba0f8b/attachment.el http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20100203/09cdb3b4/attachment.el http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20100203/08b5b249/attachment.el First applied: "Fix soft lockup in at91 udc driver" http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=5965 Signed-off-by: Anti Sullin Signed-off-by: Harro Haan Acked-by: Remy Bohmer Acked-by: Andrew Victor Signed-off-by: Russell King --- drivers/usb/gadget/at91_udc.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/at91_udc.c b/drivers/usb/gadget/at91_udc.c index 12ac9cd32a07..b3bd22ef82ba 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; -- cgit v1.2.1 From 27729aadd31dafddaaf64c24f8ef6d0ff750f3aa Mon Sep 17 00:00:00 2001 From: Eric Lescouet Date: Sat, 24 Apr 2010 23:21:52 +0200 Subject: USB: make hcd.h public (drivers dependency) The usbcore headers: hcd.h and hub.h are shared between usbcore, HCDs and a couple of other drivers (e.g. USBIP modules). So, it makes sense to move them into a more public location and to cleanup dependency of those modules on kernel internal headers. This patch moves hcd.h from drivers/usb/core into include/linux/usb/ Signed-of-by: Eric Lescouet Cc: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/dummy_hcd.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/dummy_hcd.c b/drivers/usb/gadget/dummy_hcd.c index 5e0966485188..4f9e578cde9d 100644 --- a/drivers/usb/gadget/dummy_hcd.c +++ b/drivers/usb/gadget/dummy_hcd.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -55,9 +56,6 @@ #include -#include "../core/hcd.h" - - #define DRIVER_DESC "USB Host+Gadget Emulator" #define DRIVER_VERSION "02 May 2005" -- cgit v1.2.1 From aae86e8adb3bbf5256eebe76500cf7254365753c Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Mon, 15 Mar 2010 21:38:31 +0100 Subject: USB: f_mass_storage: dynamic buffers for better alignment "Static" buffers in fsg_buffhd structure (ie. fields which are arrays rather then pointers to dynamically allocated memory) are not aligned to any "big" power of two which may lead to poor DMA performance (copying "by hand" of head or tail) or no DMA at all even if otherwise hardware supports it. Therefore, this patch makes mass storage function use kmalloc()ed buffers which are (because of their size) page aligned (which should be enough for any hardware). Signed-off-by: Michal Nazarewicz Cc: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_mass_storage.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index f4911c09022e..218f51db9721 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -302,7 +302,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 @@ -2762,13 +2761,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; @@ -2871,6 +2876,7 @@ static void fsg_common_release(struct kref *ref) container_of(ref, struct fsg_common, ref); unsigned i = common->nluns; struct fsg_lun *lun = common->luns; + struct fsg_buffhd *bh; /* If the thread isn't already dead, tell it to exit now */ if (common->state != FSG_STATE_TERMINATED) { @@ -2892,6 +2898,13 @@ static void fsg_common_release(struct kref *ref) } kfree(common->luns); + + i = FSG_NUM_BUFFERS; + bh = common->buffhds; + do { + kfree(bh->buf); + } while (++bh, --i); + if (common->free_storage_on_release) kfree(common); } -- cgit v1.2.1 From 71adf118946957839a13aa4d1094183e05c6c094 Mon Sep 17 00:00:00 2001 From: Fabien Chouteau Date: Thu, 8 Apr 2010 09:31:15 +0200 Subject: USB: gadget: add HID gadget driver g_hid is a USB gadget driver implementing the Human Interface Device class specification. The driver handles basic HID protocol handling in the kernel, and allows userspace to read/write HID reports trough /dev/hidgX character devices. Signed-off-by: Fabien Chouteau Signed-off-by: Peter Korsgaard Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 11 + drivers/usb/gadget/Makefile | 2 + drivers/usb/gadget/f_hid.c | 673 ++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/hid.c | 288 +++++++++++++++++++ 4 files changed, 974 insertions(+) create mode 100644 drivers/usb/gadget/f_hid.c create mode 100644 drivers/usb/gadget/hid.c (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 11a3e0fa4331..390c13372bdc 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -863,6 +863,17 @@ 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. diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 43b51da8d727..3075ff9cae62 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -43,6 +43,7 @@ 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 obj-$(CONFIG_USB_ZERO) += g_zero.o @@ -55,6 +56,7 @@ 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 diff --git a/drivers/usb/gadget/f_hid.c b/drivers/usb/gadget/f_hid.c new file mode 100644 index 000000000000..1e00ff9866af --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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/hid.c b/drivers/usb/gadget/hid.c new file mode 100644 index 000000000000..b77e97754b4a --- /dev/null +++ b/drivers/usb/gadget/hid.c @@ -0,0 +1,288 @@ +/* + * hid.c -- HID Composite driver + * + * Based on multi.c + * + * Copyright (C) 2010 Fabien Chouteau + * + * 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 +#include +#include + +#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) +{ + platform_driver_probe(&hidg_plat_driver, hidg_plat_driver_probe); + return usb_composite_register(&hidg_driver); +} +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); -- cgit v1.2.1 From dd0543ecc638947d67bdd3a8a41b95ed3c7b885e Mon Sep 17 00:00:00 2001 From: Robert Lukassen Date: Tue, 30 Mar 2010 14:14:01 +0200 Subject: usb: gadget: Allow larger configuration descriptors The composite framework allows gadgets with more than one function. This can lead to situations where the configuration descriptor is larger than the maximum of 512 bytes currently allowed by the composite framework. This patch proposes to double that limit to 1024. Signed-off-by: Robert Lukassen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 09289bb1e20f..5465d8767f9a 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; -- cgit v1.2.1 From 0fb2c2a1692b8f77be25d38f8802b0142cb6e6bc Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Mon, 29 Mar 2010 14:01:32 +0200 Subject: USB: gadget: f_mass_storage: per function Mass Storage Function (MSF) used the same descriptors for each usb_function instance (meaning usb_function::descriptors of different functions pointed to the same static area (the same was true for usb_function::hs_descriptors)). This would leads to problems if MSF were used in several USB configurations with different interface and/or endpoint numbers. Descriptors for all configurations would have interface/endpoint numbers overwritten by the values valid for the last configuration. This patch adds code that copies the descriptors each time MSF is added to USB configuration (that is for each usb_function). Signed-off-by: Michal Nazarewicz Cc: Kyungmin Park --- drivers/usb/gadget/f_mass_storage.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 218f51db9721..9dbe86dec332 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -2919,6 +2919,8 @@ 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); } @@ -2959,7 +2961,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; @@ -2991,7 +2995,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; @@ -3006,11 +3014,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; } -- cgit v1.2.1 From 0ff8d1b3c858ea7c8daa54f7577971a76d04d283 Mon Sep 17 00:00:00 2001 From: Matt Reimer Date: Thu, 1 Apr 2010 13:44:04 -0700 Subject: USB: pxa27x_udc: use four bits to store endpoint addresses Endpoint addresses on pxa27x can be programmed as 1-15, but since only three bits were being used to store the endpoint number it was possible to overflow. Signed-off-by: Matt Reimer Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/pxa27x_udc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/pxa27x_udc.h b/drivers/usb/gadget/pxa27x_udc.h index ff61e4866e8a..cd16231d8c73 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; -- cgit v1.2.1 From f48cf80f93ba974eb3201ab2d0f2c4cef950f3fc Mon Sep 17 00:00:00 2001 From: Fabien Chouteau Date: Fri, 23 Apr 2010 14:21:26 +0200 Subject: USB: Composite framework: Add suspended sysfs entry This patch adds a sysfs entry (/sys/devices/platform/_UDC_/gadget/suspended) to show the suspend state of an USB composite gadget. Signed-off-by: Fabien Chouteau Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 5465d8767f9a..f9aff1bbcb3e 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -898,6 +898,18 @@ static void composite_disconnect(struct usb_gadget *gadget) /*-------------------------------------------------------------------------*/ +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 /* __init_or_exit */ composite_unbind(struct usb_gadget *gadget) { @@ -944,6 +956,7 @@ composite_unbind(struct usb_gadget *gadget) } kfree(cdev); set_gadget_data(gadget, NULL); + device_remove_file(&gadget->dev, &dev_attr_suspended); composite = NULL; } @@ -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; } /*-------------------------------------------------------------------------*/ -- cgit v1.2.1 From 31436a1a64b8eed834fba5d570038dd676e04842 Mon Sep 17 00:00:00 2001 From: Fabien Chouteau Date: Mon, 26 Apr 2010 12:34:54 +0200 Subject: USB: Mass storage gadget: Handle eject request This patch adds handling of the "Start/Stop Unit" SCSI request to simulate media ejection. Signed-off-by: Fabien Chouteau Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_mass_storage.c | 48 ++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 9dbe86dec332..c904aa39ad84 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 @@ -1384,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; } -- cgit v1.2.1 From da01c7a49bfc94dbfa66af5ca5e1063b10bc6a10 Mon Sep 17 00:00:00 2001 From: Peter Korsgaard Date: Mon, 26 Apr 2010 10:05:06 +0200 Subject: USB: g_hid: unregister platform driver on probe/usb_composite_register errors Otherwise reloads will fail. Signed-off-by: Peter Korsgaard Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/hid.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/hid.c b/drivers/usb/gadget/hid.c index b77e97754b4a..775722686ed8 100644 --- a/drivers/usb/gadget/hid.c +++ b/drivers/usb/gadget/hid.c @@ -275,8 +275,18 @@ MODULE_LICENSE("GPL"); static int __init hidg_init(void) { - platform_driver_probe(&hidg_plat_driver, hidg_plat_driver_probe); - return usb_composite_register(&hidg_driver); + 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); -- cgit v1.2.1 From 88e3b59b5adce5b12e205af0e34d518ba0dcdc0c Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Tue, 4 May 2010 10:03:01 -0500 Subject: USB: mxc: gadget: Fix bitfield for calculating maximum packet size The max packet length bit mask used for isochronous endpoints should be 0x7FF instead of 0x8FF. 0x8FF will actually clear higher-order bits in the max packet length field. This patch applies to 2.6.34-rc6. Signed-off-by: Dinh Nguyen Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/fsl_udc_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c index fa3d142ba64d..08a9a62a39e3 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; -- cgit v1.2.1 From cdda479f15cd13fa50a913ca85129c0437cc7b91 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 2 May 2010 20:57:41 +0200 Subject: USB gadget: video class function driver This USB video class function driver implements a video capture device from the host's point of view. It creates a V4L2 output device on the gadget's side to transfer data from a userspace application over USB. The UVC-specific descriptors are passed by the gadget driver to the UVC function driver, making them completely configurable without any modification to the function's driver code. Signed-off-by: Laurent Pinchart Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_uvc.c | 661 +++++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/f_uvc.h | 376 +++++++++++++++++++++++ drivers/usb/gadget/uvc.h | 241 +++++++++++++++ drivers/usb/gadget/uvc_queue.c | 583 ++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/uvc_queue.h | 89 ++++++ drivers/usb/gadget/uvc_v4l2.c | 374 +++++++++++++++++++++++ drivers/usb/gadget/uvc_video.c | 386 ++++++++++++++++++++++++ 7 files changed, 2710 insertions(+) create mode 100644 drivers/usb/gadget/f_uvc.c create mode 100644 drivers/usb/gadget/f_uvc.h create mode 100644 drivers/usb/gadget/uvc.h create mode 100644 drivers/usb/gadget/uvc_queue.c create mode 100644 drivers/usb/gadget/uvc_queue.h create mode 100644 drivers/usb/gadget/uvc_v4l2.c create mode 100644 drivers/usb/gadget/uvc_video.c (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/f_uvc.c b/drivers/usb/gadget/f_uvc.c new file mode 100644 index 000000000000..fc2611f8b326 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 000000000000..8a5db7c4fe7c --- /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 + +#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/uvc.h b/drivers/usb/gadget/uvc.h new file mode 100644 index 000000000000..0a705e63c936 --- /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 +#include +#include + +#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 /* For usb_endpoint_* */ +#include +#include +#include + +#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 000000000000..43891991bf21 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 000000000000..7f5a33fe7ae2 --- /dev/null +++ b/drivers/usb/gadget/uvc_queue.h @@ -0,0 +1,89 @@ +#ifndef _UVC_QUEUE_H_ +#define _UVC_QUEUE_H_ + +#ifdef __KERNEL__ + +#include +#include +#include + +/* 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 000000000000..a7989f29837e --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 000000000000..de8cbc46518d --- /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 +#include +#include +#include +#include + +#include + +#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; +} + -- cgit v1.2.1 From a9914127e834acf648a96c72b4e271dc0c1c7c74 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 2 May 2010 20:57:42 +0200 Subject: USB gadget: Webcam device This webcam gadget instantiates a UVC camera (360p and 720p resolutions in YUYV and MJPEG). Signed-off-by: Laurent Pinchart Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 9 +- drivers/usb/gadget/Makefile | 2 + drivers/usb/gadget/webcam.c | 399 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/gadget/webcam.c (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 390c13372bdc..10bfb4197599 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -877,8 +877,15 @@ config USB_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" + 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 3075ff9cae62..03a27d921c26 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -45,6 +45,7 @@ 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 @@ -59,4 +60,5 @@ 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/webcam.c b/drivers/usb/gadget/webcam.c new file mode 100644 index 000000000000..417fd6887698 --- /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 +#include +#include + +#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"); + -- cgit v1.2.1 From 24337c133ff92ba8d7c42819db17f7f2b0de3129 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 5 May 2010 15:46:26 -0700 Subject: USB: gadget webcam: depends on VIDEO_DEV g_webcam uses v4l[2] interfaces, so it should depend on VIDEO_DEV. ERROR: "v4l2_event_unsubscribe" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_event_queue" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "video_device_release" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "video_usercopy" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_event_dequeue" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "video_register_device" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "video_device_alloc" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_event_subscribe" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "video_unregister_device" [drivers/usb/gadget/g_webcam.ko] ndefined! ERROR: "v4l2_event_pending" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_fh_init" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_event_init" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "video_devdata" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_event_alloc" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_fh_add" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_fh_del" [drivers/usb/gadget/g_webcam.ko] undefined! ERROR: "v4l2_fh_exit" [drivers/usb/gadget/g_webcam.ko] undefined! Signed-off-by: Randy Dunlap Acked-by: Laurent Pinchart Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 10bfb4197599..96e1494723ad 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -879,6 +879,7 @@ config USB_G_HID # 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 -- cgit v1.2.1 From 28824b18ac4705e876a282a15ea0de8fc957551f Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 5 May 2010 12:53:13 +0200 Subject: USB: gadget: __init and __exit tags removed __init, __initdata and __exit tags have have been removed from various files to make it possible for gadgets that do not use the __init/__exit tags to use those. Files in question are related to: * the core composite framework, * the mass storage function (fixing a section mismatch) and * ethernet driver (ACM, ECM, RNDIS). Signed-off-by: Michal Nazarewicz Cc: Kyungmin Park Cc: Marek Szyprowski Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 21 ++++++++++----------- drivers/usb/gadget/config.c | 4 ++-- drivers/usb/gadget/epautoconf.c | 12 ++++++------ drivers/usb/gadget/f_acm.c | 32 ++++++++++++++++---------------- drivers/usb/gadget/f_ecm.c | 33 +++++++++++++++++---------------- drivers/usb/gadget/f_mass_storage.c | 2 +- drivers/usb/gadget/f_rndis.c | 33 +++++++++++++++++---------------- drivers/usb/gadget/u_ether.c | 4 ++-- 8 files changed, 71 insertions(+), 70 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index f9aff1bbcb3e..dd6d1905c148 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -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 */ @@ -910,7 +910,7 @@ static ssize_t composite_show_suspended(struct device *dev, static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL); -static void /* __init_or_exit */ +static void composite_unbind(struct usb_gadget *gadget) { struct usb_composite_dev *cdev = get_gadget_data(gadget); @@ -960,7 +960,7 @@ composite_unbind(struct usb_gadget *gadget) 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; @@ -973,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) { @@ -982,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; @@ -1113,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, @@ -1142,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; @@ -1163,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 47e8e722682c..09084fd646ab 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/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 3568de210f79..8a832488ccdd 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 400e1ebe6976..d47a123f15ab 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 4e595324c614..544257a89ed2 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_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index c904aa39ad84..6cfd2f428d38 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -2967,7 +2967,7 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) } -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; diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index 56b022150f22..882484a40398 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/u_ether.c b/drivers/usb/gadget/u_ether.c index 07f4178ad178..1da755a1c855 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; -- cgit v1.2.1 From ddf8abd2599491cbad959c700b90ba72a5dce8d0 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 5 May 2010 12:53:14 +0200 Subject: USB: f_fs: the FunctionFS driver The FunctionFS is a USB composite function that can be used with the composite framework to create an USB gadget. >From kernel point of view it is just a composite function with some unique behaviour. It may be added to an USB configuration only after the user space driver has registered by writing descriptors and strings (the user space program has to provide the same information that kernel level composite functions provide when they are added to the configuration). >From user space point of view it is a file system which when mounted provide an "ep0" file. User space driver need to write descriptors and strings to that file. It does not need to worry about endpoints, interfaces or strings numbers but simply provide descriptors such as if the function was the only one (endpoints and strings numbers starting from one and interface numbers starting from core). The FunctionFS changes numbers of those as needed also handling situation when numbers differ in different configurations. When descriptors and strings are written "ep#" files appear (one for each declared endpoint) which handle communication on a single endpoint. Again, FunctionFS takes care of the real numbers and changing of the configuration (which means that "ep1" file may be really mapped to (say) endpoint 3 (and when configuration changes to (say) endpoint 2)). "ep0" is used for receiving events and handling setup requests. When all files are closed the function disables itself. Signed-off-by: Michal Nazarewicz Cc: Kyungmin Park Cc: Marek Szyprowski Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_fs.c | 2441 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2441 insertions(+) create mode 100644 drivers/usb/gadget/f_fs.c (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c new file mode 100644 index 000000000000..af89ca672c60 --- /dev/null +++ b/drivers/usb/gadget/f_fs.c @@ -0,0 +1,2441 @@ +/* + * f_fs.c -- user mode filesystem api for usb composite funtcion controllers + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz + * + * 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 +#include +#include + +#include +#include + + +#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; +} -- cgit v1.2.1 From c6c560085172c16a0141ab12ae765c7d1be68279 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 5 May 2010 12:53:15 +0200 Subject: USB: g_ffs: the FunctionFS gadget driver 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. Signed-off-by: Michal Nazarewicz Cc: Kyungmin Park Cc: Marek Szyprowski Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 37 ++++ drivers/usb/gadget/Makefile | 2 + drivers/usb/gadget/g_ffs.c | 426 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 465 insertions(+) create mode 100644 drivers/usb/gadget/g_ffs.c (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 96e1494723ad..0282c50d6404 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 + 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 + 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 diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 03a27d921c26..a55050c17893 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -51,6 +51,8 @@ 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 diff --git a/drivers/usb/gadget/g_ffs.c b/drivers/usb/gadget/g_ffs.c new file mode 100644 index 000000000000..4b0e4a040d6f --- /dev/null +++ b/drivers/usb/gadget/g_ffs.c @@ -0,0 +1,426 @@ +#include +#include + + +/* + * 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 -- cgit v1.2.1 From 1ab83238740ff1e1773d5c13ecac43c60cf4aec4 Mon Sep 17 00:00:00 2001 From: Robert Lukassen Date: Fri, 7 May 2010 09:19:53 +0200 Subject: USB: gadget: Allow function access to device ID data during bind() This is a patch that makes sure that the device ID data (idVendor, idProduct and bcdDevice) are assigned to the descriptor in the cdev structure *before* the composite gadget starts binding. This allows the composite driver, and all the composite functions it uses, access to that data. In one of the composite functions we created, we needed to register an input device and wanted to use the idVendor, idProduct and bcdDevice codes to properly initialize the id field of the input device. We could not do that because the idVendor, idProduct and bcdDevice values were only set in the cdec structure *after* the composite->bind(cdev) call. Signed-off-by: Robert Lukassen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index dd6d1905c148..391d169f8d07 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1017,6 +1017,14 @@ static int 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 @@ -1028,14 +1036,6 @@ static int 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 */ -- cgit v1.2.1 From 73a0bd77d60163d8b4639834119a1ed65155c062 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Mon, 10 May 2010 11:21:57 -0500 Subject: USB: mxc: gadget: remove 60mhz clock requirement for freescale mx51 usb core renamed fsl_mx3_udc.c -> fsl_mxc_udc.c for mx51, usb core is clocked from sources that are not 60mhz. Signed-off-by: Dinh Nguyen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Makefile | 2 +- drivers/usb/gadget/fsl_mx3_udc.c | 104 -------------------------------------- drivers/usb/gadget/fsl_mxc_udc.c | 106 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 105 deletions(-) delete mode 100644 drivers/usb/gadget/fsl_mx3_udc.c create mode 100644 drivers/usb/gadget/fsl_mxc_udc.c (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index a55050c17893..9bcde110feb1 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 diff --git a/drivers/usb/gadget/fsl_mx3_udc.c b/drivers/usb/gadget/fsl_mx3_udc.c deleted file mode 100644 index 20a802ecaa15..000000000000 --- a/drivers/usb/gadget/fsl_mx3_udc.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2009 - * Guennadi Liakhovetski, DENX Software Engineering, - * - * Description: - * Helper routines for i.MX3x SoCs from Freescale, needed by the fsl_usb2_udc.c - * driver to function correctly on these systems. - * - * 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 -#include -#include -#include -#include - -#include - -static struct clk *mxc_ahb_clk; -static struct clk *mxc_usb_clk; - -int fsl_udc_clk_init(struct platform_device *pdev) -{ - struct fsl_usb2_platform_data *pdata; - unsigned long freq; - int ret; - - pdata = pdev->dev.platform_data; - - if (!cpu_is_mx35()) { - mxc_ahb_clk = clk_get(&pdev->dev, "usb_ahb"); - if (IS_ERR(mxc_ahb_clk)) - return PTR_ERR(mxc_ahb_clk); - - ret = clk_enable(mxc_ahb_clk); - if (ret < 0) { - dev_err(&pdev->dev, "clk_enable(\"usb_ahb\") failed\n"); - goto eenahb; - } - } - - /* make sure USB_CLK is running at 60 MHz +/- 1000 Hz */ - mxc_usb_clk = clk_get(&pdev->dev, "usb"); - if (IS_ERR(mxc_usb_clk)) { - dev_err(&pdev->dev, "clk_get(\"usb\") failed\n"); - ret = PTR_ERR(mxc_usb_clk); - 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; - } - - ret = clk_enable(mxc_usb_clk); - if (ret < 0) { - dev_err(&pdev->dev, "clk_enable(\"usb_clk\") failed\n"); - goto eenusb; - } - - return 0; - -eenusb: -eclkrate: - clk_put(mxc_usb_clk); - mxc_usb_clk = NULL; -egusb: - if (!cpu_is_mx35()) - clk_disable(mxc_ahb_clk); -eenahb: - if (!cpu_is_mx35()) - clk_put(mxc_ahb_clk); - return ret; -} - -void fsl_udc_clk_finalize(struct platform_device *pdev) -{ - struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; - - /* ULPI transceivers don't need usbpll */ - if (pdata->phy_mode == FSL_USB2_PHY_ULPI) { - clk_disable(mxc_usb_clk); - clk_put(mxc_usb_clk); - mxc_usb_clk = NULL; - } -} - -void fsl_udc_clk_release(void) -{ - if (mxc_usb_clk) { - clk_disable(mxc_usb_clk); - clk_put(mxc_usb_clk); - } - if (!cpu_is_mx35()) { - clk_disable(mxc_ahb_clk); - clk_put(mxc_ahb_clk); - } -} diff --git a/drivers/usb/gadget/fsl_mxc_udc.c b/drivers/usb/gadget/fsl_mxc_udc.c new file mode 100644 index 000000000000..d0b8bde59e59 --- /dev/null +++ b/drivers/usb/gadget/fsl_mxc_udc.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2009 + * Guennadi Liakhovetski, DENX Software Engineering, + * + * Description: + * Helper routines for i.MX3x SoCs from Freescale, needed by the fsl_usb2_udc.c + * driver to function correctly on these systems. + * + * 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 +#include +#include +#include +#include + +#include + +static struct clk *mxc_ahb_clk; +static struct clk *mxc_usb_clk; + +int fsl_udc_clk_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + unsigned long freq; + int ret; + + pdata = pdev->dev.platform_data; + + if (!cpu_is_mx35()) { + mxc_ahb_clk = clk_get(&pdev->dev, "usb_ahb"); + if (IS_ERR(mxc_ahb_clk)) + return PTR_ERR(mxc_ahb_clk); + + ret = clk_enable(mxc_ahb_clk); + if (ret < 0) { + dev_err(&pdev->dev, "clk_enable(\"usb_ahb\") failed\n"); + goto eenahb; + } + } + + /* make sure USB_CLK is running at 60 MHz +/- 1000 Hz */ + mxc_usb_clk = clk_get(&pdev->dev, "usb"); + if (IS_ERR(mxc_usb_clk)) { + dev_err(&pdev->dev, "clk_get(\"usb\") failed\n"); + ret = PTR_ERR(mxc_usb_clk); + goto egusb; + } + + 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); + if (ret < 0) { + dev_err(&pdev->dev, "clk_enable(\"usb_clk\") failed\n"); + goto eenusb; + } + + return 0; + +eenusb: +eclkrate: + clk_put(mxc_usb_clk); + mxc_usb_clk = NULL; +egusb: + if (!cpu_is_mx35()) + clk_disable(mxc_ahb_clk); +eenahb: + if (!cpu_is_mx35()) + clk_put(mxc_ahb_clk); + return ret; +} + +void fsl_udc_clk_finalize(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + /* ULPI transceivers don't need usbpll */ + if (pdata->phy_mode == FSL_USB2_PHY_ULPI) { + clk_disable(mxc_usb_clk); + clk_put(mxc_usb_clk); + mxc_usb_clk = NULL; + } +} + +void fsl_udc_clk_release(void) +{ + if (mxc_usb_clk) { + clk_disable(mxc_usb_clk); + clk_put(mxc_usb_clk); + } + if (!cpu_is_mx35()) { + clk_disable(mxc_ahb_clk); + clk_put(mxc_ahb_clk); + } +} -- cgit v1.2.1 From b0608690c2deafbca32b45196672bfd74006cf61 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 10 May 2010 10:51:36 -0700 Subject: USB: gadget: f_fs.c needs to include pagemap.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix g_ffs build error, add a needed header file: drivers/usb/gadget/f_fs.c:1064:error: 'PAGE_CACHE_SIZE' undeclared (first use in this function) drivers/usb/gadget/f_fs.c:1065:error: 'PAGE_CACHE_SHIFT' undeclared (first use in this function) Signed-off-by: Randy Dunlap Acked-by: Michał Nazarewicz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_fs.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index af89ca672c60..d69eccf5f197 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -28,6 +28,7 @@ /* #define VERBOSE_DEBUG */ #include +#include #include #include -- cgit v1.2.1 From b9e000884e50bf6b25a92a3f3580e1d14fe01591 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 12 May 2010 12:51:13 +0200 Subject: USB: gadget: f_mass_storage: fix in error recovery In to places in fsg_common_init() an unconditional call to kfree() on common was performed in error recovery which is not a valid behaviour since fsg_common structure is not always allocated by fsg_common_init(). To fix, the calls has been replaced with a goto to a proper error recovery which does the correct thing. Also, refactored fsg_common_release() function. Signed-off-by: Michal Nazarewicz Reviewed-by: Viral Mehta Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/f_mass_storage.c | 51 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 6cfd2f428d38..7d05a0be5c60 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -2742,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; } @@ -2753,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; @@ -2914,11 +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_buffhd *bh; + 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) { @@ -2929,23 +2923,28 @@ 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; - 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); - } + /* 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); + kfree(common->luns); + } - i = FSG_NUM_BUFFERS; - bh = common->buffhds; - do { - kfree(bh->buf); - } while (++bh, --i); + { + struct fsg_buffhd *bh = common->buffhds; + unsigned i = FSG_NUM_BUFFERS; + do { + kfree(bh->buf); + } while (++bh, --i); + } if (common->free_storage_on_release) kfree(common); -- cgit v1.2.1 From 17b2765ef8db8dc00a24a9afd4646bb3decffbd2 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 13 May 2010 09:41:12 -0700 Subject: USB: fix functionfs for CONFIG_NET disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix functionfs build to handle CONFIG_NET not enabled, to prevent these build errors: ERROR: "netif_carrier_on" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "netif_carrier_off" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "skb_realloc_headroom" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "skb_trim" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "netif_rx" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "alloc_etherdev_mq" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "ethtool_op_get_link" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "free_netdev" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "register_netdev" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "skb_push" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "skb_pull" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "dev_kfree_skb_any" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "skb_queue_tail" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "__alloc_skb" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "eth_type_trans" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "eth_validate_addr" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "skb_dequeue" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "unregister_netdev" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "__netif_schedule" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "skb_put" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "eth_mac_addr" [drivers/usb/gadget/g_ffs.ko] undefined! ERROR: "dev_get_stats" [drivers/usb/gadget/g_ffs.ko] undefined! Signed-off-by: Randy Dunlap Acked-by: Michał Nazarewicz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 0282c50d6404..649c0c5f7158 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -726,7 +726,7 @@ config USB_FUNCTIONFS config USB_FUNCTIONFS_ETH bool "Include CDC ECM (Ethernet) function" - depends on USB_FUNCTIONFS + 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 @@ -734,7 +734,7 @@ config USB_FUNCTIONFS_ETH config USB_FUNCTIONFS_RNDIS bool "Include RNDIS (Ethernet) function" - depends on USB_FUNCTIONFS + 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 -- cgit v1.2.1 From 4b8e12336fede9e30b6140e840431161d46cf677 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 15 May 2010 23:15:44 +0200 Subject: USB: gadget: Use kmemdup Use kmemdup when some other buffer is immediately copied into the allocated region. A simplified version of the semantic patch that makes this change is as follows: (http://coccinelle.lip6.fr/) // @@ expression from,to,size,flag; statement S; @@ - to = \(kmalloc\|kzalloc\)(size,flag); + to = kmemdup(from,size,flag); if (to==NULL || ...) S - memcpy(to, from, size); // Signed-off-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/atmel_usba_udc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/atmel_usba_udc.c b/drivers/usb/gadget/atmel_usba_udc.c index 75a256f3d45b..d623c7bda1f6 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); -- cgit v1.2.1 From 8018ab057480974e7f26a387bf4ce040e9a5f6f1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 22 Mar 2010 17:32:25 +0100 Subject: sanitize vfs_fsync calling conventions Now that the last user passing a NULL file pointer is gone we can remove the redundant dentry argument and associated hacks inside vfs_fsynmc_range. The next step will be removig the dentry argument from ->fsync, but given the luck with the last round of method prototype changes I'd rather defer this until after the main merge window. Signed-off-by: Christoph Hellwig Signed-off-by: Al Viro --- drivers/usb/gadget/storage_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 868d8ee86756..04c462ff0ea6 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -654,7 +654,7 @@ static int fsg_lun_fsync_sub(struct fsg_lun *curlun) if (curlun->ro || !filp) return 0; - return vfs_fsync(filp, filp->f_path.dentry, 1); + return vfs_fsync(filp, 1); } static void store_cdrom_address(u8 *dest, int msf, u32 addr) -- cgit v1.2.1