From: Jaewon Kim Date: Tue, 2 Dec 2014 12:34:54 +0000 (+0900) Subject: local / usb: gadget: Add slp composite gadget X-Git-Tag: submit/tizen/20150416.081342~45 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c74485f3bd9488a1c58c6ec6c03bba64d038557e;p=platform%2Fkernel%2Flinux-exynos.git local / usb: gadget: Add slp composite gadget This patch adds slp composite gadget for tizen platform. slp composite gadget code comes from android.c Signed-off-by: Jaewon Kim --- diff --git a/drivers/usb/gadget/function/f_sdb.c b/drivers/usb/gadget/function/f_sdb.c new file mode 100644 index 000000000000..9fcb1d05b647 --- /dev/null +++ b/drivers/usb/gadget/function/f_sdb.c @@ -0,0 +1,667 @@ +/* + * Gadget Driver for Samsung SDB (based on Android ADB) + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SDB_BULK_BUFFER_SIZE 4096 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +static const char sdb_shortname[] = "samsung_sdb"; + +struct sdb_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; +}; + +static struct usb_interface_descriptor sdb_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x20, + .bInterfaceProtocol = 0x02, +}; + +static struct usb_endpoint_descriptor sdb_superspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor sdb_superspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor sdb_superspeed_bulk_comp_desc = { + .bLength = sizeof sdb_superspeed_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor sdb_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor sdb_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor sdb_fullspeed_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 sdb_fullspeed_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 *fs_sdb_descs[] = { + (struct usb_descriptor_header *) &sdb_interface_desc, + (struct usb_descriptor_header *) &sdb_fullspeed_in_desc, + (struct usb_descriptor_header *) &sdb_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_sdb_descs[] = { + (struct usb_descriptor_header *) &sdb_interface_desc, + (struct usb_descriptor_header *) &sdb_highspeed_in_desc, + (struct usb_descriptor_header *) &sdb_highspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_sdb_descs[] = { + (struct usb_descriptor_header *) &sdb_interface_desc, + (struct usb_descriptor_header *) &sdb_superspeed_in_desc, + (struct usb_descriptor_header *) &sdb_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &sdb_superspeed_out_desc, + (struct usb_descriptor_header *) &sdb_superspeed_bulk_comp_desc, + NULL, +}; + +static void sdb_ready_callback(void); +static void sdb_closed_callback(void); + +/* temporary variable used between sdb_open() and sdb_gadget_bind() */ +static struct sdb_dev *_sdb_dev; + +static inline struct sdb_dev *func_to_sdb(struct usb_function *f) +{ + return container_of(f, struct sdb_dev, function); +} + + +static struct usb_request *sdb_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void sdb_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int sdb_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void sdb_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void sdb_req_put(struct sdb_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *sdb_req_get(struct sdb_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void sdb_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct sdb_dev *dev = _sdb_dev; + + if (req->status != 0) + dev->error = 1; + + sdb_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void sdb_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct sdb_dev *dev = _sdb_dev; + + dev->rx_done = 1; + if (req->status != 0 && req->status != -ECONNRESET) + dev->error = 1; + + wake_up(&dev->read_wq); +} + +static int sdb_create_bulk_endpoints(struct sdb_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for sdb ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = sdb_request_new(dev->ep_out, SDB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = sdb_complete_out; + dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = sdb_request_new(dev->ep_in, SDB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = sdb_complete_in; + sdb_req_put(dev, &dev->tx_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "sdb_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t sdb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct sdb_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int maxp; + int ret; + + pr_debug("sdb_read(%d)\n", (int)count); + if (!_sdb_dev) + return -ENODEV; + + if (sdb_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + pr_debug("sdb_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + sdb_unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + goto done; + } + + maxp = usb_endpoint_maxp(dev->ep_out->desc); + count = round_up(count, maxp); + + if (count > SDB_BULK_BUFFER_SIZE) + return -EINVAL; + +requeue_req: + /* queue a request */ + req = dev->rx_req; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("sdb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->error = 1; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + if (ret != -ERESTARTSYS) + dev->error = 1; + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (!dev->error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + + } else + r = -EIO; + +done: + sdb_unlock(&dev->read_excl); + pr_debug("sdb_read returning %d\n", r); + return r; +} + +static ssize_t sdb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct sdb_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + if (!_sdb_dev) + return -ENODEV; + pr_debug("sdb_write(%d)\n", (int)count); + + if (sdb_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + pr_debug("sdb_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = sdb_req_get(dev, &dev->tx_idle)) || dev->error); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > SDB_BULK_BUFFER_SIZE) + xfer = SDB_BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("sdb_write: xfer error %d\n", ret); + dev->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + sdb_req_put(dev, &dev->tx_idle, req); + + sdb_unlock(&dev->write_excl); + pr_debug("sdb_write returning %d\n", r); + return r; +} + +static int sdb_open(struct inode *ip, struct file *fp) +{ + pr_info("sdb_open\n"); + if (!_sdb_dev) + return -ENODEV; + + if (sdb_lock(&_sdb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _sdb_dev; + + /* clear the error latch */ + _sdb_dev->error = 0; + + sdb_ready_callback(); + + return 0; +} + +static int sdb_release(struct inode *ip, struct file *fp) +{ + pr_info("sdb_release\n"); + + sdb_closed_callback(); + + sdb_unlock(&_sdb_dev->open_excl); + return 0; +} + +/* file operations for sdb device /dev/samsung_sdb */ +static const struct file_operations sdb_fops = { + .owner = THIS_MODULE, + .read = sdb_read, + .write = sdb_write, + .open = sdb_open, + .release = sdb_release, +}; + +static struct miscdevice sdb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = sdb_shortname, + .fops = &sdb_fops, +}; + + + + +static int +sdb_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct sdb_dev *dev = func_to_sdb(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "sdb_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + sdb_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = sdb_create_bulk_endpoints(dev, &sdb_fullspeed_in_desc, + &sdb_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + sdb_highspeed_in_desc.bEndpointAddress = + sdb_fullspeed_in_desc.bEndpointAddress; + sdb_highspeed_out_desc.bEndpointAddress = + sdb_fullspeed_out_desc.bEndpointAddress; + } + + /* support super speed hardware */ + if (gadget_is_superspeed(c->cdev->gadget)) { + sdb_superspeed_in_desc.bEndpointAddress = + sdb_fullspeed_in_desc.bEndpointAddress; + sdb_superspeed_out_desc.bEndpointAddress = + sdb_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +sdb_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct sdb_dev *dev = func_to_sdb(f); + struct usb_request *req; + + + dev->online = 0; + dev->error = 1; + + wake_up(&dev->read_wq); + + sdb_request_free(dev->rx_req, dev->ep_out); + while ((req = sdb_req_get(dev, &dev->tx_idle))) + sdb_request_free(req, dev->ep_in); +} + +static int sdb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct sdb_dev *dev = func_to_sdb(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "sdb_function_set_alt intf: %d alt: %d\n", intf, alt); + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_in); + if (ret) + return ret; + + ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out); + if (ret) + return ret; + + ret = usb_ep_enable(dev->ep_out); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void sdb_function_disable(struct usb_function *f) +{ + struct sdb_dev *dev = func_to_sdb(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "sdb_function_disable cdev %p\n", cdev); + dev->online = 0; + dev->error = 1; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int sdb_bind_config(struct usb_configuration *c) +{ + struct sdb_dev *dev = _sdb_dev; + + printk(KERN_INFO "sdb_bind_config\n"); + + dev->cdev = c->cdev; + dev->function.name = "sdb"; + dev->function.fs_descriptors = fs_sdb_descs; + dev->function.hs_descriptors = hs_sdb_descs; + dev->function.ss_descriptors = ss_sdb_descs; + dev->function.bind = sdb_function_bind; + dev->function.unbind = sdb_function_unbind; + dev->function.set_alt = sdb_function_set_alt; + dev->function.disable = sdb_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int sdb_setup(void) +{ + struct sdb_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + INIT_LIST_HEAD(&dev->tx_idle); + + _sdb_dev = dev; + + ret = misc_register(&sdb_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + printk(KERN_ERR "sdb gadget driver failed to initialize\n"); + return ret; +} + +static void sdb_cleanup(void) +{ + misc_deregister(&sdb_device); + + kfree(_sdb_dev); + _sdb_dev = NULL; +} diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 113c87e22117..e2c9f6784d22 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -314,6 +314,17 @@ config USB_G_PRINTER For more information, see Documentation/usb/gadget_printer.txt which includes sample code for accessing the device file. +config USB_G_SLP + boolean "SLP Gadget based on Android" + select USB_F_ACM + select USB_LIBCOMPOSITE + select USB_U_SERIAL + help + The SLP gadget driver supports multiple USB functions. + The functions can be configured via a board file and may be + enabled and disabled dynamically. + Support functions: sdb, acm, mtp, mass storage, rndis, + android accessory, diag. if TTY config USB_CDC_COMPOSITE diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile index 7f485f25705e..43586919e877 100644 --- a/drivers/usb/gadget/legacy/Makefile +++ b/drivers/usb/gadget/legacy/Makefile @@ -14,6 +14,7 @@ g_midi-y := gmidi.o gadgetfs-y := inode.o g_mass_storage-y := mass_storage.o g_printer-y := printer.o +g_slp-y := slp.o g_cdc-y := cdc2.o g_multi-y := multi.o g_hid-y := hid.o @@ -32,6 +33,7 @@ obj-$(CONFIG_USB_FUNCTIONFS) += g_ffs.o obj-$(CONFIG_USB_MASS_STORAGE) += g_mass_storage.o obj-$(CONFIG_USB_G_SERIAL) += g_serial.o obj-$(CONFIG_USB_G_PRINTER) += g_printer.o +obj-$(CONFIG_USB_G_SLP) += g_slp.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 diff --git a/drivers/usb/gadget/legacy/slp.c b/drivers/usb/gadget/legacy/slp.c new file mode 100644 index 000000000000..48316a6d093d --- /dev/null +++ b/drivers/usb/gadget/legacy/slp.c @@ -0,0 +1,1342 @@ +/* + * Gadget Driver for SLP based on Android + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * Benoit Goby + * Modified : Jaewon Kim + * + * Heavily based on android.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "gadget_chips.h" + +#include "../function/f_sdb.c" +#include "../function/f_acm.c" +#define USB_ETH_RNDIS y +#define USB_FRNDIS_INCLUDED y +#include "../function/f_rndis.c" +#include "../function/rndis.c" +#include "../function/u_ether.c" + +/* Default vendor and product IDs, overridden by userspace */ +#define VENDOR_ID 0x04E8 +#define PRODUCT_ID 0x6860 +#define USB_MODE_VERSION "1.1" + +static const char longname[] = "Gadget SLP"; + +enum slp_multi_config_id { + USB_CONFIGURATION_1 = 1, + USB_CONFIGURATION_2 = 2, + USB_CONFIGURATION_DUAL = 0xFF, +}; + +struct slp_multi_usb_function { + char *name; + void *config; + + struct device *dev; + char *dev_name; + struct device_attribute **attributes; + + /* for slp_multi_dev.funcs_fconf */ + struct list_head fconf_list; + /* for slp_multi_dev.funcs_sconf */ + struct list_head sconf_list; + + /* Optional: initialization during gadget bind */ + int (*init)(struct slp_multi_usb_function *, struct usb_composite_dev *); + /* Optional: cleanup during gadget unbind */ + void (*cleanup)(struct slp_multi_usb_function *); + /* Optional: called when the function is added the list of + * enabled functions */ + void (*enable)(struct slp_multi_usb_function *); + /* Optional: called when it is removed */ + void (*disable)(struct slp_multi_usb_function *); + + int (*bind_config)(struct slp_multi_usb_function *, + struct usb_configuration *); + + /* Optional: called when the configuration is removed */ + void (*unbind_config)(struct slp_multi_usb_function *, + struct usb_configuration *); + /* Optional: handle ctrl requests before the device is configured */ + int (*ctrlrequest)(struct slp_multi_usb_function *, + struct usb_composite_dev *, + const struct usb_ctrlrequest *); +}; + +struct slp_multi_dev { + struct slp_multi_usb_function **functions; + + /* for each configuration control */ + struct list_head funcs_fconf; + struct list_head funcs_sconf; + + struct usb_composite_dev *cdev; + struct device *dev; + + bool enabled; + bool dual_config; + int disable_depth; + struct mutex mutex; + bool connected; + bool sw_connected; + char ffs_aliases[256]; +}; + +static struct class *slp_multi_class; +static struct slp_multi_dev *_slp_multi_dev; +static int slp_multi_bind_config(struct usb_configuration *c); +static void slp_multi_unbind_config(struct usb_configuration *c); + +/* string IDs are assigned dynamically */ +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +static char manufacturer_string[256]; +static char product_string[256]; +static char serial_string[256]; + +/* String Table */ +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer_string, + [STRING_PRODUCT_IDX].s = product_string, + [STRING_SERIAL_IDX].s = serial_string, + { } /* 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, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof(device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = __constant_cpu_to_le16(VENDOR_ID), + .idProduct = __constant_cpu_to_le16(PRODUCT_ID), + .bcdDevice = __constant_cpu_to_le16(0xffff), + .bNumConfigurations = 1, +}; + +static struct usb_configuration first_config_driver = { + .label = "slp_first_config", + .unbind = slp_multi_unbind_config, + .bConfigurationValue = USB_CONFIGURATION_1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .MaxPower = 500, /* 500ma */ +}; + +static struct usb_configuration second_config_driver = { + .label = "slp_second_config", + .unbind = slp_multi_unbind_config, + .bConfigurationValue = USB_CONFIGURATION_2, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .MaxPower = 500, /* 500ma */ +}; + +static void slp_multi_enable(struct slp_multi_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + if (WARN_ON(!dev->disable_depth)) + return; + + if (--dev->disable_depth == 0) { + if (!dev->dual_config) + usb_add_config(cdev, &first_config_driver, + slp_multi_bind_config); + else + usb_add_config(cdev, &second_config_driver, + slp_multi_bind_config); + + usb_gadget_connect(cdev->gadget); + } +} + +static void slp_multi_disable(struct slp_multi_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + + if (dev->disable_depth++ == 0) { + usb_gadget_disconnect(cdev->gadget); + /* Cancel pending control requests */ + usb_ep_dequeue(cdev->gadget->ep0, cdev->req); + if (!dev->dual_config) + usb_remove_config(cdev, &first_config_driver); + else + usb_remove_config(cdev, &second_config_driver); + } +} + +/*-------------------------------------------------------------------------*/ +/* Supported functions initialization */ +struct sdb_data { + bool opened; + bool enabled; +}; + +static int +sdb_function_init(struct slp_multi_usb_function *f, + struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct sdb_data), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + + return sdb_setup(); +} + +static void sdb_function_cleanup(struct slp_multi_usb_function *f) +{ + sdb_cleanup(); + kfree(f->config); +} + +static int +sdb_function_bind_config(struct slp_multi_usb_function *f, + struct usb_configuration *c) +{ + return sdb_bind_config(c); +} + +static void sdb_slp_multi_function_enable(struct slp_multi_usb_function *f) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + struct sdb_data *data = f->config; + + data->enabled = true; + + /* Disable the gadget until sdbd is ready */ + if (!data->opened) + slp_multi_disable(dev); +} + +static void sdb_slp_multi_function_disable(struct slp_multi_usb_function *f) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + struct sdb_data *data = f->config; + + data->enabled = false; + + /* Balance the disable that was called in closed_callback */ + if (!data->opened) + slp_multi_enable(dev); +} + +static struct slp_multi_usb_function sdb_function = { + .name = "sdb", + .enable = sdb_slp_multi_function_enable, + .disable = sdb_slp_multi_function_disable, + .init = sdb_function_init, + .cleanup = sdb_function_cleanup, + .bind_config = sdb_function_bind_config, +}; + +static void sdb_ready_callback(void) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + struct sdb_data *data = sdb_function.config; + + mutex_lock(&dev->mutex); + + data->opened = true; + + if (data->enabled) + slp_multi_enable(dev); + + mutex_unlock(&dev->mutex); +} + +static void sdb_closed_callback(void) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + struct sdb_data *data = sdb_function.config; + + mutex_lock(&dev->mutex); + + data->opened = false; + + if (data->enabled) + slp_multi_disable(dev); + + mutex_unlock(&dev->mutex); +} + +#define MAX_ACM_INSTANCES 4 +struct acm_function_config { + int instances; + int instances_on; + struct usb_function *f_acm[MAX_ACM_INSTANCES]; + struct usb_function_instance *f_acm_inst[MAX_ACM_INSTANCES]; +}; + +static int +acm_function_init(struct slp_multi_usb_function *f, + struct usb_composite_dev *cdev) +{ + int i; + int ret; + struct acm_function_config *config; + + config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + f->config = config; + + for (i = 0; i < MAX_ACM_INSTANCES; i++) { + config->f_acm_inst[i] = usb_get_function_instance("acm"); + if (IS_ERR(config->f_acm_inst[i])) { + ret = PTR_ERR(config->f_acm_inst[i]); + goto err_usb_get_function_instance; + } + config->f_acm[i] = usb_get_function(config->f_acm_inst[i]); + if (IS_ERR(config->f_acm[i])) { + ret = PTR_ERR(config->f_acm[i]); + goto err_usb_get_function; + } + } + return 0; +err_usb_get_function_instance: + while (i-- > 0) { + usb_put_function(config->f_acm[i]); +err_usb_get_function: + usb_put_function_instance(config->f_acm_inst[i]); + } + return ret; +} + +static void acm_function_cleanup(struct slp_multi_usb_function *f) +{ + int i; + struct acm_function_config *config = f->config; + + for (i = 0; i < MAX_ACM_INSTANCES; i++) { + usb_put_function(config->f_acm[i]); + usb_put_function_instance(config->f_acm_inst[i]); + } + kfree(f->config); + f->config = NULL; +} + +static int +acm_function_bind_config(struct slp_multi_usb_function *f, + struct usb_configuration *c) +{ + int i; + int ret = 0; + struct acm_function_config *config = f->config; + + config->instances_on = config->instances; + for (i = 0; i < config->instances_on; i++) { + ret = usb_add_function(c, config->f_acm[i]); + if (ret) { + pr_err("Could not bind acm%u config\n", i); + goto err_usb_add_function; + } + } + + return 0; + +err_usb_add_function: + while (i-- > 0) + usb_remove_function(c, config->f_acm[i]); + return ret; +} + +static void acm_function_unbind_config(struct slp_multi_usb_function *f, + struct usb_configuration *c) +{ + int i; + struct acm_function_config *config = f->config; + + for (i = 0; i < config->instances_on; i++) + usb_remove_function(c, config->f_acm[i]); +} + +static ssize_t acm_instances_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + + return sprintf(buf, "%d\n", config->instances); +} + +static ssize_t acm_instances_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + int value; + + sscanf(buf, "%d", &value); + if (value > MAX_ACM_INSTANCES) + value = MAX_ACM_INSTANCES; + config->instances = value; + return size; +} + +static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show, + acm_instances_store); +static struct device_attribute *acm_function_attributes[] = { + &dev_attr_instances, + NULL +}; + +static struct slp_multi_usb_function acm_function = { + .name = "acm", + .init = acm_function_init, + .cleanup = acm_function_cleanup, + .bind_config = acm_function_bind_config, + .unbind_config = acm_function_unbind_config, + .attributes = acm_function_attributes, +}; + +struct rndis_function_config { + u8 ethaddr[ETH_ALEN]; + u32 vendorID; + char manufacturer[256]; + bool wceis; + u8 rndis_string_defs0_id; + struct usb_function *f_rndis; + struct usb_function_instance *fi_rndis; +}; +static char host_addr_string[18]; + +static int +rndis_function_init(struct slp_multi_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct rndis_function_config *config; + int status, i; + + config = kzalloc(sizeof(struct rndis_function_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + config->fi_rndis = usb_get_function_instance("rndis"); + if (IS_ERR(config->fi_rndis)) { + status = PTR_ERR(config->fi_rndis); + goto rndis_alloc_inst_error; + } + + /* maybe allocate device-global string IDs */ + if (rndis_string_defs[0].id == 0) { + + /* control interface label */ + status = usb_string_id(cdev); + if (status < 0) + goto rndis_init_error; + config->rndis_string_defs0_id = status; + rndis_string_defs[0].id = status; + rndis_control_intf.iInterface = status; + + /* data interface label */ + status = usb_string_id(cdev); + if (status < 0) + goto rndis_init_error; + rndis_string_defs[1].id = status; + rndis_data_intf.iInterface = status; + + /* IAD iFunction label */ + status = usb_string_id(cdev); + if (status < 0) + goto rndis_init_error; + rndis_string_defs[2].id = status; + rndis_iad_descriptor.iFunction = status; + } + + /* create a fake MAC address from our serial number. */ + for (i = 0; (i < 256) && serial_string[i]; i++) { + /* XOR the USB serial across the remaining bytes */ + config->ethaddr[i % (ETH_ALEN - 1) + 1] ^= serial_string[i]; + } + config->ethaddr[0] &= 0xfe; /* clear multicast bit */ + config->ethaddr[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + snprintf(host_addr_string, sizeof(host_addr_string), + "%02x:%02x:%02x:%02x:%02x:%02x", + config->ethaddr[0], config->ethaddr[1], + config->ethaddr[2], config->ethaddr[3], + config->ethaddr[4], config->ethaddr[5]); + + f->config = config; + return 0; + + rndis_init_error: + usb_put_function_instance(config->fi_rndis); +rndis_alloc_inst_error: + kfree(config); + return status; +} + +static void rndis_function_cleanup(struct slp_multi_usb_function *f) +{ + struct rndis_function_config *rndis = f->config; + + usb_put_function_instance(rndis->fi_rndis); + kfree(rndis); + f->config = NULL; +} + +static int rndis_function_bind_config(struct slp_multi_usb_function *f, + struct usb_configuration *c) +{ + int ret = -EINVAL; + struct rndis_function_config *rndis = f->config; + struct f_rndis_opts *rndis_opts; + struct net_device *net; + + if (!rndis) { + dev_err(f->dev, "error rndis_pdata is null\n"); + return ret; + } + + rndis_opts = + container_of(rndis->fi_rndis, struct f_rndis_opts, func_inst); + net = rndis_opts->net; + + gether_set_qmult(net, QMULT_DEFAULT); + gether_set_host_addr(net, host_addr_string); + gether_set_dev_addr(net, host_addr_string); + rndis_opts->vendor_id = rndis->vendorID; + rndis_opts->manufacturer = rndis->manufacturer; + + if (rndis->wceis) { + /* "Wireless" RNDIS; auto-detected by Windows */ + rndis_iad_descriptor.bFunctionClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_iad_descriptor.bFunctionSubClass = 0x01; + rndis_iad_descriptor.bFunctionProtocol = 0x03; + rndis_control_intf.bInterfaceClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_control_intf.bInterfaceSubClass = 0x01; + rndis_control_intf.bInterfaceProtocol = 0x03; + } + + /* Android team reset "rndis_string_defs[0].id" when RNDIS unbinded + * in f_rndis.c but, that makes failure of rndis_bind_config() by + * the overflow of "next_string_id" value in usb_string_id(). + * So, Android team also reset "next_string_id" value in android.c + * but SLP does not reset "next_string_id" value. And we decided to + * re-update "rndis_string_defs[0].id" by old value. + * 20120224 yongsul96.oh@samsung.com + */ + if (rndis_string_defs[0].id == 0) + rndis_string_defs[0].id = rndis->rndis_string_defs0_id; + + rndis->f_rndis = usb_get_function(rndis->fi_rndis); + if (IS_ERR(rndis->f_rndis)) + return PTR_ERR(rndis->f_rndis); + + ret = usb_add_function(c, rndis->f_rndis); + if (ret < 0) { + usb_put_function(rndis->f_rndis); + dev_err(f->dev, "rndis_bind_config failed(ret:%d)\n", ret); + } + + return ret; +} + +static void rndis_function_unbind_config(struct slp_multi_usb_function *f, + struct usb_configuration *c) +{ + struct rndis_function_config *rndis = f->config; + + usb_put_function(rndis->f_rndis); +} + +static ssize_t rndis_manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + return snprintf(buf, PAGE_SIZE, "%s\n", config->manufacturer); +} + +static ssize_t rndis_manufacturer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + if ((size >= sizeof(config->manufacturer)) || + (sscanf(buf, "%s", config->manufacturer) != 1)) + return -EINVAL; + + return size; +} + +static DEVICE_ATTR(manufacturer, S_IRUGO | S_IWUSR, rndis_manufacturer_show, + rndis_manufacturer_store); + +static ssize_t rndis_wceis_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + return snprintf(buf, PAGE_SIZE, "%d\n", config->wceis); +} + +static ssize_t rndis_wceis_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%d", &value) == 1) { + config->wceis = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(wceis, S_IRUGO | S_IWUSR, rndis_wceis_show, + rndis_wceis_store); + +static ssize_t rndis_ethaddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + + return snprintf(buf, PAGE_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x\n", + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); +} + +static DEVICE_ATTR(ethaddr, S_IRUGO, rndis_ethaddr_show, + NULL); + +static ssize_t rndis_vendorID_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + return snprintf(buf, PAGE_SIZE, "%04x\n", config->vendorID); +} + +static ssize_t rndis_vendorID_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct slp_multi_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%04x", &value) == 1) { + config->vendorID = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(vendorID, S_IRUGO | S_IWUSR, rndis_vendorID_show, + rndis_vendorID_store); + +static struct device_attribute *rndis_function_attributes[] = { + &dev_attr_manufacturer, + &dev_attr_wceis, + &dev_attr_ethaddr, + &dev_attr_vendorID, + NULL +}; + +static struct slp_multi_usb_function rndis_function = { + .name = "rndis", + .init = rndis_function_init, + .cleanup = rndis_function_cleanup, + .bind_config = rndis_function_bind_config, + .unbind_config = rndis_function_unbind_config, + .attributes = rndis_function_attributes, +}; + +static struct slp_multi_usb_function *supported_functions[] = { + &sdb_function, + &acm_function, + &rndis_function, + NULL +}; + +static int slp_multi_init_functions(struct slp_multi_usb_function **functions, + struct usb_composite_dev *cdev) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + struct slp_multi_usb_function *f; + struct device_attribute **attrs; + struct device_attribute *attr; + int err; + int index = 0; + + for (; (f = *functions++); index++) { + f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name); + f->dev = device_create(slp_multi_class, dev->dev, + MKDEV(0, index), f, f->dev_name); + if (IS_ERR(f->dev)) { + pr_err("%s: Failed to create dev %s", __func__, + f->dev_name); + err = PTR_ERR(f->dev); + goto err_create; + } + + if (f->init) { + err = f->init(f, cdev); + if (err) { + pr_err("%s: Failed to init %s", __func__, + f->name); + goto err_out; + } + } + + attrs = f->attributes; + if (attrs) { + while ((attr = *attrs++) && !err) + err = device_create_file(f->dev, attr); + } + if (err) { + pr_err("%s: Failed to create function %s attributes", + __func__, f->name); + goto err_out; + } + } + return 0; + +err_out: + device_destroy(slp_multi_class, f->dev->devt); +err_create: + kfree(f->dev_name); + + return err; +} + +static void slp_multi_cleanup_functions(struct slp_multi_usb_function **functions) +{ + struct slp_multi_usb_function *f; + + while (*functions) { + f = *functions++; + + if (f->dev) { + device_destroy(slp_multi_class, f->dev->devt); + kfree(f->dev_name); + } + + if (f->cleanup) + f->cleanup(f); + } +} + +static int +slp_multi_bind_enabled_functions(struct slp_multi_dev *dev, + struct usb_configuration *c) +{ + struct slp_multi_usb_function *f = NULL; + int ret; + + if (c->bConfigurationValue == USB_CONFIGURATION_1) { + list_for_each_entry(f, &dev->funcs_fconf, fconf_list) { + ret = f->bind_config(f, c); + if (ret) { + pr_err("%s: %s failed", __func__, f->name); + return ret; + } + } + } else if (c->bConfigurationValue == USB_CONFIGURATION_2) { + list_for_each_entry(f, &dev->funcs_sconf, sconf_list) { + ret = f->bind_config(f, c); + if (ret) { + pr_err("%s: %s failed", __func__, f->name); + return ret; + } + } + } else { + pr_err("%s: %s Not supported configuration(%d)", __func__, + f->name, c->bConfigurationValue); + return -EINVAL; + } + + return 0; +} + +static void +slp_multi_unbind_enabled_functions(struct slp_multi_dev *dev, + struct usb_configuration *c) +{ + struct slp_multi_usb_function *f; + + if (c->bConfigurationValue == USB_CONFIGURATION_1) { + list_for_each_entry(f, &dev->funcs_fconf, fconf_list) { + if (f->unbind_config) + f->unbind_config(f, c); + } + } else if (c->bConfigurationValue == USB_CONFIGURATION_2) { + list_for_each_entry(f, &dev->funcs_sconf, sconf_list) { + if (f->unbind_config) + f->unbind_config(f, c); + } + } +} + +static int slp_multi_enable_function(struct slp_multi_dev *dev, + char *name, int type) +{ + struct slp_multi_usb_function **functions = dev->functions; + struct slp_multi_usb_function *f; + while ((f = *functions++)) { + if (!strcmp(name, f->name)) { + if (type == USB_CONFIGURATION_1) + list_add_tail(&f->fconf_list, + &dev->funcs_fconf); + else if (type == USB_CONFIGURATION_2) + list_add_tail(&f->sconf_list, + &dev->funcs_sconf); + return 0; + } + } + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ +/* /sys/class/slp_multi_usb/slp_multi%d/ interface */ + +static ssize_t +funcs_sconf_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct slp_multi_dev *dev = dev_get_drvdata(pdev); + struct slp_multi_usb_function *f; + char *buff = buf; + + mutex_lock(&dev->mutex); + + list_for_each_entry(f, &dev->funcs_sconf, sconf_list) + buff += sprintf(buff, "%s,", f->name); + + mutex_unlock(&dev->mutex); + + if (buff != buf) + *(buff-1) = '\n'; + return buff - buf; +} + +static ssize_t +funcs_sconf_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct slp_multi_dev *dev = dev_get_drvdata(pdev); + char *name; + char buf[256], *b; + int err; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + INIT_LIST_HEAD(&dev->funcs_sconf); + + strlcpy(buf, buff, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + if (!name) + continue; + + err = slp_multi_enable_function(dev, name, USB_CONFIGURATION_2); + if (err) + pr_err("slp_multi_usb: Cannot enable '%s' (%d)", + name, err); + } + + mutex_unlock(&dev->mutex); + + return size; +} + +static ssize_t +funcs_fconf_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct slp_multi_dev *dev = dev_get_drvdata(pdev); + struct slp_multi_usb_function *f; + char *buff = buf; + + mutex_lock(&dev->mutex); + + list_for_each_entry(f, &dev->funcs_fconf, fconf_list) + buff += sprintf(buff, "%s,", f->name); + + mutex_unlock(&dev->mutex); + + if (buff != buf) + *(buff-1) = '\n'; + return buff - buf; +} + +static ssize_t +funcs_fconf_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct slp_multi_dev *dev = dev_get_drvdata(pdev); + char *name; + char buf[256], *b; + int err; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + INIT_LIST_HEAD(&dev->funcs_fconf); + + strlcpy(buf, buff, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + if (!name) + continue; + + err = slp_multi_enable_function(dev, name, USB_CONFIGURATION_1); + if (err) + pr_err("slp_multi_usb: Cannot enable '%s' (%d)", + name, err); + } + + mutex_unlock(&dev->mutex); + + return size; +} + +static ssize_t enable_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct slp_multi_dev *dev = dev_get_drvdata(pdev); + + return sprintf(buf, "%d\n", dev->enabled); +} + +static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct slp_multi_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + struct slp_multi_usb_function *f; + int enabled = 0; + + if (!cdev) + return -ENODEV; + + mutex_lock(&dev->mutex); + + sscanf(buff, "%d", &enabled); + if (enabled && !dev->enabled) { + /* + * Update values in composite driver's copy of + * device descriptor. + */ + cdev->desc.idVendor = device_desc.idVendor; + cdev->desc.idProduct = device_desc.idProduct; + cdev->desc.bcdDevice = device_desc.bcdDevice; + cdev->desc.bDeviceClass = device_desc.bDeviceClass; + cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass; + cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol; + + list_for_each_entry(f, &dev->funcs_fconf, fconf_list) { + if (!strcmp(f->name, "acm")) + cdev->desc.bcdDevice = cpu_to_le16(0x0400); + } + + list_for_each_entry(f, &dev->funcs_sconf, sconf_list) { + if (!strcmp(f->name, "acm")) + cdev->desc.bcdDevice = cpu_to_le16(0x400); + dev->dual_config = true; + } + + slp_multi_enable(dev); + dev->enabled = true; + } else if (!enabled && dev->enabled) { + slp_multi_disable(dev); + dev->enabled = false; + dev->dual_config = false; + } else { + pr_err("slp_multi_usb: already %s\n", + dev->enabled ? "enabled" : "disabled"); + } + + mutex_unlock(&dev->mutex); + return size; +} + +static ssize_t state_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct slp_multi_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + char *state = "DISCONNECTED"; + unsigned long flags; + + if (!cdev) + goto out; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + state = "CONFIGURED"; + else if (dev->connected) + state = "CONNECTED"; + spin_unlock_irqrestore(&cdev->lock, flags); +out: + return sprintf(buf, "%s\n", state); +} + +#define DESCRIPTOR_ATTR(field, format_string) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, format_string, device_desc.field); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + int value; \ + if (sscanf(buf, format_string, &value) == 1) { \ + device_desc.field = value; \ + return size; \ + } \ + return -1; \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + +#define DESCRIPTOR_STRING_ATTR(field, buffer) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "%s", buffer); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + if (size >= sizeof(buffer)) \ + return -EINVAL; \ + return strlcpy(buffer, buf, sizeof(buffer)); \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + + +DESCRIPTOR_ATTR(idVendor, "%04x\n") +DESCRIPTOR_ATTR(idProduct, "%04x\n") +DESCRIPTOR_ATTR(bcdDevice, "%04x\n") +DESCRIPTOR_ATTR(bDeviceClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n") +DESCRIPTOR_STRING_ATTR(iManufacturer, manufacturer_string) +DESCRIPTOR_STRING_ATTR(iProduct, product_string) +DESCRIPTOR_STRING_ATTR(iSerial, serial_string) + +static DEVICE_ATTR(funcs_fconf, S_IRUGO | S_IWUSR, + funcs_fconf_show, funcs_fconf_store); +static DEVICE_ATTR(funcs_sconf, S_IRUGO | S_IWUSR, + funcs_sconf_show, funcs_sconf_store); +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); + +static struct device_attribute *slp_multi_usb_attributes[] = { + &dev_attr_idVendor, + &dev_attr_idProduct, + &dev_attr_bcdDevice, + &dev_attr_bDeviceClass, + &dev_attr_bDeviceSubClass, + &dev_attr_bDeviceProtocol, + &dev_attr_iManufacturer, + &dev_attr_iProduct, + &dev_attr_iSerial, + &dev_attr_funcs_fconf, + &dev_attr_funcs_sconf, + &dev_attr_enable, + &dev_attr_state, + NULL +}; + +/*-------------------------------------------------------------------------*/ +/* Composite driver */ + +static int slp_multi_bind_config(struct usb_configuration *c) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + int ret = 0; + + ret = slp_multi_bind_enabled_functions(dev, c); + if (ret) + return ret; + + return 0; +} + +static void slp_multi_unbind_config(struct usb_configuration *c) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + + slp_multi_unbind_enabled_functions(dev, c); +} + +static int slp_multi_bind(struct usb_composite_dev *cdev) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + struct usb_gadget *gadget = cdev->gadget; + int id, ret; + + /* + * Start disconnected. Userspace will connect the gadget once + * it is done configuring the functions. + */ + usb_gadget_disconnect(gadget); + + ret = slp_multi_init_functions(dev->functions, cdev); + if (ret) + return ret; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_MANUFACTURER_IDX].id = id; + device_desc.iManufacturer = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_PRODUCT_IDX].id = id; + device_desc.iProduct = id; + + /* Default strings - should be updated by userspace */ + strncpy(manufacturer_string, "Samsung", sizeof(manufacturer_string)-1); + strncpy(product_string, "TIZEN", sizeof(product_string) - 1); + snprintf(serial_string, 18, "%s", "01234TEST"); + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_SERIAL_IDX].id = id; + device_desc.iSerialNumber = id; + + usb_gadget_set_selfpowered(gadget); + dev->cdev = cdev; + + return 0; +} + +static int slp_multi_usb_unbind(struct usb_composite_dev *cdev) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + + slp_multi_cleanup_functions(dev->functions); + return 0; +} + +/* HACK: slp_multi needs to override setup for accessory to work */ +static int (*composite_setup_func)(struct usb_gadget *gadget, const struct usb_ctrlrequest *c); + +static int +slp_multi_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + struct slp_multi_usb_function *f; + struct slp_multi_usb_function **functions = dev->functions; + unsigned long flags; + int value = -EOPNOTSUPP; + int index = 0; + + req->zero = 0; + req->length = 0; + gadget->ep0->driver_data = cdev; + + for (; (f = *functions++); index++) { + if (f->ctrlrequest) { + value = f->ctrlrequest(f, cdev, c); + if (value >= 0) + break; + } + } + + /* Special case the accessory function. + * It needs to handle control requests before it is enabled. + */ + if (value < 0) + value = composite_setup_func(gadget, c); + + spin_lock_irqsave(&cdev->lock, flags); + if (!dev->connected) { + dev->connected = 1; + } + + spin_unlock_irqrestore(&cdev->lock, flags); + + return value; +} + +static void slp_multi_disconnect(struct usb_composite_dev *cdev) +{ + struct slp_multi_dev *dev = _slp_multi_dev; + + /* accessory HID support can be active while the + accessory function is not actually enabled, + so we need to inform it when we are disconnected. + */ + dev->connected = 0; +} + +static struct usb_composite_driver slp_multi_usb_driver = { + .name = "slp_multi_composite", + .dev = &device_desc, + .strings = dev_strings, + .bind = slp_multi_bind, + .unbind = slp_multi_usb_unbind, + .disconnect = slp_multi_disconnect, + .max_speed = USB_SPEED_HIGH, +}; + +static int slp_multi_create_device(struct slp_multi_dev *dev) +{ + struct device_attribute **attrs = slp_multi_usb_attributes; + struct device_attribute *attr; + int err; + + dev->dev = device_create(slp_multi_class, NULL, + MKDEV(0, 0), NULL, "usb0"); + if (IS_ERR(dev->dev)) + return PTR_ERR(dev->dev); + + dev_set_drvdata(dev->dev, dev); + + while ((attr = *attrs++)) { + err = device_create_file(dev->dev, attr); + if (err) { + device_destroy(slp_multi_class, dev->dev->devt); + return err; + } + } + return 0; +} +static CLASS_ATTR_STRING(version, S_IRUSR | S_IRGRP | S_IROTH, + USB_MODE_VERSION); + +static int __init init(void) +{ + struct slp_multi_dev *dev; + int err; + + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + return -ENOMEM; + } + + dev->disable_depth = 1; + dev->functions = supported_functions; + _slp_multi_dev = dev; + + INIT_LIST_HEAD(&dev->funcs_fconf); + INIT_LIST_HEAD(&dev->funcs_sconf); + mutex_init(&dev->mutex); + + slp_multi_class = class_create(THIS_MODULE, "usb_mode"); + if (IS_ERR(slp_multi_class)) { + err = PTR_ERR(slp_multi_class); + goto err_create_class; + } + + err = class_create_file(slp_multi_class, &class_attr_version.attr); + if (err) { + pr_err("%s: failed to create class file\n", __func__); + goto err_create; + } + + err = slp_multi_create_device(dev); + if (err) { + pr_err("%s: failed to create slp_multi device %d", __func__, err); + goto err_create; + } + + err = usb_composite_probe(&slp_multi_usb_driver); + if (err) { + pr_err("%s: failed to probe driver %d", __func__, err); + goto err_create; + } + + /* HACK: exchange composite's setup with ours */ + composite_setup_func = slp_multi_usb_driver.gadget_driver.setup; + slp_multi_usb_driver.gadget_driver.setup = slp_multi_setup; + + return 0; + +err_create: + class_destroy(slp_multi_class); +err_create_class: + kfree(dev); + + return err; +} +late_initcall(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&slp_multi_usb_driver); + class_destroy(slp_multi_class); + kfree(_slp_multi_dev); + _slp_multi_dev = NULL; +} +module_exit(cleanup); + +MODULE_AUTHOR("Jaewon Kim"); +MODULE_DESCRIPTION("SLP Composite USB Driver similar to Android Composite"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(USB_MODE_VERSION);