summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/composite.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/composite.c')
-rw-r--r--drivers/usb/gadget/composite.c220
1 files changed, 214 insertions, 6 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index c2251c40a20..920de9baaee 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -25,7 +25,8 @@
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/utsname.h>
-
+#include <linux/delay.h>
+#include <linux/kdev_t.h>
#include <linux/usb/composite.h>
@@ -75,6 +76,56 @@ static char composite_manufacturer[50];
/*-------------------------------------------------------------------------*/
+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) {
+ 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
@@ -92,15 +143,30 @@ static char composite_manufacturer[50];
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);
@@ -126,7 +192,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;
}
@@ -236,17 +302,20 @@ 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;
+ struct usb_interface_assoc_descriptor *iad = NULL;
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;
@@ -265,23 +334,54 @@ 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;
+ if (iad) {
+ iad->bFirstInterface =
+ intf->bInterfaceNumber;
+ iad = NULL;
+ }
+ } else if (intf->bDescriptorType ==
+ USB_DT_INTERFACE_ASSOCIATION) {
+ /* This will be first if it exists. Save
+ * a pointer to it so we can properly set
+ * bFirstInterface when we process the first
+ * interface.
+ */
+ iad = (struct usb_interface_assoc_descriptor *)
+ dest;
+ }
+ dest += intf->bLength;
+ }
+
len -= status;
next += status;
}
len = next - buf;
c->wTotalLength = cpu_to_le16(len);
+ c->bNumInterfaces = interfaceCount;
return len;
}
@@ -428,6 +528,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
@@ -467,6 +569,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;
}
@@ -806,6 +910,14 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
u16 w_length = le16_to_cpu(ctrl->wLength);
struct usb_function *f = NULL;
u8 endp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (!cdev->connected) {
+ cdev->connected = 1;
+ schedule_work(&cdev->switch_work);
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
/* partial re-init of the response message; the function or the
* gadget might need to intercept e.g. a control-OUT completion
@@ -849,6 +961,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;
@@ -954,6 +1081,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;
}
@@ -985,8 +1131,12 @@ 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);
+
+ cdev->connected = 0;
+ schedule_work(&cdev->switch_work);
spin_unlock_irqrestore(&cdev->lock, flags);
}
@@ -1048,6 +1198,8 @@ composite_unbind(struct usb_gadget *gadget)
kfree(cdev->req->buf);
usb_ep_free_request(gadget->ep0, cdev->req);
}
+ switch_dev_unregister(&cdev->sw_connected);
+ switch_dev_unregister(&cdev->sw_config);
device_remove_file(&gadget->dev, &dev_attr_suspended);
kfree(cdev);
set_gadget_data(gadget, NULL);
@@ -1067,6 +1219,30 @@ static u8 override_id(struct usb_composite_dev *cdev, u8 *desc)
return *desc;
}
+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;
+ int connected;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->connected != cdev->sw_connected.state) {
+ connected = cdev->connected;
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ switch_set_state(&cdev->sw_connected, connected);
+ } else {
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ }
+
+ if (config)
+ switch_set_state(&cdev->sw_config, config->bConfigurationValue);
+ else
+ switch_set_state(&cdev->sw_config, 0);
+}
+
static int composite_bind(struct usb_gadget *gadget)
{
struct usb_composite_dev *cdev;
@@ -1116,6 +1292,16 @@ static int composite_bind(struct usb_gadget *gadget)
if (status < 0)
goto fail;
+ cdev->sw_connected.name = "usb_connected";
+ status = switch_dev_register(&cdev->sw_connected);
+ if (status < 0)
+ goto fail;
+ cdev->sw_config.name = "usb_configuration";
+ status = switch_dev_register(&cdev->sw_config);
+ if (status < 0)
+ goto fail;
+ INIT_WORK(&cdev->switch_work, composite_switch_work);
+
cdev->desc = *composite->dev;
cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
@@ -1221,6 +1407,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 = {
@@ -1273,6 +1476,11 @@ int usb_composite_probe(struct usb_composite_driver *driver,
composite = driver;
composite_gadget_bind = bind;
+ 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_probe_driver(&composite_driver, composite_bind);
}