diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index dab0620e94e7..7987a626889f 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -208,7 +208,7 @@ 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->hidden) + if (product_has_function(p, f) == !!f->disabled) return 0; } return 1; @@ -323,8 +323,8 @@ void android_enable_function(struct usb_function *f, int enable) int disable = !enable; int product_id; - if (!!f->hidden != disable) { - f->hidden = disable; + if (!!f->disabled != disable) { + usb_function_set_enabled(f, !disable); #ifdef CONFIG_USB_ANDROID_RNDIS if (!strcmp(f->name, "rndis")) { @@ -347,7 +347,7 @@ void android_enable_function(struct usb_function *f, int enable) */ list_for_each_entry(func, &android_config_driver.functions, list) { if (!strcmp(func->name, "usb_mass_storage")) { - func->hidden = enable; + usb_function_set_enabled(func, !enable); break; } } @@ -358,14 +358,7 @@ void android_enable_function(struct usb_function *f, int enable) device_desc.idProduct = __constant_cpu_to_le16(product_id); if (dev->cdev) dev->cdev->desc.idProduct = device_desc.idProduct; - - /* force reenumeration */ - if (dev->cdev && dev->cdev->gadget && - dev->cdev->gadget->speed != USB_SPEED_UNKNOWN) { - usb_gadget_disconnect(dev->cdev->gadget); - msleep(10); - usb_gadget_connect(dev->cdev->gadget); - } + usb_composite_force_reset(dev->cdev); } } diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 11e17a60a43f..c6ab4d1b206c 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -75,7 +75,7 @@ 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->hidden); + return sprintf(buf, "%d\n", !f->disabled); } static ssize_t enable_store( @@ -90,13 +90,39 @@ static ssize_t enable_store( if (driver->enable_function) driver->enable_function(f, value); else - f->hidden = !value; + 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 @@ -310,7 +336,7 @@ static int config_buf(struct usb_configuration *config, descriptors = f->hs_descriptors; else descriptors = f->descriptors; - if (f->hidden || !descriptors || descriptors[0] == NULL) + if (f->disabled || !descriptors || descriptors[0] == NULL) continue; status = usb_descriptor_fillbuf(next, len, (const struct usb_descriptor_header **) descriptors); @@ -481,7 +507,7 @@ static int set_config(struct usb_composite_dev *cdev, if (!f) break; - if (f->hidden) + if (f->disabled) continue; result = f->set_alt(f, tmp, 0); @@ -498,6 +524,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; } @@ -939,6 +967,11 @@ static void composite_disconnect(struct usb_gadget *gadget) spin_lock_irqsave(&cdev->lock, flags); if (cdev->config) reset_config(cdev); + + if (cdev->mute_switch) + cdev->mute_switch = 0; + else + schedule_work(&cdev->switch_work); spin_unlock_irqrestore(&cdev->lock, flags); } @@ -988,6 +1021,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); composite = NULL; @@ -1015,6 +1050,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 __init composite_bind(struct usb_gadget *gadget) { struct usb_composite_dev *cdev; @@ -1058,6 +1106,12 @@ static int __init 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; @@ -1132,6 +1186,23 @@ composite_resume(struct usb_gadget *gadget) } } +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 = { @@ -1180,6 +1251,7 @@ int __init usb_composite_register(struct usb_composite_driver *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_adb.c b/drivers/usb/gadget/f_adb.c index df3604d34b6c..ec23b72dd8bd 100644 --- a/drivers/usb/gadget/f_adb.c +++ b/drivers/usb/gadget/f_adb.c @@ -612,7 +612,7 @@ static int adb_bind_config(struct usb_configuration *c) dev->function.disable = adb_function_disable; /* start disabled */ - dev->function.hidden = 0; + dev->function.disabled = 1; /* _adb_dev must be set before calling usb_gadget_register_driver */ _adb_dev = dev; diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index 7c4fb4d022ab..8486ade79e42 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -840,7 +840,7 @@ int __init rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) #ifdef CONFIG_USB_ANDROID_RNDIS /* start disabled */ - rndis->port.func.hidden = 1; + rndis->port.func.disabled = 1; #endif status = usb_add_function(c, &rndis->port.func); diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index 66884030c58a..080365ba3b21 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -36,8 +36,10 @@ #include #include +#include +struct usb_composite_dev; struct usb_configuration; /** @@ -100,7 +102,9 @@ struct usb_function { struct usb_descriptor_header **hs_descriptors; struct usb_configuration *config; - int hidden; + + /* disabled is zero if the function is enabled */ + int disabled; /* REVISIT: bind() functions can be marked __init, which * makes trouble for section mismatch analysis. See if @@ -138,6 +142,9 @@ int usb_function_activate(struct usb_function *); int usb_interface_id(struct usb_configuration *, struct usb_function *); +void usb_function_set_enabled(struct usb_function *, int); +void usb_composite_force_reset(struct usb_composite_dev *); + /** * ep_choose - select descriptor endpoint at current device speed * @g: gadget, connected and running at some speed @@ -344,6 +351,11 @@ struct usb_composite_dev { /* protects at least deactivation count */ spinlock_t lock; + + struct switch_dev sdev; + /* used by usb_composite_force_reset to avoid signalling switch changes */ + bool mute_switch; + struct work_struct switch_work; }; extern int usb_string_id(struct usb_composite_dev *c);