diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 2544ce151b70..02dfe8ffde98 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -47,6 +47,13 @@ static struct class *udc_class; static LIST_HEAD(udc_list); static LIST_HEAD(gadget_driver_pending_list); static DEFINE_MUTEX(udc_lock); +/** + * protects udc->vbus, udc->started, gadget->connect, gadget->deactivate related + * functions. usb_gadget_connect_locked, usb_gadget_disconnect_locked, + * usb_udc_connect_control_locked, usb_gadget_udc_start_locked, usb_gadget_udc_stop_locked are + * called with this lock held. + */ +static DEFINE_MUTEX(connect_lock); static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver); @@ -655,17 +662,9 @@ out: } EXPORT_SYMBOL_GPL(usb_gadget_vbus_disconnect); -/** - * usb_gadget_connect - software-controlled connect to USB host - * @gadget:the peripheral being connected - * - * Enables the D+ (or potentially D-) pullup. The host will start - * enumerating this gadget when the pullup is active and a VBUS session - * is active (the link is powered). - * - * Returns zero on success, else negative errno. - */ -int usb_gadget_connect(struct usb_gadget *gadget) +/* Internal version of usb_gadget_connect needs to be called with connect_lock held. */ +static int usb_gadget_connect_locked(struct usb_gadget *gadget) + __must_hold(&connect_lock) { int ret = 0; @@ -674,10 +673,12 @@ int usb_gadget_connect(struct usb_gadget *gadget) goto out; } - if (gadget->deactivated) { + if (gadget->deactivated || !gadget->udc->started) { /* * If gadget is deactivated we only save new state. * Gadget will be connected automatically after activation. + * + * udc first needs to be started before gadget can be pulled up. */ gadget->connected = true; goto out; @@ -692,8 +693,67 @@ out: return ret; } + +/** + * usb_gadget_connect - software-controlled connect to USB host + * @gadget:the peripheral being connected + * + * Enables the D+ (or potentially D-) pullup. The host will start + * enumerating this gadget when the pullup is active and a VBUS session + * is active (the link is powered). + * + * Returns zero on success, else negative errno. + */ +int usb_gadget_connect(struct usb_gadget *gadget) +{ + int ret; + + mutex_lock(&connect_lock); + ret = usb_gadget_connect_locked(gadget); + mutex_unlock(&connect_lock); + + return ret; +} EXPORT_SYMBOL_GPL(usb_gadget_connect); +/* Internal version of usb_gadget_disconnect needs to be called with connect_lock held. */ +static int usb_gadget_disconnect_locked(struct usb_gadget *gadget) + __must_hold(&connect_lock) +{ + int ret = 0; + + if (!gadget->ops->pullup) { + ret = -EOPNOTSUPP; + goto out; + } + + if (!gadget->connected) + goto out; + + if (gadget->deactivated || !gadget->udc->started) { + /* + * If gadget is deactivated we only save new state. + * Gadget will stay disconnected after activation. + * + * udc should have been started before gadget being pulled down. + */ + gadget->connected = false; + goto out; + } + + ret = gadget->ops->pullup(gadget, 0); + if (!ret) + gadget->connected = 0; + + if (gadget->udc->driver) + gadget->udc->driver->disconnect(gadget); + +out: + trace_usb_gadget_disconnect(gadget, ret); + + return ret; +} + /** * usb_gadget_disconnect - software-controlled disconnect from USB host * @gadget:the peripheral being disconnected @@ -709,34 +769,11 @@ EXPORT_SYMBOL_GPL(usb_gadget_connect); */ int usb_gadget_disconnect(struct usb_gadget *gadget) { - int ret = 0; + int ret; - if (!gadget->ops->pullup) { - ret = -EOPNOTSUPP; - goto out; - } - - if (!gadget->connected) - goto out; - - if (gadget->deactivated) { - /* - * If gadget is deactivated we only save new state. - * Gadget will stay disconnected after activation. - */ - gadget->connected = false; - goto out; - } - - ret = gadget->ops->pullup(gadget, 0); - if (!ret) - gadget->connected = 0; - - if (gadget->udc->driver) - gadget->udc->driver->disconnect(gadget); - -out: - trace_usb_gadget_disconnect(gadget, ret); + mutex_lock(&connect_lock); + ret = usb_gadget_disconnect_locked(gadget); + mutex_unlock(&connect_lock); return ret; } @@ -760,10 +797,11 @@ int usb_gadget_deactivate(struct usb_gadget *gadget) if (gadget->deactivated) goto out; + mutex_lock(&connect_lock); if (gadget->connected) { - ret = usb_gadget_disconnect(gadget); + ret = usb_gadget_disconnect_locked(gadget); if (ret) - goto out; + goto unlock; /* * If gadget was being connected before deactivation, we want @@ -773,6 +811,8 @@ int usb_gadget_deactivate(struct usb_gadget *gadget) } gadget->deactivated = true; +unlock: + mutex_unlock(&connect_lock); out: trace_usb_gadget_deactivate(gadget, ret); @@ -796,6 +836,7 @@ int usb_gadget_activate(struct usb_gadget *gadget) if (!gadget->deactivated) goto out; + mutex_lock(&connect_lock); gadget->deactivated = false; /* @@ -803,7 +844,8 @@ int usb_gadget_activate(struct usb_gadget *gadget) * while it was being deactivated, we call usb_gadget_connect(). */ if (gadget->connected) - ret = usb_gadget_connect(gadget); + ret = usb_gadget_connect_locked(gadget); + mutex_unlock(&connect_lock); out: trace_usb_gadget_activate(gadget, ret); @@ -1044,12 +1086,13 @@ EXPORT_SYMBOL_GPL(usb_gadget_set_state); /* ------------------------------------------------------------------------- */ -static void usb_udc_connect_control(struct usb_udc *udc) +/* Acquire connect_lock before calling this function. */ +static void usb_udc_connect_control_locked(struct usb_udc *udc) __must_hold(&connect_lock) { - if (udc->vbus) - usb_gadget_connect(udc->gadget); + if (udc->vbus && udc->started) + usb_gadget_connect_locked(udc->gadget); else - usb_gadget_disconnect(udc->gadget); + usb_gadget_disconnect_locked(udc->gadget); } /** @@ -1065,10 +1108,12 @@ void usb_udc_vbus_handler(struct usb_gadget *gadget, bool status) { struct usb_udc *udc = gadget->udc; + mutex_lock(&connect_lock); if (udc) { udc->vbus = status; - usb_udc_connect_control(udc); + usb_udc_connect_control_locked(udc); } + mutex_unlock(&connect_lock); } EXPORT_SYMBOL_GPL(usb_udc_vbus_handler); @@ -1090,7 +1135,7 @@ void usb_gadget_udc_reset(struct usb_gadget *gadget, EXPORT_SYMBOL_GPL(usb_gadget_udc_reset); /** - * usb_gadget_udc_start - tells usb device controller to start up + * usb_gadget_udc_start_locked - tells usb device controller to start up * @udc: The UDC to be started * * This call is issued by the UDC Class driver when it's about @@ -1101,8 +1146,11 @@ EXPORT_SYMBOL_GPL(usb_gadget_udc_reset); * necessary to have it powered on. * * Returns zero on success, else negative errno. + * + * Caller should acquire connect_lock before invoking this function. */ -static inline int usb_gadget_udc_start(struct usb_udc *udc) +static inline int usb_gadget_udc_start_locked(struct usb_udc *udc) + __must_hold(&connect_lock) { int ret; @@ -1119,7 +1167,7 @@ static inline int usb_gadget_udc_start(struct usb_udc *udc) } /** - * usb_gadget_udc_stop - tells usb device controller we don't need it anymore + * usb_gadget_udc_stop_locked - tells usb device controller we don't need it anymore * @udc: The UDC to be stopped * * This call is issued by the UDC Class driver after calling @@ -1128,8 +1176,11 @@ static inline int usb_gadget_udc_start(struct usb_udc *udc) * The details are implementation specific, but it can go as * far as powering off UDC completely and disable its data * line pullups. + * + * Caller should acquire connect lock before invoking this function. */ -static inline void usb_gadget_udc_stop(struct usb_udc *udc) +static inline void usb_gadget_udc_stop_locked(struct usb_udc *udc) + __must_hold(&connect_lock) { if (!udc->started) { dev_err(&udc->dev, "UDC had already stopped\n"); @@ -1427,12 +1478,14 @@ static void usb_gadget_remove_driver(struct usb_udc *udc) kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); - usb_gadget_disconnect(udc->gadget); + mutex_lock(&connect_lock); + usb_gadget_disconnect_locked(udc->gadget); usb_gadget_disable_async_callbacks(udc); if (udc->gadget->irq) synchronize_irq(udc->gadget->irq); udc->driver->unbind(udc->gadget); - usb_gadget_udc_stop(udc); + usb_gadget_udc_stop_locked(udc); + mutex_unlock(&connect_lock); udc->driver = NULL; udc->gadget->dev.driver = NULL; @@ -1503,13 +1556,16 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri ret = driver->bind(udc->gadget, driver); if (ret) goto err1; - ret = usb_gadget_udc_start(udc); + mutex_lock(&connect_lock); + ret = usb_gadget_udc_start_locked(udc); if (ret) { + mutex_unlock(&connect_lock); driver->unbind(udc->gadget); goto err1; } usb_gadget_enable_async_callbacks(udc); - usb_udc_connect_control(udc); + usb_udc_connect_control_locked(udc); + mutex_unlock(&connect_lock); kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); return 0; @@ -1632,11 +1688,15 @@ static ssize_t soft_connect_store(struct device *dev, } if (sysfs_streq(buf, "connect")) { - usb_gadget_udc_start(udc); - usb_gadget_connect(udc->gadget); + mutex_lock(&connect_lock); + usb_gadget_udc_start_locked(udc); + usb_gadget_connect_locked(udc->gadget); + mutex_unlock(&connect_lock); } else if (sysfs_streq(buf, "disconnect")) { - usb_gadget_disconnect(udc->gadget); - usb_gadget_udc_stop(udc); + mutex_lock(&connect_lock); + usb_gadget_disconnect_locked(udc->gadget); + usb_gadget_udc_stop_locked(udc); + mutex_unlock(&connect_lock); } else { dev_err(dev, "unsupported command '%s'\n", buf); ret = -EINVAL;