| From c7c12a7f32646a426e39f1b9ce52b0d66bcfa116 Mon Sep 17 00:00:00 2001 |
| From: Krzysztof Opasiak <k.opasiak@samsung.com> |
| Date: Mon, 16 Jan 2017 08:40:57 +0100 |
| Subject: [PATCH] usb: gadget: udc-core: Rescan pending list on driver unbind |
| |
| commit 8236800da115a3e24b9165c573067343f51cf5ea upstream. |
| |
| 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> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c |
| index b4c47a58ee18..281060173fd0 100644 |
| --- a/drivers/usb/gadget/udc/core.c |
| +++ b/drivers/usb/gadget/udc/core.c |
| @@ -1082,6 +1082,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 |
| @@ -1095,7 +1113,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); |
| @@ -1138,17 +1155,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); |
| |
| @@ -1358,14 +1367,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); |
| -- |
| 2.12.0 |
| |