summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/gadget/Kconfig48
-rw-r--r--drivers/usb/gadget/Makefile6
-rw-r--r--drivers/usb/gadget/android.c417
-rw-r--r--drivers/usb/gadget/composite.c193
-rw-r--r--drivers/usb/gadget/f_acm.c40
-rw-r--r--drivers/usb/gadget/f_adb.c655
-rw-r--r--drivers/usb/gadget/f_mass_storage.c79
-rw-r--r--drivers/usb/gadget/f_mtp.c1273
-rw-r--r--drivers/usb/gadget/f_rndis.c95
-rw-r--r--drivers/usb/gadget/storage_common.c6
-rw-r--r--drivers/usb/gadget/u_ether.c1
-rw-r--r--drivers/usb/gadget/u_ether.h2
12 files changed, 2785 insertions, 30 deletions
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index bd1751e37f1d..08da28181280 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -837,6 +837,54 @@ config USB_G_PRINTER
For more information, see Documentation/usb/gadget_printer.txt
which includes sample code for accessing the device file.
+config USB_ANDROID
+ boolean "Android Gadget"
+ depends on SWITCH
+ help
+ The Android gadget driver supports multiple USB functions.
+ The functions can be configured via a board file and may be
+ enabled and disabled dynamically.
+
+config USB_ANDROID_ACM
+ boolean "Android gadget ACM serial function"
+ depends on USB_ANDROID
+ help
+ Provides ACM serial function for android gadget driver.
+
+config USB_ANDROID_ADB
+ boolean "Android gadget adb function"
+ depends on USB_ANDROID
+ help
+ Provides adb function for android gadget driver.
+
+config USB_ANDROID_MASS_STORAGE
+ boolean "Android gadget mass storage function"
+ depends on USB_ANDROID && SWITCH
+ help
+ Provides USB mass storage function for android gadget driver.
+
+config USB_ANDROID_MTP
+ boolean "Android MTP function"
+ depends on USB_ANDROID
+ help
+ Provides Media Transfer Protocol (MTP) support for android gadget driver.
+
+config USB_ANDROID_RNDIS
+ boolean "Android gadget RNDIS ethernet function"
+ depends on USB_ANDROID
+ help
+ Provides RNDIS ethernet function for android gadget driver.
+
+config USB_ANDROID_RNDIS_WCEIS
+ boolean "Use Windows Internet Sharing Class/SubClass/Protocol"
+ depends on USB_ANDROID_RNDIS
+ help
+ Causes the driver to look like a Windows-compatible Internet
+ Sharing device, so Windows auto-detects it.
+
+ If you enable this option, the device is no longer CDC ethernet
+ compatible.
+
config USB_CDC_COMPOSITE
tristate "CDC Composite Device (Ethernet and ACM)"
depends on NET
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index f6f822d2841a..d51e31f5592c 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -67,4 +67,10 @@ obj-$(CONFIG_USB_G_DBGP) += g_dbgp.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
+obj-$(CONFIG_USB_ANDROID) += android.o
+obj-$(CONFIG_USB_ANDROID_ACM) += f_acm.o u_serial.o
+obj-$(CONFIG_USB_ANDROID_ADB) += f_adb.o
+obj-$(CONFIG_USB_ANDROID_MASS_STORAGE) += f_mass_storage.o
+obj-$(CONFIG_USB_ANDROID_MTP) += f_mtp.o
+obj-$(CONFIG_USB_ANDROID_RNDIS) += f_rndis.o u_ether.o
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
new file mode 100644
index 000000000000..ed4f5739cb9c
--- /dev/null
+++ b/drivers/usb/gadget/android.c
@@ -0,0 +1,417 @@
+/*
+ * Gadget Driver for Android
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/platform_device.h>
+
+#include <linux/usb/android_composite.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+
+#include "gadget_chips.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 "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+#include "composite.c"
+
+MODULE_AUTHOR("Mike Lockwood");
+MODULE_DESCRIPTION("Android Composite USB Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+static const char longname[] = "Gadget Android";
+
+/* Default vendor and product IDs, overridden by platform data */
+#define VENDOR_ID 0x18D1
+#define PRODUCT_ID 0x0001
+
+struct android_dev {
+ struct usb_composite_dev *cdev;
+ struct usb_configuration *config;
+ int num_products;
+ struct android_usb_product *products;
+ int num_functions;
+ char **functions;
+
+ int product_id;
+ int version;
+};
+
+static struct android_dev *_android_dev;
+
+/* string IDs are assigned dynamically */
+
+#define STRING_MANUFACTURER_IDX 0
+#define STRING_PRODUCT_IDX 1
+#define STRING_SERIAL_IDX 2
+
+/* String Table */
+static struct usb_string strings_dev[] = {
+ /* These dummy values should be overridden by platform data */
+ [STRING_MANUFACTURER_IDX].s = "Android",
+ [STRING_PRODUCT_IDX].s = "Android",
+ [STRING_SERIAL_IDX].s = "0123456789ABCDEF",
+ { } /* 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(0x0200),
+ .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 list_head _functions = LIST_HEAD_INIT(_functions);
+static int _registered_function_count = 0;
+
+static struct android_usb_function *get_function(const char *name)
+{
+ struct android_usb_function *f;
+ list_for_each_entry(f, &_functions, list) {
+ if (!strcmp(name, f->name))
+ return f;
+ }
+ return 0;
+}
+
+static void bind_functions(struct android_dev *dev)
+{
+ struct android_usb_function *f;
+ char **functions = dev->functions;
+ int i;
+
+ for (i = 0; i < dev->num_functions; i++) {
+ char *name = *functions++;
+ f = get_function(name);
+ if (f)
+ f->bind_config(dev->config);
+ else
+ printk(KERN_ERR "function %s not found in bind_functions\n", name);
+ }
+}
+
+static int android_bind_config(struct usb_configuration *c)
+{
+ struct android_dev *dev = _android_dev;
+
+ printk(KERN_DEBUG "android_bind_config\n");
+ dev->config = c;
+
+ /* bind our functions if they have all registered */
+ if (_registered_function_count == dev->num_functions)
+ bind_functions(dev);
+
+ return 0;
+}
+
+static int android_setup_config(struct usb_configuration *c,
+ const struct usb_ctrlrequest *ctrl);
+
+static struct usb_configuration android_config_driver = {
+ .label = "android",
+ .bind = android_bind_config,
+ .setup = android_setup_config,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+ .bMaxPower = 0xFA, /* 500ma */
+};
+
+static int android_setup_config(struct usb_configuration *c,
+ const struct usb_ctrlrequest *ctrl)
+{
+ int i;
+ int ret = -EOPNOTSUPP;
+
+ for (i = 0; i < android_config_driver.next_interface_id; i++) {
+ if (android_config_driver.interface[i]->setup) {
+ ret = android_config_driver.interface[i]->setup(
+ android_config_driver.interface[i], ctrl);
+ if (ret >= 0)
+ return ret;
+ }
+ }
+ return ret;
+}
+
+static int product_has_function(struct android_usb_product *p,
+ struct usb_function *f)
+{
+ char **functions = p->functions;
+ int count = p->num_functions;
+ const char *name = f->name;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (!strcmp(name, *functions++))
+ return 1;
+ }
+ return 0;
+}
+
+static int product_matches_functions(struct android_usb_product *p)
+{
+ struct usb_function *f;
+ list_for_each_entry(f, &android_config_driver.functions, list) {
+ if (product_has_function(p, f) == !!f->disabled)
+ return 0;
+ }
+ return 1;
+}
+
+static int get_product_id(struct android_dev *dev)
+{
+ struct android_usb_product *p = dev->products;
+ int count = dev->num_products;
+ int i;
+
+ if (p) {
+ for (i = 0; i < count; i++, p++) {
+ if (product_matches_functions(p))
+ return p->product_id;
+ }
+ }
+ /* use default product ID */
+ return dev->product_id;
+}
+
+static int android_bind(struct usb_composite_dev *cdev)
+{
+ struct android_dev *dev = _android_dev;
+ struct usb_gadget *gadget = cdev->gadget;
+ int gcnum, id, product_id, ret;
+
+ printk(KERN_INFO "android_bind\n");
+
+ /* 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;
+
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ strings_dev[STRING_SERIAL_IDX].id = id;
+ device_desc.iSerialNumber = id;
+
+ /* register our configuration */
+ ret = usb_add_config(cdev, &android_config_driver);
+ if (ret) {
+ printk(KERN_ERR "usb_add_config failed\n");
+ return ret;
+ }
+
+ gcnum = usb_gadget_controller_number(gadget);
+ if (gcnum >= 0)
+ device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
+ else {
+ /* gadget zero is so simple (for now, no altsettings) that
+ * it SHOULD NOT have problems with bulk-capable hardware.
+ * so just warn about unrcognized controllers -- don't panic.
+ *
+ * things like configuration and altsetting numbering
+ * can need hardware-specific attention though.
+ */
+ pr_warning("%s: controller '%s' not recognized\n",
+ longname, gadget->name);
+ device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
+ }
+
+ usb_gadget_set_selfpowered(gadget);
+ dev->cdev = cdev;
+ product_id = get_product_id(dev);
+ device_desc.idProduct = __constant_cpu_to_le16(product_id);
+ cdev->desc.idProduct = device_desc.idProduct;
+
+ return 0;
+}
+
+static struct usb_composite_driver android_usb_driver = {
+ .name = "android_usb",
+ .dev = &device_desc,
+ .strings = dev_strings,
+ .bind = android_bind,
+ .enable_function = android_enable_function,
+};
+
+void android_register_function(struct android_usb_function *f)
+{
+ struct android_dev *dev = _android_dev;
+
+ printk(KERN_INFO "android_register_function %s\n", f->name);
+ list_add_tail(&f->list, &_functions);
+ _registered_function_count++;
+
+ /* bind our functions if they have all registered
+ * and the main driver has bound.
+ */
+ if (dev && dev->config && _registered_function_count == dev->num_functions)
+ bind_functions(dev);
+}
+
+void android_enable_function(struct usb_function *f, int enable)
+{
+ struct android_dev *dev = _android_dev;
+ int disable = !enable;
+ int product_id;
+
+ if (!!f->disabled != disable) {
+ usb_function_set_enabled(f, !disable);
+
+#ifdef CONFIG_USB_ANDROID_RNDIS
+ if (!strcmp(f->name, "rndis")) {
+ struct usb_function *func;
+
+ /* We need to specify the COMM class in the device descriptor
+ * if we are using RNDIS.
+ */
+ if (enable)
+#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS
+ dev->cdev->desc.bDeviceClass = USB_CLASS_WIRELESS_CONTROLLER;
+#else
+ dev->cdev->desc.bDeviceClass = USB_CLASS_COMM;
+#endif
+ else
+ dev->cdev->desc.bDeviceClass = USB_CLASS_PER_INTERFACE;
+
+ /* Windows does not support other interfaces when RNDIS is enabled,
+ * so we disable UMS and MTP when RNDIS is on.
+ */
+ list_for_each_entry(func, &android_config_driver.functions, list) {
+ if (!strcmp(func->name, "usb_mass_storage")
+ || !strcmp(func->name, "mtp")) {
+ usb_function_set_enabled(func, !enable);
+ }
+ }
+ }
+#endif
+
+ product_id = get_product_id(dev);
+ device_desc.idProduct = __constant_cpu_to_le16(product_id);
+ if (dev->cdev)
+ dev->cdev->desc.idProduct = device_desc.idProduct;
+ usb_composite_force_reset(dev->cdev);
+ }
+}
+
+static int android_probe(struct platform_device *pdev)
+{
+ struct android_usb_platform_data *pdata = pdev->dev.platform_data;
+ struct android_dev *dev = _android_dev;
+
+ printk(KERN_INFO "android_probe pdata: %p\n", pdata);
+
+ if (pdata) {
+ dev->products = pdata->products;
+ dev->num_products = pdata->num_products;
+ dev->functions = pdata->functions;
+ dev->num_functions = pdata->num_functions;
+ if (pdata->vendor_id)
+ device_desc.idVendor =
+ __constant_cpu_to_le16(pdata->vendor_id);
+ if (pdata->product_id) {
+ dev->product_id = pdata->product_id;
+ device_desc.idProduct =
+ __constant_cpu_to_le16(pdata->product_id);
+ }
+ if (pdata->version)
+ dev->version = pdata->version;
+
+ if (pdata->product_name)
+ strings_dev[STRING_PRODUCT_IDX].s = pdata->product_name;
+ if (pdata->manufacturer_name)
+ strings_dev[STRING_MANUFACTURER_IDX].s =
+ pdata->manufacturer_name;
+ if (pdata->serial_number)
+ strings_dev[STRING_SERIAL_IDX].s = pdata->serial_number;
+ }
+
+ return usb_composite_register(&android_usb_driver);
+}
+
+static struct platform_driver android_platform_driver = {
+ .driver = { .name = "android_usb", },
+ .probe = android_probe,
+};
+
+static int __init init(void)
+{
+ struct android_dev *dev;
+
+ printk(KERN_INFO "android init\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* set default values, which should be overridden by platform data */
+ dev->product_id = PRODUCT_ID;
+ _android_dev = dev;
+
+ return platform_driver_register(&android_platform_driver);
+}
+module_init(init);
+
+static void __exit cleanup(void)
+{
+ usb_composite_unregister(&android_usb_driver);
+ platform_driver_unregister(&android_platform_driver);
+ kfree(_android_dev);
+ _android_dev = NULL;
+}
+module_exit(cleanup);
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 1160c55de7f2..840c41e2f33e 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -71,6 +71,59 @@ MODULE_PARM_DESC(iSerialNumber, "SerialNumber string");
/*-------------------------------------------------------------------------*/
+static ssize_t enable_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_function *f = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", !f->disabled);
+}
+
+static ssize_t enable_store(
+ struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct usb_function *f = dev_get_drvdata(dev);
+ struct usb_composite_driver *driver = f->config->cdev->driver;
+ int value;
+
+ sscanf(buf, "%d", &value);
+ if (driver->enable_function)
+ driver->enable_function(f, value);
+ else
+ usb_function_set_enabled(f, value);
+
+ return size;
+}
+
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store);
+
+void usb_function_set_enabled(struct usb_function *f, int enabled)
+{
+ f->disabled = !enabled;
+ kobject_uevent(&f->dev->kobj, KOBJ_CHANGE);
+}
+
+
+void usb_composite_force_reset(struct usb_composite_dev *cdev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ /* force reenumeration */
+ if (cdev && cdev->gadget &&
+ cdev->gadget->speed != USB_SPEED_UNKNOWN) {
+ /* avoid sending a disconnect switch event until after we disconnect */
+ cdev->mute_switch = 1;
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ usb_gadget_disconnect(cdev->gadget);
+ msleep(10);
+ usb_gadget_connect(cdev->gadget);
+ } else {
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ }
+}
+
/**
* usb_add_function() - add a function to a configuration
* @config: the configuration
@@ -88,15 +141,30 @@ MODULE_PARM_DESC(iSerialNumber, "SerialNumber string");
int usb_add_function(struct usb_configuration *config,
struct usb_function *function)
{
+ struct usb_composite_dev *cdev = config->cdev;
int value = -EINVAL;
+ int index;
- DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n",
+ DBG(cdev, "adding '%s'/%p to config '%s'/%p\n",
function->name, function,
config->label, config);
if (!function->set_alt || !function->disable)
goto done;
+ index = atomic_inc_return(&cdev->driver->function_count);
+ function->dev = device_create(cdev->driver->class, NULL,
+ MKDEV(0, index), NULL, function->name);
+ if (IS_ERR(function->dev))
+ return PTR_ERR(function->dev);
+
+ value = device_create_file(function->dev, &dev_attr_enable);
+ if (value < 0) {
+ device_destroy(cdev->driver->class, MKDEV(0, index));
+ return value;
+ }
+ dev_set_drvdata(function->dev, function);
+
function->config = config;
list_add_tail(&function->list, &config->functions);
@@ -122,7 +190,7 @@ int usb_add_function(struct usb_configuration *config,
done:
if (value)
- DBG(config->cdev, "adding '%s'/%p --> %d\n",
+ DBG(cdev, "adding '%s'/%p --> %d\n",
function->name, function, value);
return value;
}
@@ -232,17 +300,19 @@ static int config_buf(struct usb_configuration *config,
enum usb_device_speed speed, void *buf, u8 type)
{
struct usb_config_descriptor *c = buf;
+ struct usb_interface_descriptor *intf;
void *next = buf + USB_DT_CONFIG_SIZE;
int len = USB_BUFSIZ - USB_DT_CONFIG_SIZE;
struct usb_function *f;
int status;
+ int interfaceCount = 0;
+ u8 *dest;
/* write the config descriptor */
c = buf;
c->bLength = USB_DT_CONFIG_SIZE;
c->bDescriptorType = type;
- /* wTotalLength is written later */
- c->bNumInterfaces = config->next_interface_id;
+ /* wTotalLength and bNumInterfaces are written later */
c->bConfigurationValue = config->bConfigurationValue;
c->iConfiguration = config->iConfiguration;
c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes;
@@ -261,23 +331,40 @@ static int config_buf(struct usb_configuration *config,
/* add each function's descriptors */
list_for_each_entry(f, &config->functions, list) {
struct usb_descriptor_header **descriptors;
+ struct usb_descriptor_header *descriptor;
if (speed == USB_SPEED_HIGH)
descriptors = f->hs_descriptors;
else
descriptors = f->descriptors;
- if (!descriptors)
+ if (f->disabled || !descriptors || descriptors[0] == NULL)
continue;
status = usb_descriptor_fillbuf(next, len,
(const struct usb_descriptor_header **) descriptors);
if (status < 0)
return status;
+
+ /* set interface numbers dynamically */
+ dest = next;
+ while ((descriptor = *descriptors++) != NULL) {
+ intf = (struct usb_interface_descriptor *)dest;
+ if (intf->bDescriptorType == USB_DT_INTERFACE) {
+ /* don't increment bInterfaceNumber for alternate settings */
+ if (intf->bAlternateSetting == 0)
+ intf->bInterfaceNumber = interfaceCount++;
+ else
+ intf->bInterfaceNumber = interfaceCount - 1;
+ }
+ dest += intf->bLength;
+ }
+
len -= status;
next += status;
}
len = next - buf;
c->wTotalLength = cpu_to_le16(len);
+ c->bNumInterfaces = interfaceCount;
return len;
}
@@ -424,6 +511,8 @@ static int set_config(struct usb_composite_dev *cdev,
if (!f)
break;
+ if (f->disabled)
+ continue;
/*
* Record which endpoints are used by the function. This is used
@@ -463,6 +552,8 @@ static int set_config(struct usb_composite_dev *cdev,
power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW;
done:
usb_gadget_vbus_draw(gadget, power);
+
+ schedule_work(&cdev->switch_work);
return result;
}
@@ -822,6 +913,21 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
case USB_DT_STRING:
value = get_string(cdev, req->buf,
w_index, w_value & 0xff);
+
+ /* Allow functions to handle USB_DT_STRING.
+ * This is required for MTP.
+ */
+ if (value < 0) {
+ struct usb_configuration *cfg;
+ list_for_each_entry(cfg, &cdev->configs, list) {
+ if (cfg && cfg->setup) {
+ value = cfg->setup(cfg, ctrl);
+ if (value >= 0)
+ break;
+ }
+ }
+ }
+
if (value >= 0)
value = min(w_length, (u16) value);
break;
@@ -847,11 +953,11 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
case USB_REQ_GET_CONFIGURATION:
if (ctrl->bRequestType != USB_DIR_IN)
goto unknown;
- if (cdev->config)
+ if (cdev->config) {
*(u8 *)req->buf = cdev->config->bConfigurationValue;
- else
+ value = min(w_length, (u16) 1);
+ } else
*(u8 *)req->buf = 0;
- value = min(w_length, (u16) 1);
break;
/* function drivers must handle get/set altsetting; if there's
@@ -901,6 +1007,9 @@ unknown:
*/
switch (ctrl->bRequestType & USB_RECIP_MASK) {
case USB_RECIP_INTERFACE:
+ if (cdev->config == NULL)
+ return value;
+
f = cdev->config->interface[intf];
break;
@@ -925,6 +1034,25 @@ unknown:
value = c->setup(c, ctrl);
}
+ /* If the vendor request is not processed (value < 0),
+ * call all device registered configure setup callbacks
+ * to process it.
+ * This is used to handle the following cases:
+ * - vendor request is for the device and arrives before
+ * setconfiguration.
+ * - Some devices are required to handle vendor request before
+ * setconfiguration such as MTP, USBNET.
+ */
+
+ if (value < 0) {
+ struct usb_configuration *cfg;
+
+ list_for_each_entry(cfg, &cdev->configs, list) {
+ if (cfg && cfg->setup)
+ value = cfg->setup(cfg, ctrl);
+ }
+ }
+
goto done;
}
@@ -956,8 +1084,14 @@ static void composite_disconnect(struct usb_gadget *gadget)
spin_lock_irqsave(&cdev->lock, flags);
if (cdev->config)
reset_config(cdev);
+
if (composite->disconnect)
composite->disconnect(cdev);
+
+ if (cdev->mute_switch)
+ cdev->mute_switch = 0;
+ else
+ schedule_work(&cdev->switch_work);
spin_unlock_irqrestore(&cdev->lock, flags);
}
@@ -1019,6 +1153,8 @@ composite_unbind(struct usb_gadget *gadget)
kfree(cdev->req->buf);
usb_ep_free_request(gadget->ep0, cdev->req);
}
+
+ switch_dev_unregister(&cdev->sdev);
kfree(cdev);
set_gadget_data(gadget, NULL);
device_remove_file(&gadget->dev, &dev_attr_suspended);
@@ -1047,6 +1183,19 @@ string_override(struct usb_gadget_strings **tab, u8 id, const char *s)
}
}
+static void
+composite_switch_work(struct work_struct *data)
+{
+ struct usb_composite_dev *cdev =
+ container_of(data, struct usb_composite_dev, switch_work);
+ struct usb_configuration *config = cdev->config;
+
+ if (config)
+ switch_set_state(&cdev->sdev, config->bConfigurationValue);
+ else
+ switch_set_state(&cdev->sdev, 0);
+}
+
static int composite_bind(struct usb_gadget *gadget)
{
struct usb_composite_dev *cdev;
@@ -1098,6 +1247,12 @@ static int composite_bind(struct usb_gadget *gadget)
if (status < 0)
goto fail;
+ cdev->sdev.name = "usb_configuration";
+ status = switch_dev_register(&cdev->sdev);
+ if (status < 0)
+ goto fail;
+ INIT_WORK(&cdev->switch_work, composite_switch_work);
+
cdev->desc = *composite->dev;
cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
@@ -1172,6 +1327,23 @@ composite_resume(struct usb_gadget *gadget)
cdev->suspended = 0;
}
+static int
+composite_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct usb_function *f = dev_get_drvdata(dev);
+
+ if (!f) {
+ /* this happens when the device is first created */
+ return 0;
+ }
+
+ if (add_uevent_var(env, "FUNCTION=%s", f->name))
+ return -ENOMEM;
+ if (add_uevent_var(env, "ENABLED=%d", !f->disabled))
+ return -ENOMEM;
+ return 0;
+}
+
/*-------------------------------------------------------------------------*/
static struct usb_gadget_driver composite_driver = {
@@ -1217,6 +1389,11 @@ int usb_composite_register(struct usb_composite_driver *driver)
composite_driver.driver.name = driver->name;
composite = driver;
+ driver->class = class_create(THIS_MODULE, "usb_composite");
+ if (IS_ERR(driver->class))
+ return PTR_ERR(driver->class);
+ driver->class->dev_uevent = composite_uevent;
+
return usb_gadget_register_driver(&composite_driver);
}
diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c
index d47a123f15ab..e3e5410d922f 100644
--- a/drivers/usb/gadget/f_acm.c
+++ b/drivers/usb/gadget/f_acm.c
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
+#include <linux/usb/android_composite.h>
#include "u_serial.h"
#include "gadget_chips.h"
@@ -405,10 +406,10 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
usb_ep_disable(acm->notify);
} else {
VDBG(cdev, "init acm ctrl interface %d\n", intf);
- acm->notify_desc = ep_choose(cdev->gadget,
- acm->hs.notify,
- acm->fs.notify);
}
+ acm->notify_desc = ep_choose(cdev->gadget,
+ acm->hs.notify,
+ acm->fs.notify);
usb_ep_enable(acm->notify, acm->notify_desc);
acm->notify->driver_data = acm;
@@ -418,11 +419,11 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
gserial_disconnect(&acm->port);
} else {
DBG(cdev, "activate acm ttyGS%d\n", acm->port_num);
- acm->port.in_desc = ep_choose(cdev->gadget,
- acm->hs.in, acm->fs.in);
- acm->port.out_desc = ep_choose(cdev->gadget,
- acm->hs.out, acm->fs.out);
}
+ acm->port.in_desc = ep_choose(cdev->gadget,
+ acm->hs.in, acm->fs.in);
+ acm->port.out_desc = ep_choose(cdev->gadget,
+ acm->hs.out, acm->fs.out);
gserial_connect(&acm->port, acm->port_num);
} else
@@ -782,3 +783,28 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num)
kfree(acm);
return status;
}
+
+#ifdef CONFIG_USB_ANDROID_ACM
+
+int acm_function_bind_config(struct usb_configuration *c)
+{
+ int ret = acm_bind_config(c, 0);
+ if (ret == 0)
+ gserial_setup(c->cdev->gadget, 1);
+ return ret;
+}
+
+static struct android_usb_function acm_function = {
+ .name = "acm",
+ .bind_config = acm_function_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_acm init\n");
+ android_register_function(&acm_function);
+ return 0;
+}
+module_init(init);
+
+#endif /* CONFIG_USB_ANDROID_ACM */
diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c
new file mode 100644
index 000000000000..a0b0774b9556
--- /dev/null
+++ b/drivers/usb/gadget/f_adb.c
@@ -0,0 +1,655 @@
+/*
+ * Gadget Driver for Android ADB
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/usb/android_composite.h>
+
+#define BULK_BUFFER_SIZE 4096
+
+/* number of tx requests to allocate */
+#define TX_REQ_MAX 4
+
+static const char shortname[] = "android_adb";
+
+struct adb_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 adb_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xFF,
+ .bInterfaceSubClass = 0x42,
+ .bInterfaceProtocol = 1,
+};
+
+static struct usb_endpoint_descriptor adb_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 adb_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 adb_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 adb_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_adb_descs[] = {
+ (struct usb_descriptor_header *) &adb_interface_desc,
+ (struct usb_descriptor_header *) &adb_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &adb_fullspeed_out_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_adb_descs[] = {
+ (struct usb_descriptor_header *) &adb_interface_desc,
+ (struct usb_descriptor_header *) &adb_highspeed_in_desc,
+ (struct usb_descriptor_header *) &adb_highspeed_out_desc,
+ NULL,
+};
+
+
+/* temporary variable used between adb_open() and adb_gadget_bind() */
+static struct adb_dev *_adb_dev;
+
+static atomic_t adb_enable_excl;
+
+static inline struct adb_dev *func_to_dev(struct usb_function *f)
+{
+ return container_of(f, struct adb_dev, function);
+}
+
+
+static struct usb_request *adb_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 adb_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int _lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void _unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+void req_put(struct adb_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 *req_get(struct adb_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 adb_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct adb_dev *dev = _adb_dev;
+
+ if (req->status != 0)
+ dev->error = 1;
+
+ req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void adb_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct adb_dev *dev = _adb_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->error = 1;
+
+ wake_up(&dev->read_wq);
+}
+
+static int __init create_bulk_endpoints(struct adb_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 adb 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 = adb_request_new(dev->ep_out, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = adb_complete_out;
+ dev->rx_req = req;
+
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = adb_request_new(dev->ep_in, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = adb_complete_in;
+ req_put(dev, &dev->tx_idle, req);
+ }
+
+ return 0;
+
+fail:
+ printk(KERN_ERR "adb_bind() could not allocate requests\n");
+ return -1;
+}
+
+static ssize_t adb_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct adb_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret;
+
+ DBG(cdev, "adb_read(%d)\n", count);
+
+ if (count > BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ if (_lock(&dev->read_excl))
+ return -EBUSY;
+
+ /* we will block until we're online */
+ while (!(dev->online || dev->error)) {
+ DBG(cdev, "adb_read: waiting for online state\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ (dev->online || dev->error));
+ if (ret < 0) {
+ _unlock(&dev->read_excl);
+ return ret;
+ }
+ }
+ if (dev->error) {
+ r = -EIO;
+ goto done;
+ }
+
+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) {
+ DBG(cdev, "adb_read: failed to queue req %p (%d)\n", req, ret);
+ r = -EIO;
+ dev->error = 1;
+ goto done;
+ } else {
+ DBG(cdev, "rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ dev->error = 1;
+ r = ret;
+ 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;
+
+ DBG(cdev, "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:
+ _unlock(&dev->read_excl);
+ DBG(cdev, "adb_read returning %d\n", r);
+ return r;
+}
+
+static ssize_t adb_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct adb_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int ret;
+
+ DBG(cdev, "adb_write(%d)\n", count);
+
+ if (_lock(&dev->write_excl))
+ return -EBUSY;
+
+ while (count > 0) {
+ if (dev->error) {
+ DBG(cdev, "adb_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = req_get(dev, &dev->tx_idle)) || dev->error));
+
+ if (ret < 0) {
+ r = ret;
+ break;
+ }
+
+ if (req != 0) {
+ if (count > BULK_BUFFER_SIZE)
+ xfer = 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) {
+ DBG(cdev, "adb_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)
+ req_put(dev, &dev->tx_idle, req);
+
+ _unlock(&dev->write_excl);
+ DBG(cdev, "adb_write returning %d\n", r);
+ return r;
+}
+
+static int adb_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "adb_open\n");
+ if (_lock(&_adb_dev->open_excl))
+ return -EBUSY;
+
+ fp->private_data = _adb_dev;
+
+ /* clear the error latch */
+ _adb_dev->error = 0;
+
+ return 0;
+}
+
+static int adb_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "adb_release\n");
+ _unlock(&_adb_dev->open_excl);
+ return 0;
+}
+
+/* file operations for ADB device /dev/android_adb */
+static struct file_operations adb_fops = {
+ .owner = THIS_MODULE,
+ .read = adb_read,
+ .write = adb_write,
+ .open = adb_open,
+ .release = adb_release,
+};
+
+static struct miscdevice adb_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = shortname,
+ .fops = &adb_fops,
+};
+
+static int adb_enable_open(struct inode *ip, struct file *fp)
+{
+ if (atomic_inc_return(&adb_enable_excl) != 1) {
+ atomic_dec(&adb_enable_excl);
+ return -EBUSY;
+ }
+
+ printk(KERN_INFO "enabling adb\n");
+ android_enable_function(&_adb_dev->function, 1);
+
+ return 0;
+}
+
+static int adb_enable_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "disabling adb\n");
+ android_enable_function(&_adb_dev->function, 0);
+ atomic_dec(&adb_enable_excl);
+ return 0;
+}
+
+static const struct file_operations adb_enable_fops = {
+ .owner = THIS_MODULE,
+ .open = adb_enable_open,
+ .release = adb_enable_release,
+};
+
+static struct miscdevice adb_enable_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "android_adb_enable",
+ .fops = &adb_enable_fops,
+};
+
+static int
+adb_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct adb_dev *dev = func_to_dev(f);
+ int id;
+ int ret;
+
+ dev->cdev = cdev;
+ DBG(cdev, "adb_function_bind dev: %p\n", dev);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ adb_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = create_bulk_endpoints(dev, &adb_fullspeed_in_desc,
+ &adb_fullspeed_out_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ adb_highspeed_in_desc.bEndpointAddress =
+ adb_fullspeed_in_desc.bEndpointAddress;
+ adb_highspeed_out_desc.bEndpointAddress =
+ adb_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+adb_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct adb_dev *dev = func_to_dev(f);
+ struct usb_request *req;
+
+ spin_lock_irq(&dev->lock);
+
+ adb_request_free(dev->rx_req, dev->ep_out);
+ while ((req = req_get(dev, &dev->tx_idle)))
+ adb_request_free(req, dev->ep_in);
+
+ dev->online = 0;
+ dev->error = 1;
+ spin_unlock_irq(&dev->lock);
+
+ misc_deregister(&adb_device);
+ misc_deregister(&adb_enable_device);
+ kfree(_adb_dev);
+ _adb_dev = NULL;
+}
+
+static int adb_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct adb_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt);
+ ret = usb_ep_enable(dev->ep_in,
+ ep_choose(cdev->gadget,
+ &adb_highspeed_in_desc,
+ &adb_fullspeed_in_desc));
+ if (ret)
+ return ret;
+ ret = usb_ep_enable(dev->ep_out,
+ ep_choose(cdev->gadget,
+ &adb_highspeed_out_desc,
+ &adb_fullspeed_out_desc));
+ 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 adb_function_disable(struct usb_function *f)
+{
+ struct adb_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "adb_function_disable\n");
+ 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 adb_bind_config(struct usb_configuration *c)
+{
+ struct adb_dev *dev;
+ int ret;
+
+ printk(KERN_INFO "adb_bind_config\n");
+
+ 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);
+
+ dev->cdev = c->cdev;
+ dev->function.name = "adb";
+ dev->function.descriptors = fs_adb_descs;
+ dev->function.hs_descriptors = hs_adb_descs;
+ dev->function.bind = adb_function_bind;
+ dev->function.unbind = adb_function_unbind;
+ dev->function.set_alt = adb_function_set_alt;
+ dev->function.disable = adb_function_disable;
+
+ /* start disabled */
+ dev->function.disabled = 1;
+
+ /* _adb_dev must be set before calling usb_gadget_register_driver */
+ _adb_dev = dev;
+
+ ret = misc_register(&adb_device);
+ if (ret)
+ goto err1;
+ ret = misc_register(&adb_enable_device);
+ if (ret)
+ goto err2;
+
+ ret = usb_add_function(c, &dev->function);
+ if (ret)
+ goto err3;
+
+ return 0;
+
+err3:
+ misc_deregister(&adb_enable_device);
+err2:
+ misc_deregister(&adb_device);
+err1:
+ kfree(dev);
+ printk(KERN_ERR "adb gadget driver failed to initialize\n");
+ return ret;
+}
+
+static struct android_usb_function adb_function = {
+ .name = "adb",
+ .bind_config = adb_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_adb init\n");
+ android_register_function(&adb_function);
+ return 0;
+}
+module_init(init);
diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c
index 32cce029f65c..9e37e0863143 100644
--- a/drivers/usb/gadget/f_mass_storage.c
+++ b/drivers/usb/gadget/f_mass_storage.c
@@ -295,7 +295,12 @@
#include "gadget_chips.h"
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+#include <linux/usb/android_composite.h>
+#include <linux/platform_device.h>
+#define FUNCTION_NAME "usb_mass_storage"
+#endif
/*------------------------------------------------------------------------*/
@@ -424,6 +429,10 @@ struct fsg_config {
u16 release;
char can_stall;
+
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+ struct platform_device *pdev;
+#endif
};
@@ -2746,7 +2755,13 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common,
curlun->ro = lcfg->cdrom || lcfg->ro;
curlun->removable = lcfg->removable;
curlun->dev.release = fsg_lun_release;
+
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+ /* use "usb_mass_storage" platform device as parent */
+ curlun->dev.parent = &cfg->pdev->dev;
+#else
curlun->dev.parent = &gadget->dev;
+#endif
/* curlun->dev.driver = &fsg_driver.driver; XXX */
dev_set_drvdata(&curlun->dev, &common->filesem);
dev_set_name(&curlun->dev,
@@ -3029,7 +3044,11 @@ static int fsg_bind_config(struct usb_composite_dev *cdev,
if (unlikely(!fsg))
return -ENOMEM;
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+ fsg->function.name = FUNCTION_NAME;
+#else
fsg->function.name = FSG_DRIVER_DESC;
+#endif
fsg->function.strings = fsg_strings_array;
fsg->function.bind = fsg_bind;
fsg->function.unbind = fsg_unbind;
@@ -3153,3 +3172,63 @@ fsg_common_from_params(struct fsg_common *common,
return fsg_common_init(common, cdev, &cfg);
}
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+
+static struct fsg_config fsg_cfg;
+
+static int fsg_probe(struct platform_device *pdev)
+{
+ struct usb_mass_storage_platform_data *pdata = pdev->dev.platform_data;
+ int i, nluns;
+
+ printk(KERN_INFO "fsg_probe pdev: %p, pdata: %p\n", pdev, pdata);
+ if (!pdata)
+ return -1;
+
+ nluns = pdata->nluns;
+ if (nluns > FSG_MAX_LUNS)
+ nluns = FSG_MAX_LUNS;
+ fsg_cfg.nluns = nluns;
+ for (i = 0; i < nluns; i++)
+ fsg_cfg.luns[i].removable = 1;
+
+ fsg_cfg.vendor_name = pdata->vendor;
+ fsg_cfg.product_name = pdata->product;
+ fsg_cfg.release = pdata->release;
+ fsg_cfg.can_stall = 0;
+ fsg_cfg.pdev = pdev;
+
+ return 0;
+}
+
+static struct platform_driver fsg_platform_driver = {
+ .driver = { .name = FUNCTION_NAME, },
+ .probe = fsg_probe,
+};
+
+int mass_storage_bind_config(struct usb_configuration *c)
+{
+ struct fsg_common *common = fsg_common_init(NULL, c->cdev, &fsg_cfg);
+ if (IS_ERR(common))
+ return -1;
+ return fsg_add(c->cdev, c, common);
+}
+
+static struct android_usb_function mass_storage_function = {
+ .name = FUNCTION_NAME,
+ .bind_config = mass_storage_bind_config,
+};
+
+static int __init init(void)
+{
+ int rc;
+ printk(KERN_INFO "f_mass_storage init\n");
+ rc = platform_driver_register(&fsg_platform_driver);
+ if (rc != 0)
+ return rc;
+ android_register_function(&mass_storage_function);
+ return 0;
+}module_init(init);
+
+#endif /* CONFIG_USB_ANDROID_MASS_STORAGE */
+
diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c
new file mode 100644
index 000000000000..2c61a2613475
--- /dev/null
+++ b/drivers/usb/gadget/f_mtp.c
@@ -0,0 +1,1273 @@
+/*
+ * Gadget Function Driver for MTP
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/usb.h>
+#include <linux/usb_usual.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/android_composite.h>
+#include <linux/usb/f_mtp.h>
+
+#define BULK_BUFFER_SIZE 16384
+#define INTR_BUFFER_SIZE 28
+
+/* String IDs */
+#define INTERFACE_STRING_INDEX 0
+
+/* values for mtp_dev.state */
+#define STATE_OFFLINE 0 /* initial state, disconnected */
+#define STATE_READY 1 /* ready for userspace calls */
+#define STATE_BUSY 2 /* processing userspace calls */
+#define STATE_CANCELED 3 /* transaction canceled by host */
+#define STATE_ERROR 4 /* error from completion routine */
+
+/* number of tx and rx requests to allocate */
+#define TX_REQ_MAX 4
+#define RX_REQ_MAX 2
+
+/* IO Thread commands */
+#define ANDROID_THREAD_QUIT 1
+#define ANDROID_THREAD_SEND_FILE 2
+#define ANDROID_THREAD_RECEIVE_FILE 3
+
+/* ID for Microsoft MTP OS String */
+#define MTP_OS_STRING_ID 0xEE
+
+/* MTP class reqeusts */
+#define MTP_REQ_CANCEL 0x64
+#define MTP_REQ_GET_EXT_EVENT_DATA 0x65
+#define MTP_REQ_RESET 0x66
+#define MTP_REQ_GET_DEVICE_STATUS 0x67
+
+/* constants for device status */
+#define MTP_RESPONSE_OK 0x2001
+#define MTP_RESPONSE_DEVICE_BUSY 0x2019
+
+static const char shortname[] = "mtp_usb";
+
+struct mtp_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ spinlock_t lock;
+
+ /* appear as MTP or PTP when enumerating */
+ int interface_mode;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+ struct usb_ep *ep_intr;
+
+ int state;
+
+ /* synchronize access to our device file */
+ atomic_t open_excl;
+
+ struct list_head tx_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ wait_queue_head_t intr_wq;
+ struct usb_request *rx_req[RX_REQ_MAX];
+ struct usb_request *intr_req;
+ int rx_done;
+
+ /* synchronize access to interrupt endpoint */
+ struct mutex intr_mutex;
+ /* true if interrupt endpoint is busy */
+ int intr_busy;
+
+ /* for our file IO thread */
+ struct task_struct *thread;
+ /* current command for IO thread (or zero for none) */
+ int thread_command;
+ struct file *thread_file;
+ loff_t thread_file_offset;
+ size_t thread_file_length;
+ /* used to wait for thread to complete current command */
+ struct completion thread_wait;
+ /* result from current command */
+ int thread_result;
+};
+
+static struct usb_interface_descriptor mtp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
+ .bInterfaceProtocol = 0,
+};
+
+static struct usb_interface_descriptor ptp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 1,
+};
+
+static struct usb_endpoint_descriptor mtp_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 mtp_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 mtp_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 mtp_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor mtp_intr_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE),
+ .bInterval = 6,
+};
+
+static struct usb_descriptor_header *fs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *fs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_string mtp_string_defs[] = {
+ /* Naming interface "MTP" so libmtp will recognize us */
+ [INTERFACE_STRING_INDEX].s = "MTP",
+ { }, /* end of list */
+};
+
+static struct usb_gadget_strings mtp_string_table = {
+ .language = 0x0409, /* en-US */
+ .strings = mtp_string_defs,
+};
+
+static struct usb_gadget_strings *mtp_strings[] = {
+ &mtp_string_table,
+ NULL,
+};
+
+/* Microsoft MTP OS String */
+static u8 mtp_os_string[] = {
+ 18, /* sizeof(mtp_os_string) */
+ USB_DT_STRING,
+ /* Signature field: "MSFT100" */
+ 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
+ /* vendor code */
+ 1,
+ /* padding */
+ 0
+};
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mtp_ext_config_desc_header {
+ __le32 dwLength;
+ __u16 bcdVersion;
+ __le16 wIndex;
+ __u8 bCount;
+ __u8 reserved[7];
+};
+
+/* Microsoft Extended Configuration Descriptor Function Section */
+struct mtp_ext_config_desc_function {
+ __u8 bFirstInterfaceNumber;
+ __u8 bInterfaceCount;
+ __u8 compatibleID[8];
+ __u8 subCompatibleID[8];
+ __u8 reserved[6];
+};
+
+/* MTP Extended Configuration Descriptor */
+struct {
+ struct mtp_ext_config_desc_header header;
+ struct mtp_ext_config_desc_function function;
+} mtp_ext_config_desc = {
+ .header = {
+ .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)),
+ .bcdVersion = __constant_cpu_to_le16(0x0100),
+ .wIndex = __constant_cpu_to_le16(4),
+ .bCount = __constant_cpu_to_le16(1),
+ },
+ .function = {
+ .bFirstInterfaceNumber = 0,
+ .bInterfaceCount = 1,
+ .compatibleID = { 'M', 'T', 'P' },
+ },
+};
+
+struct mtp_device_status {
+ __le16 wLength;
+ __le16 wCode;
+};
+
+/* temporary variable used between mtp_open() and mtp_gadget_bind() */
+static struct mtp_dev *_mtp_dev;
+
+static inline struct mtp_dev *func_to_dev(struct usb_function *f)
+{
+ return container_of(f, struct mtp_dev, function);
+}
+
+static struct usb_request *mtp_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 mtp_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int _lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void _unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+static void req_put(struct mtp_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 */
+static struct usb_request *req_get(struct mtp_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 mtp_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ wake_up(&dev->read_wq);
+}
+
+static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ DBG(dev->cdev, "mtp_complete_intr status: %d actual: %d\n", req->status, req->actual);
+ dev->intr_busy = 0;
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ wake_up(&dev->intr_wq);
+}
+
+static int __init create_bulk_endpoints(struct mtp_dev *dev,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc,
+ struct usb_endpoint_descriptor *intr_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 mtp ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = 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 mtp ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, intr_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_intr = ep;
+
+ /* now allocate requests for our endpoints */
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_in, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_in;
+ req_put(dev, &dev->tx_idle, req);
+ }
+ for (i = 0; i < RX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_out, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_out;
+ dev->rx_req[i] = req;
+ }
+ req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_intr;
+ dev->intr_req = req;
+
+ return 0;
+
+fail:
+ printk(KERN_ERR "mtp_bind() could not allocate requests\n");
+ return -1;
+}
+
+static ssize_t mtp_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret = 0;
+
+ DBG(cdev, "mtp_read(%d)\n", count);
+
+ if (count > BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ /* we will block until we're online */
+ DBG(cdev, "mtp_read: waiting for online state\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->state != STATE_OFFLINE);
+ if (ret < 0) {
+ r = ret;
+ goto done;
+ }
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+requeue_req:
+ /* queue a request */
+ req = dev->rx_req[0];
+ req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ goto done;
+ } else {
+ DBG(cdev, "rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ r = ret;
+ goto done;
+ }
+ if (dev->state == STATE_BUSY) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ DBG(cdev, "rx %p %d\n", req, req->actual);
+ xfer = (req->actual < count) ? req->actual : count;
+ r = xfer;
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+ } else
+ r = -EIO;
+
+done:
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_read returning %d\n", r);
+ return r;
+}
+
+static ssize_t mtp_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int ret;
+
+ DBG(cdev, "mtp_write(%d)\n", count);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ return -ENODEV;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ while (count > 0) {
+ if (dev->state != STATE_BUSY) {
+ DBG(cdev, "mtp_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY));
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > BULK_BUFFER_SIZE)
+ xfer = 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_KERNEL);
+ if (ret < 0) {
+ DBG(cdev, "mtp_write: xfer error %d\n", ret);
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ req_put(dev, &dev->tx_idle, req);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_write returning %d\n", r);
+ return r;
+}
+
+static int mtp_send_file(struct mtp_dev *dev, struct file *filp,
+ loff_t offset, size_t count)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ int r = count, xfer, ret;
+
+ DBG(cdev, "mtp_send_file(%lld %d)\n", offset, count);
+
+ while (count > 0) {
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ (req = req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY);
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > BULK_BUFFER_SIZE)
+ xfer = BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ ret = vfs_read(filp, req->buf, xfer, &offset);
+ if (ret < 0) {
+ r = ret;
+ break;
+ }
+ xfer = ret;
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+ if (ret < 0) {
+ DBG(cdev, "mtp_write: xfer error %d\n", ret);
+ dev->state = STATE_ERROR;
+ r = -EIO;
+ break;
+ }
+
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ req_put(dev, &dev->tx_idle, req);
+
+ DBG(cdev, "mtp_write returning %d\n", r);
+ return r;
+}
+
+static int mtp_receive_file(struct mtp_dev *dev, struct file *filp,
+ loff_t offset, size_t count)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *read_req = NULL, *write_req = NULL;
+ int r = count;
+ int ret;
+ int cur_buf = 0;
+
+ DBG(cdev, "mtp_receive_file(%d)\n", count);
+
+ while (count > 0 || write_req) {
+ if (count > 0) {
+ /* queue a request */
+ read_req = dev->rx_req[cur_buf];
+ cur_buf = (cur_buf + 1) % RX_REQ_MAX;
+
+ read_req->length = (count > BULK_BUFFER_SIZE
+ ? BULK_BUFFER_SIZE : count);
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ count -= ret;
+ }
+
+ if (write_req) {
+ DBG(cdev, "rx %p %d\n", write_req, write_req->actual);
+ ret = vfs_write(filp, write_req->buf, write_req->actual,
+ &offset);
+ DBG(cdev, "vfs_write %d\n", ret);
+ if (ret != write_req->actual) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ write_req = NULL;
+ }
+
+ if (read_req) {
+ /* wait for our last read to complete */
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->rx_done || dev->state != STATE_BUSY);
+ if (ret < 0 || dev->state != STATE_BUSY) {
+ r = ret;
+ break;
+ }
+ count -= read_req->actual;
+ write_req = read_req;
+ read_req = NULL;
+ }
+ }
+
+ DBG(cdev, "mtp_read returning %d\n", r);
+ return r;
+}
+
+/* Kernel thread for handling file IO operations */
+static int mtp_thread(void *data)
+{
+ struct mtp_dev *dev = (struct mtp_dev *)data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ int flags;
+
+ DBG(cdev, "mtp_thread started\n");
+
+ while (1) {
+ /* wait for a command */
+ while (1) {
+ try_to_freeze();
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (dev->thread_command != 0)
+ break;
+ schedule();
+ }
+ __set_current_state(TASK_RUNNING);
+
+ if (dev->thread_command == ANDROID_THREAD_QUIT) {
+ DBG(cdev, "ANDROID_THREAD_QUIT\n");
+ dev->thread_result = 0;
+ goto done;
+ }
+
+ if (dev->thread_command == ANDROID_THREAD_SEND_FILE)
+ flags = O_RDONLY | O_LARGEFILE;
+ else
+ flags = O_WRONLY | O_LARGEFILE | O_CREAT;
+
+ if (dev->thread_command == ANDROID_THREAD_SEND_FILE) {
+ dev->thread_result = mtp_send_file(dev,
+ dev->thread_file,
+ dev->thread_file_offset,
+ dev->thread_file_length);
+ } else {
+ dev->thread_result = mtp_receive_file(dev,
+ dev->thread_file,
+ dev->thread_file_offset,
+ dev->thread_file_length);
+ }
+
+ if (dev->thread_file) {
+ fput(dev->thread_file);
+ dev->thread_file = NULL;
+ }
+ dev->thread_command = 0;
+ complete(&dev->thread_wait);
+ }
+
+done:
+ DBG(cdev, "android_thread done\n");
+ complete_and_exit(&dev->thread_wait, 0);
+}
+
+static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event)
+{
+ struct usb_request *req;
+ int ret;
+ int length = event->length;
+
+ DBG(dev->cdev, "mtp_send_event(%d)\n", event->length);
+
+ if (length < 0 || length > INTR_BUFFER_SIZE)
+ return -EINVAL;
+
+ mutex_lock(&dev->intr_mutex);
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->intr_wq, !dev->intr_busy || dev->state == STATE_OFFLINE);
+ if (ret < 0)
+ goto done;
+ if (dev->state == STATE_OFFLINE) {
+ ret = -ENODEV;
+ goto done;
+ }
+ req = dev->intr_req;
+ if (copy_from_user(req->buf, (void __user *)event->data, length)) {
+ ret = -EFAULT;
+ goto done;
+ }
+ req->length = length;
+ dev->intr_busy = 1;
+ ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL);
+ if (ret)
+ dev->intr_busy = 0;
+
+done:
+ mutex_unlock(&dev->intr_mutex);
+ return ret;
+}
+
+static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct file *filp = NULL;
+ int ret = -EINVAL;
+
+ switch (code) {
+ case MTP_SEND_FILE:
+ case MTP_RECEIVE_FILE:
+ {
+ struct mtp_file_range mfr;
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ return -ENODEV;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) {
+ ret = -EFAULT;
+ goto fail;
+ }
+ filp = fget(mfr.fd);
+ if (!filp) {
+ ret = -EBADF;
+ goto fail;
+ }
+
+ dev->thread_file = filp;
+ dev->thread_file_offset = mfr.offset;
+ dev->thread_file_length = mfr.length;
+
+ if (code == MTP_SEND_FILE)
+ dev->thread_command = ANDROID_THREAD_SEND_FILE;
+ else
+ dev->thread_command = ANDROID_THREAD_RECEIVE_FILE;
+
+ /* wake up the thread */
+ init_completion(&dev->thread_wait);
+ wake_up_process(dev->thread);
+
+ /* wait for the thread to complete the command */
+ wait_for_completion(&dev->thread_wait);
+ ret = dev->thread_result;
+ DBG(dev->cdev, "thread returned %d\n", ret);
+ break;
+ }
+ case MTP_SET_INTERFACE_MODE:
+ if (value == MTP_INTERFACE_MODE_MTP ||
+ value == MTP_INTERFACE_MODE_PTP) {
+ dev->interface_mode = value;
+ if (value == MTP_INTERFACE_MODE_PTP) {
+ dev->function.descriptors = fs_ptp_descs;
+ dev->function.hs_descriptors = hs_ptp_descs;
+ } else {
+ dev->function.descriptors = fs_mtp_descs;
+ dev->function.hs_descriptors = hs_mtp_descs;
+ }
+ ret = 0;
+ }
+ break;
+ case MTP_SEND_EVENT:
+ {
+ struct mtp_event event;
+ /* return here so we don't change dev->state below,
+ * which would interfere with bulk transfer state.
+ */
+ if (copy_from_user(&event, (void __user *)value, sizeof(event)))
+ return -EFAULT;
+ else
+ return mtp_send_event(dev, &event);
+ }
+ }
+
+fail:
+ if (filp)
+ fput(filp);
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ ret = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ DBG(dev->cdev, "ioctl returning %d\n", ret);
+ return ret;
+}
+
+static int mtp_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "mtp_open\n");
+ if (_lock(&_mtp_dev->open_excl))
+ return -EBUSY;
+
+ _mtp_dev->thread = kthread_create(mtp_thread, _mtp_dev, "f_mtp");
+ if (IS_ERR(_mtp_dev->thread))
+ return -ENOMEM;
+
+ /* clear any error condition */
+ if (_mtp_dev->state != STATE_OFFLINE)
+ _mtp_dev->state = STATE_READY;
+
+ fp->private_data = _mtp_dev;
+ return 0;
+}
+
+static int mtp_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "mtp_release\n");
+
+ /* tell the thread to quit */
+ if (_mtp_dev->thread) {
+ _mtp_dev->thread_command = ANDROID_THREAD_QUIT;
+ init_completion(&_mtp_dev->thread_wait);
+ wake_up_process(_mtp_dev->thread);
+ wait_for_completion(&_mtp_dev->thread_wait);
+ }
+
+ _unlock(&_mtp_dev->open_excl);
+ return 0;
+}
+
+/* file operations for /dev/mtp_usb */
+static const struct file_operations mtp_fops = {
+ .owner = THIS_MODULE,
+ .read = mtp_read,
+ .write = mtp_write,
+ .unlocked_ioctl = mtp_ioctl,
+ .open = mtp_open,
+ .release = mtp_release,
+};
+
+static struct miscdevice mtp_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = shortname,
+ .fops = &mtp_fops,
+};
+
+static int
+mtp_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct mtp_dev *dev = func_to_dev(f);
+ int id;
+ int ret;
+
+ dev->cdev = cdev;
+ DBG(cdev, "mtp_function_bind dev: %p\n", dev);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ mtp_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = create_bulk_endpoints(dev, &mtp_fullspeed_in_desc,
+ &mtp_fullspeed_out_desc, &mtp_intr_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ mtp_highspeed_in_desc.bEndpointAddress =
+ mtp_fullspeed_in_desc.bEndpointAddress;
+ mtp_highspeed_out_desc.bEndpointAddress =
+ mtp_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+mtp_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_request *req;
+ int i;
+
+ spin_lock_irq(&dev->lock);
+ while ((req = req_get(dev, &dev->tx_idle)))
+ mtp_request_free(req, dev->ep_in);
+ for (i = 0; i < RX_REQ_MAX; i++)
+ mtp_request_free(dev->rx_req[i], dev->ep_out);
+ mtp_request_free(dev->intr_req, dev->ep_intr);
+ dev->state = STATE_OFFLINE;
+ spin_unlock_irq(&dev->lock);
+ wake_up(&dev->intr_wq);
+
+ misc_deregister(&mtp_device);
+ kfree(_mtp_dev);
+ _mtp_dev = NULL;
+}
+
+static int mtp_function_setup(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+ unsigned long flags;
+
+ /* do nothing if we are disabled */
+ if (dev->function.disabled)
+ return value;
+
+ VDBG(cdev, "mtp_function_setup "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+
+ /* Handle MTP OS string */
+ if (dev->interface_mode == MTP_INTERFACE_MODE_MTP
+ && ctrl->bRequestType ==
+ (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
+ && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+ && (w_value >> 8) == USB_DT_STRING
+ && (w_value & 0xFF) == MTP_OS_STRING_ID) {
+ value = (w_length < sizeof(mtp_os_string)
+ ? w_length : sizeof(mtp_os_string));
+ memcpy(cdev->req->buf, mtp_os_string, value);
+ /* return here since composite.c will send for us */
+ return value;
+ }
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
+ /* Handle MTP OS descriptor */
+ DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (dev->interface_mode == MTP_INTERFACE_MODE_MTP
+ && ctrl->bRequest == 1
+ && (ctrl->bRequestType & USB_DIR_IN)
+ && (w_index == 4 || w_index == 5)) {
+ value = (w_length < sizeof(mtp_ext_config_desc) ?
+ w_length : sizeof(mtp_ext_config_desc));
+ memcpy(cdev->req->buf, &mtp_ext_config_desc, value);
+ }
+ }
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) {
+ DBG(cdev, "class request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0
+ && w_value == 0) {
+ DBG(cdev, "MTP_REQ_CANCEL\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->state == STATE_BUSY) {
+ dev->state = STATE_CANCELED;
+ wake_up(&dev->read_wq);
+ wake_up(&dev->write_wq);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ /* We need to queue a request to read the remaining
+ * bytes, but we don't actually need to look at
+ * the contents.
+ */
+ value = w_length;
+ } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS
+ && w_index == 0 && w_value == 0) {
+ struct mtp_device_status *status = cdev->req->buf;
+ status->wLength =
+ __constant_cpu_to_le16(sizeof(*status));
+
+ DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n");
+ spin_lock_irqsave(&dev->lock, flags);
+ /* device status is "busy" until we report
+ * the cancelation to userspace
+ */
+ if (dev->state == STATE_BUSY
+ || dev->state == STATE_CANCELED)
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY);
+ else
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_OK);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ value = sizeof(*status);
+ }
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ int rc;
+ cdev->req->zero = value < w_length;
+ cdev->req->length = value;
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ ERROR(cdev, "%s setup response queue error\n", __func__);
+ }
+
+ if (value == -EOPNOTSUPP)
+ VDBG(cdev,
+ "unknown class-specific control req "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ return value;
+}
+
+static int mtp_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt);
+ ret = usb_ep_enable(dev->ep_in,
+ ep_choose(cdev->gadget,
+ &mtp_highspeed_in_desc,
+ &mtp_fullspeed_in_desc));
+ if (ret)
+ return ret;
+ ret = usb_ep_enable(dev->ep_out,
+ ep_choose(cdev->gadget,
+ &mtp_highspeed_out_desc,
+ &mtp_fullspeed_out_desc));
+ if (ret) {
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ ret = usb_ep_enable(dev->ep_intr, &mtp_intr_desc);
+ if (ret) {
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ dev->state = STATE_READY;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void mtp_function_disable(struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "mtp_function_disable\n");
+ dev->state = STATE_OFFLINE;
+ usb_ep_disable(dev->ep_in);
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_intr);
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ wake_up(&dev->intr_wq);
+
+ VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int mtp_bind_config(struct usb_configuration *c)
+{
+ struct mtp_dev *dev;
+ int ret;
+
+ printk(KERN_INFO "mtp_bind_config\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* allocate a string ID for our interface */
+ if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) {
+ ret = usb_string_id(c->cdev);
+ if (ret < 0)
+ return ret;
+ mtp_string_defs[INTERFACE_STRING_INDEX].id = ret;
+ mtp_interface_desc.iInterface = ret;
+ }
+
+ spin_lock_init(&dev->lock);
+ init_completion(&dev->thread_wait);
+ init_waitqueue_head(&dev->read_wq);
+ init_waitqueue_head(&dev->write_wq);
+ init_waitqueue_head(&dev->intr_wq);
+ atomic_set(&dev->open_excl, 0);
+ INIT_LIST_HEAD(&dev->tx_idle);
+ mutex_init(&dev->intr_mutex);
+
+ dev->cdev = c->cdev;
+ dev->function.name = "mtp";
+ dev->function.strings = mtp_strings,
+ dev->function.descriptors = fs_mtp_descs;
+ dev->function.hs_descriptors = hs_mtp_descs;
+ dev->function.bind = mtp_function_bind;
+ dev->function.unbind = mtp_function_unbind;
+ dev->function.setup = mtp_function_setup;
+ dev->function.set_alt = mtp_function_set_alt;
+ dev->function.disable = mtp_function_disable;
+
+ /* MTP mode by default */
+ dev->interface_mode = MTP_INTERFACE_MODE_MTP;
+
+ /* _mtp_dev must be set before calling usb_gadget_register_driver */
+ _mtp_dev = dev;
+
+ ret = misc_register(&mtp_device);
+ if (ret)
+ goto err1;
+
+ ret = usb_add_function(c, &dev->function);
+ if (ret)
+ goto err2;
+
+ return 0;
+
+err2:
+ misc_deregister(&mtp_device);
+err1:
+ kfree(dev);
+ printk(KERN_ERR "mtp gadget driver failed to initialize\n");
+ return ret;
+}
+
+static struct android_usb_function mtp_function = {
+ .name = "mtp",
+ .bind_config = mtp_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_mtp init\n");
+ android_register_function(&mtp_function);
+ return 0;
+}
+module_init(init);
diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c
index 882484a40398..2d9d97ecce2b 100644
--- a/drivers/usb/gadget/f_rndis.c
+++ b/drivers/usb/gadget/f_rndis.c
@@ -26,8 +26,9 @@
#include <linux/slab.h>
#include <linux/kernel.h>
-#include <linux/device.h>
+#include <linux/platform_device.h>
#include <linux/etherdevice.h>
+#include <linux/usb/android_composite.h>
#include <asm/atomic.h>
@@ -129,9 +130,16 @@ static struct usb_interface_descriptor rndis_control_intf = {
/* .bInterfaceNumber = DYNAMIC */
/* status endpoint is optional; this could be patched later */
.bNumEndpoints = 1,
+#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS
+ /* "Wireless" RNDIS; auto-detected by Windows */
+ .bInterfaceClass = USB_CLASS_WIRELESS_CONTROLLER,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 3,
+#else
.bInterfaceClass = USB_CLASS_COMM,
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
.bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR,
+#endif
/* .iInterface = DYNAMIC */
};
@@ -304,6 +312,10 @@ static struct usb_gadget_strings *rndis_strings[] = {
NULL,
};
+#ifdef CONFIG_USB_ANDROID_RNDIS
+static struct usb_ether_platform_data *rndis_pdata;
+#endif
+
/*-------------------------------------------------------------------------*/
static struct sk_buff *rndis_add_header(struct gether *port,
@@ -487,10 +499,10 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
usb_ep_disable(rndis->notify);
} else {
VDBG(cdev, "init rndis ctrl %d\n", intf);
- rndis->notify_desc = ep_choose(cdev->gadget,
- rndis->hs.notify,
- rndis->fs.notify);
}
+ rndis->notify_desc = ep_choose(cdev->gadget,
+ rndis->hs.notify,
+ rndis->fs.notify);
usb_ep_enable(rndis->notify, rndis->notify_desc);
rndis->notify->driver_data = rndis;
@@ -504,11 +516,11 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (!rndis->port.in) {
DBG(cdev, "init rndis\n");
- rndis->port.in = ep_choose(cdev->gadget,
- rndis->hs.in, rndis->fs.in);
- rndis->port.out = ep_choose(cdev->gadget,
- rndis->hs.out, rndis->fs.out);
}
+ rndis->port.in = ep_choose(cdev->gadget,
+ rndis->hs.in, rndis->fs.in);
+ rndis->port.out = ep_choose(cdev->gadget,
+ rndis->hs.out, rndis->fs.out);
/* Avoid ZLPs; they can be troublesome. */
rndis->port.is_zlp_ok = false;
@@ -707,11 +719,12 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0);
rndis_set_host_mac(rndis->config, rndis->ethaddr);
-#if 0
-// FIXME
- if (rndis_set_param_vendor(rndis->config, vendorID,
- manufacturer))
- goto fail0;
+#ifdef CONFIG_USB_ANDROID_RNDIS
+ if (rndis_pdata) {
+ if (rndis_set_param_vendor(rndis->config, rndis_pdata->vendorID,
+ rndis_pdata->vendorDescr))
+ goto fail;
+ }
#endif
/* NOTE: all that is done without knowing or caring about
@@ -850,6 +863,11 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
rndis->port.func.setup = rndis_setup;
rndis->port.func.disable = rndis_disable;
+#ifdef CONFIG_USB_ANDROID_RNDIS
+ /* start disabled */
+ rndis->port.func.disabled = 1;
+#endif
+
status = usb_add_function(c, &rndis->port.func);
if (status) {
kfree(rndis);
@@ -858,3 +876,54 @@ fail:
}
return status;
}
+
+#ifdef CONFIG_USB_ANDROID_RNDIS
+#include "rndis.c"
+
+static int rndis_probe(struct platform_device *pdev)
+{
+ rndis_pdata = pdev->dev.platform_data;
+ return 0;
+}
+
+static struct platform_driver rndis_platform_driver = {
+ .driver = { .name = "rndis", },
+ .probe = rndis_probe,
+};
+
+int rndis_function_bind_config(struct usb_configuration *c)
+{
+ int ret;
+
+ if (!rndis_pdata) {
+ printk(KERN_ERR "rndis_pdata null in rndis_function_bind_config\n");
+ return -1;
+ }
+
+ printk(KERN_INFO
+ "rndis_function_bind_config MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
+ rndis_pdata->ethaddr[0], rndis_pdata->ethaddr[1],
+ rndis_pdata->ethaddr[2], rndis_pdata->ethaddr[3],
+ rndis_pdata->ethaddr[4], rndis_pdata->ethaddr[5]);
+
+ ret = gether_setup(c->cdev->gadget, rndis_pdata->ethaddr);
+ if (ret == 0)
+ ret = rndis_bind_config(c, rndis_pdata->ethaddr);
+ return ret;
+}
+
+static struct android_usb_function rndis_function = {
+ .name = "rndis",
+ .bind_config = rndis_function_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_rndis init\n");
+ platform_driver_register(&rndis_platform_driver);
+ android_register_function(&rndis_function);
+ return 0;
+}
+module_init(init);
+
+#endif /* CONFIG_USB_ANDROID_RNDIS */
diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c
index 484acfb1a7c5..2771166ac69a 100644
--- a/drivers/usb/gadget/storage_common.c
+++ b/drivers/usb/gadget/storage_common.c
@@ -805,10 +805,16 @@ static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr,
struct rw_semaphore *filesem = dev_get_drvdata(dev);
int rc = 0;
+
+#ifndef CONFIG_USB_ANDROID_MASS_STORAGE
+ /* disabled in android because we need to allow closing the backing file
+ * if the media was removed
+ */
if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) {
LDBG(curlun, "eject attempt prevented\n");
return -EBUSY; /* "Door is locked" */
}
+#endif
/* Remove a trailing newline */
if (count > 0 && buf[count-1] == '\n')
diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c
index 6bb876d65252..efdc8817f77d 100644
--- a/drivers/usb/gadget/u_ether.c
+++ b/drivers/usb/gadget/u_ether.c
@@ -936,7 +936,6 @@ void gether_disconnect(struct gether *link)
struct eth_dev *dev = link->ioport;
struct usb_request *req;
- WARN_ON(!dev);
if (!dev)
return;
diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h
index 3c8c0c9f9d72..99e4aa3b1374 100644
--- a/drivers/usb/gadget/u_ether.h
+++ b/drivers/usb/gadget/u_ether.h
@@ -105,7 +105,7 @@ int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
int eem_bind_config(struct usb_configuration *c);
-#ifdef USB_ETH_RNDIS
+#if defined(USB_ETH_RNDIS) || defined(CONFIG_USB_ANDROID_RNDIS)
int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);