diff options
Diffstat (limited to 'drivers/usb/gadget/composite.c')
-rw-r--r-- | drivers/usb/gadget/composite.c | 258 |
1 files changed, 247 insertions, 11 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 391d169f8d0..5b20f6ba268 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -24,9 +24,10 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/device.h> - +#include <linux/kdev_t.h> +#include <linux/delay.h> #include <linux/usb/composite.h> - +#include <linux/switch.h> /* * The code in this file is utility code, used to build a gadget driver @@ -71,6 +72,56 @@ 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) { + 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 +139,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 +188,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 +298,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 +329,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 +509,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 +550,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; } @@ -717,6 +806,14 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) 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 * when we delegate to it. @@ -759,6 +856,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; @@ -784,11 +896,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 @@ -838,6 +950,8 @@ unknown: */ switch (ctrl->bRequestType & USB_RECIP_MASK) { case USB_RECIP_INTERFACE: + if (!cdev->config || w_index >= MAX_CONFIG_INTERFACES) + break; f = cdev->config->interface[intf]; break; @@ -862,6 +976,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; } @@ -887,12 +1020,19 @@ static void composite_disconnect(struct usb_gadget *gadget) struct usb_composite_dev *cdev = get_gadget_data(gadget); unsigned long flags; +#ifdef CONFIG_USB_OTG_20 + gadget->host_request = 0; +#endif + /* REVISIT: should we have config and device level * disconnect callbacks? */ spin_lock_irqsave(&cdev->lock, flags); if (cdev->config) reset_config(cdev); + + cdev->connected = 0; + schedule_work(&cdev->switch_work); spin_unlock_irqrestore(&cdev->lock, flags); } @@ -910,6 +1050,25 @@ static ssize_t composite_show_suspended(struct device *dev, static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL); +#ifdef CONFIG_USB_OTG_20 +static ssize_t composite_set_host_request(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + int value; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + gadget->host_request = !!value; + return count; + +} + +static DEVICE_ATTR(host_request, S_IWUSR, NULL, composite_set_host_request); +#endif + static void composite_unbind(struct usb_gadget *gadget) { @@ -954,6 +1113,13 @@ 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); +#ifdef CONFIG_USB_OTG_20 + device_remove_file(&gadget->dev, &dev_attr_host_request); +#endif + kfree(cdev); set_gadget_data(gadget, NULL); device_remove_file(&gadget->dev, &dev_attr_suspended); @@ -982,6 +1148,30 @@ 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; + 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; @@ -1009,7 +1199,13 @@ static int composite_bind(struct usb_gadget *gadget) cdev->bufsiz = USB_BUFSIZ; cdev->driver = composite; - usb_gadget_set_selfpowered(gadget); + /* + * As per USB compliance update, a device that is actively drawing + * more than 100mA from USB must report itself as bus-powered in + * the GetStatus(DEVICE) call. + */ + if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_set_selfpowered(gadget); /* interface and string IDs start at zero via kzalloc. * we force endpoints to start unassigned; few controller @@ -1033,6 +1229,24 @@ 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); + +#ifdef CONFIG_USB_OTG_20 + status = device_create_file(&gadget->dev, &dev_attr_host_request); + if (status) + DBG(cdev, "unable to create host_request sysfs file\n"); +#endif + + cdev->desc = *composite->dev; cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket; @@ -1107,6 +1321,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 = { @@ -1152,6 +1383,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); } |