Commit 7dc0c55e authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: UDC core: Add udc_async_callbacks gadget op

The Gadget API has a theoretical race when a gadget driver is unbound.
Although the pull-up is turned off before the driver's ->unbind
callback runs, if the USB cable were to be unplugged at just the wrong
moment there would be nothing to prevent the UDC driver from invoking
the ->disconnect callback after the unbind has finished.  In theory,
other asynchronous callbacks could also happen during the time before
the UDC driver's udc_stop routine is called, and the gadget driver
would not be prepared to handle any of them.

We need a way to tell UDC drivers to stop issuing asynchronous (that is,
->suspend, ->resume, ->disconnect, ->reset, or ->setup) callbacks at
some point after the pull-up has been turned off and before the
->unbind callback runs.  This patch adds a new ->udc_async_callbacks
callback to the usb_gadget_ops structure for precisely this purpose,
and it adds the corresponding support to the UDC core.

Later patches in this series add support for udc_async_callbacks to
several UDC drivers.
Acked-by: default avatarFelipe Balbi <balbi@kernel.org>
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20210520202144.GC1216852@rowland.harvard.eduSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent ab1150e9
...@@ -1147,6 +1147,53 @@ static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, ...@@ -1147,6 +1147,53 @@ static inline void usb_gadget_udc_set_speed(struct usb_udc *udc,
gadget->ops->udc_set_speed(gadget, s); gadget->ops->udc_set_speed(gadget, s);
} }
/**
* usb_gadget_enable_async_callbacks - tell usb device controller to enable asynchronous callbacks
* @udc: The UDC which should enable async callbacks
*
* This routine is used when binding gadget drivers. It undoes the effect
* of usb_gadget_disable_async_callbacks(); the UDC driver should enable IRQs
* (if necessary) and resume issuing callbacks.
*
* This routine will always be called in process context.
*/
static inline void usb_gadget_enable_async_callbacks(struct usb_udc *udc)
{
struct usb_gadget *gadget = udc->gadget;
if (gadget->ops->udc_async_callbacks)
gadget->ops->udc_async_callbacks(gadget, true);
}
/**
* usb_gadget_disable_async_callbacks - tell usb device controller to disable asynchronous callbacks
* @udc: The UDC which should disable async callbacks
*
* This routine is used when unbinding gadget drivers. It prevents a race:
* The UDC driver doesn't know when the gadget driver's ->unbind callback
* runs, so unless it is told to disable asynchronous callbacks, it might
* issue a callback (such as ->disconnect) after the unbind has completed.
*
* After this function runs, the UDC driver must suppress all ->suspend,
* ->resume, ->disconnect, ->reset, and ->setup callbacks to the gadget driver
* until async callbacks are again enabled. A simple-minded but effective
* way to accomplish this is to tell the UDC hardware not to generate any
* more IRQs.
*
* Request completion callbacks must still be issued. However, it's okay
* to defer them until the request is cancelled, since the pull-up will be
* turned off during the time period when async callbacks are disabled.
*
* This routine will always be called in process context.
*/
static inline void usb_gadget_disable_async_callbacks(struct usb_udc *udc)
{
struct usb_gadget *gadget = udc->gadget;
if (gadget->ops->udc_async_callbacks)
gadget->ops->udc_async_callbacks(gadget, false);
}
/** /**
* usb_udc_release - release the usb_udc struct * usb_udc_release - release the usb_udc struct
* @dev: the dev member within usb_udc * @dev: the dev member within usb_udc
...@@ -1361,6 +1408,7 @@ static void usb_gadget_remove_driver(struct usb_udc *udc) ...@@ -1361,6 +1408,7 @@ static void usb_gadget_remove_driver(struct usb_udc *udc)
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
usb_gadget_disconnect(udc->gadget); usb_gadget_disconnect(udc->gadget);
usb_gadget_disable_async_callbacks(udc);
if (udc->gadget->irq) if (udc->gadget->irq)
synchronize_irq(udc->gadget->irq); synchronize_irq(udc->gadget->irq);
udc->driver->unbind(udc->gadget); udc->driver->unbind(udc->gadget);
...@@ -1442,6 +1490,7 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri ...@@ -1442,6 +1490,7 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri
driver->unbind(udc->gadget); driver->unbind(udc->gadget);
goto err1; goto err1;
} }
usb_gadget_enable_async_callbacks(udc);
usb_udc_connect_control(udc); usb_udc_connect_control(udc);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
......
...@@ -325,6 +325,7 @@ struct usb_gadget_ops { ...@@ -325,6 +325,7 @@ struct usb_gadget_ops {
void (*udc_set_speed)(struct usb_gadget *, enum usb_device_speed); void (*udc_set_speed)(struct usb_gadget *, enum usb_device_speed);
void (*udc_set_ssp_rate)(struct usb_gadget *gadget, void (*udc_set_ssp_rate)(struct usb_gadget *gadget,
enum usb_ssp_rate rate); enum usb_ssp_rate rate);
void (*udc_async_callbacks)(struct usb_gadget *gadget, bool enable);
struct usb_ep *(*match_ep)(struct usb_gadget *, struct usb_ep *(*match_ep)(struct usb_gadget *,
struct usb_endpoint_descriptor *, struct usb_endpoint_descriptor *,
struct usb_ss_ep_comp_descriptor *); struct usb_ss_ep_comp_descriptor *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment