USB: composite: Add usb_composite_force_reset utility to force enumeration
[firefly-linux-kernel-4.4.55.git] / drivers / usb / gadget / composite.c
index 5cbb1a41c223d7cd0f139cd1dba4c6c2248f69f8..c2684b5adc02c074a4554929ea973c29b52b5c88 100644 (file)
@@ -75,6 +75,59 @@ 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) {
+               /* 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
@@ -92,15 +145,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 +194,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 +304,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;
@@ -265,23 +335,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;
 }
 
@@ -428,6 +515,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
@@ -476,6 +565,9 @@ 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);
+
        if (result >= 0 && cdev->delayed_status)
                result = USB_GADGET_DELAYED_STATUS;
        return result;
@@ -860,6 +952,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;
@@ -885,11 +992,12 @@ 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
@@ -973,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;
        }
 
@@ -1008,8 +1135,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);
 }
 
@@ -1071,6 +1204,7 @@ composite_unbind(struct usb_gadget *gadget)
                kfree(cdev->req->buf);
                usb_ep_free_request(gadget->ep0, cdev->req);
        }
+       switch_dev_unregister(&cdev->sdev);
        device_remove_file(&gadget->dev, &dev_attr_suspended);
        kfree(cdev);
        set_gadget_data(gadget, NULL);
@@ -1090,6 +1224,19 @@ 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;
+
+       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;
@@ -1139,6 +1286,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;
 
@@ -1244,6 +1397,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 = {
@@ -1296,6 +1466,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);
 }