usb: gadget: udc-core: Rescan pending list on driver unbind
authorKrzysztof Opasiak <k.opasiak@samsung.com>
Mon, 16 Jan 2017 07:40:57 +0000 (08:40 +0100)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Tue, 24 Jan 2017 09:04:07 +0000 (11:04 +0200)
Since:

commit 855ed04a3758 ("usb: gadget: udc-core: independent registration
of gadgets and gadget drivers")

if we load gadget module but there is no free udc available
then it will be stored on a pending gadgets list.

$ modprobe g_zero.ko
$ modprobe g_ether.ko
[] udc-core: couldn't find an available UDC - added [g_ether] to list
of pending drivers

We scan this list each time when new UDC appears in system.
But we can get a free UDC each time after gadget unbind.
This commit add scanning of that list directly after unbinding
gadget from udc.

Thanks to this, when we unload first gadget:

$ rmmod g_zero.ko

gadget which is pending is automatically
attached to that UDC (if name matches).

Fixes: 855ed04a3758  ("usb: gadget: udc-core: independent registration of gadgets and gadget drivers")
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Krzysztof Opasiak <k.opasiak@samsung.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/gadget/udc/core.c

index 0402177..d685d82 100644 (file)
@@ -1080,6 +1080,24 @@ static void usb_udc_nop_release(struct device *dev)
        dev_vdbg(dev, "%s\n", __func__);
 }
 
+/* should be called with udc_lock held */
+static int check_pending_gadget_drivers(struct usb_udc *udc)
+{
+       struct usb_gadget_driver *driver;
+       int ret = 0;
+
+       list_for_each_entry(driver, &gadget_driver_pending_list, pending)
+               if (!driver->udc_name || strcmp(driver->udc_name,
+                                               dev_name(&udc->dev)) == 0) {
+                       ret = udc_bind_to_driver(udc, driver);
+                       if (ret != -EPROBE_DEFER)
+                               list_del(&driver->pending);
+                       break;
+               }
+
+       return ret;
+}
+
 /**
  * usb_add_gadget_udc_release - adds a new gadget to the udc class driver list
  * @parent: the parent device to this udc. Usually the controller driver's
@@ -1093,7 +1111,6 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget,
                void (*release)(struct device *dev))
 {
        struct usb_udc          *udc;
-       struct usb_gadget_driver *driver;
        int                     ret = -ENOMEM;
 
        udc = kzalloc(sizeof(*udc), GFP_KERNEL);
@@ -1136,17 +1153,9 @@ int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget,
        udc->vbus = true;
 
        /* pick up one of pending gadget drivers */
-       list_for_each_entry(driver, &gadget_driver_pending_list, pending) {
-               if (!driver->udc_name || strcmp(driver->udc_name,
-                                               dev_name(&udc->dev)) == 0) {
-                       ret = udc_bind_to_driver(udc, driver);
-                       if (ret != -EPROBE_DEFER)
-                               list_del(&driver->pending);
-                       if (ret)
-                               goto err5;
-                       break;
-               }
-       }
+       ret = check_pending_gadget_drivers(udc);
+       if (ret)
+               goto err5;
 
        mutex_unlock(&udc_lock);
 
@@ -1356,14 +1365,22 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
                return -EINVAL;
 
        mutex_lock(&udc_lock);
-       list_for_each_entry(udc, &udc_list, list)
+       list_for_each_entry(udc, &udc_list, list) {
                if (udc->driver == driver) {
                        usb_gadget_remove_driver(udc);
                        usb_gadget_set_state(udc->gadget,
-                                       USB_STATE_NOTATTACHED);
+                                            USB_STATE_NOTATTACHED);
+
+                       /* Maybe there is someone waiting for this UDC? */
+                       check_pending_gadget_drivers(udc);
+                       /*
+                        * For now we ignore bind errors as probably it's
+                        * not a valid reason to fail other's gadget unbind
+                        */
                        ret = 0;
                        break;
                }
+       }
 
        if (ret) {
                list_del(&driver->pending);