Some drivers were calling usb_reset_endpoint() in interrupt or tasklet context. The underlying host controller driver function reset_endpoint() may block, so this is less than ideal. Create a new USB core function to start an asynchronous endpoint reset, usb_start_reset_endpoint(). The driver's callback function will be asynchronously called when the reset is finished. Add a new host controller driver function start_endpoint_reset(), that will be called by usb_hcd_start_reset_endpoint(). WIP: Change WHCI driver to use start_endpoint_reset() and do asynchronous completions. Change the hisax and UB drivers to use usb_start_reset_endpoint() Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> --- Can someone test this patch with a mass storage device that stalls? I currently don't have any. drivers/usb/core/hcd.c | 32 +++++++++++++++++++- drivers/usb/core/hcd.h | 7 ++++- drivers/usb/core/message.c | 69 ++++++++++++++++++++++++++++++++++++++----- include/linux/usb.h | 12 +++++++ 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 42b93da..ad89642 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1540,7 +1540,8 @@ void usb_hcd_disable_endpoint(struct usb_device *udev, } /** - * usb_hcd_reset_endpoint - reset host endpoint state + * usb_hcd_reset_endpoint - reset host endpoint state. + * May not be called in interrupt context. * @udev: USB device. * @ep: the endpoint to reset. * @@ -1565,6 +1566,35 @@ void usb_hcd_reset_endpoint(struct usb_device *udev, } } +/** + * usb_hcd_start_reset_endpoint - begin an asynchronous reset of host endpoint state. + * May be called in interrupt or tasklet context. + * @udev: USB device. + * @ep: the endpoint to reset. + * + * Resets any host endpoint state such as the toggle bit, sequence + * number and current window. + */ +void usb_hcd_start_reset_endpoint(struct endpoint_reset_callback *callback_info) +{ + struct usb_hcd *hcd = bus_to_hcd(callback_info->dev->bus); + struct usb_host_endpoint *ep = callback_info->ep; + struct usb_device *udev = callback_info->dev; + + if (hcd->driver->start_endpoint_reset) + hcd->driver->start_endpoint_reset(hcd, callback_info); + else { + int epnum = usb_endpoint_num(&ep->desc); + int is_out = usb_endpoint_dir_out(&ep->desc); + int is_control = usb_endpoint_xfer_control(&ep->desc); + + usb_settoggle(udev, epnum, is_out, 0); + if (is_control) + usb_settoggle(udev, epnum, !is_out, 0); + callback_info->callback(callback_info); + } +} + /* Protect against drivers that try to unlink URBs after the device * is gone, by waiting until all unlinks for @udev are finished. * Since we don't currently track URBs by device, simply wait until diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index e7d4479..d01d597 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -207,9 +207,13 @@ struct hc_driver { struct usb_host_endpoint *ep); /* (optional) reset any endpoint state such as sequence number - and current window */ + and current window in non-interrupt context */ void (*endpoint_reset)(struct usb_hcd *hcd, struct usb_host_endpoint *ep); + /* (optional) reset any endpoint state such as sequence number + and current window in interrupt or tasklet context */ + void (*start_endpoint_reset)(struct usb_hcd *hcd, + struct endpoint_reset_callback *callback); /* root hub support */ int (*hub_status_data) (struct usb_hcd *hcd, char *buf); @@ -241,6 +245,7 @@ extern void usb_hcd_disable_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep); extern void usb_hcd_reset_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep); +extern void usb_hcd_start_reset_endpoint(struct endpoint_reset_callback *callback_info); extern void usb_hcd_synchronize_unlinks(struct usb_device *udev); extern int usb_hcd_get_frame_number(struct usb_device *udev); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index b626283..1c29b92 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1074,8 +1074,17 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, } } +static void usb_finish_reset_endpoint(struct endpoint_reset_callback *callback_info) +{ + struct completion *reset_completion = + (struct completion *) callback_info->driver_priv; + + complete(reset_completion); +} + /** * usb_reset_endpoint - Reset an endpoint's state. + * Cannot be called in interrupt context. * @dev: the device whose endpoint is to be reset * @epaddr: the endpoint's address. Endpoint number for output, * endpoint number + USB_DIR_IN for input @@ -1085,18 +1094,62 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, */ void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr) { - unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK; - struct usb_host_endpoint *ep; + struct completion *reset_completion; + struct endpoint_reset_callback *callback_info; - if (usb_endpoint_out(epaddr)) - ep = dev->ep_out[epnum]; - else - ep = dev->ep_in[epnum]; - if (ep) - usb_hcd_reset_endpoint(dev, ep); + reset_completion = kmalloc(sizeof(*reset_completion), GFP_KERNEL); + /* FIXME Probably should do something more useful here */ + if (!reset_completion) + return; + callback_info = kzalloc(sizeof(*callback_info), GFP_KERNEL); + if (!callback_info) { + kfree(reset_completion); + return; + } + + INIT_COMPLETION(*reset_completion); + callback_info->dev = dev; + callback_info->epaddr = epaddr; + callback_info->driver_priv = reset_completion; + callback_info->callback = usb_finish_reset_endpoint; + + usb_hcd_start_reset_endpoint(callback_info); + wait_for_completion(reset_completion); + + kfree(reset_completion); + kfree(callback_info); } EXPORT_SYMBOL_GPL(usb_reset_endpoint); +/** + * usb_start_reset_endpoint - Reset an endpoint's state from interrupt context. + * @callback_info - caller-allocated information about which endpoint on the + * device to reset. Also contains the function pointer to callback when the HC + * is done resetting the endpoint. + * + * Resets any host-side endpoint state such as the toggle bit, + * sequence number or current window. + */ +void usb_start_reset_endpoint(struct endpoint_reset_callback *callback_info) +{ + struct usb_device *udev; + + if (!callback_info) + return; + + callback_info->epaddr &= USB_ENDPOINT_NUMBER_MASK; + udev = callback_info->dev; + if (usb_endpoint_out(callback_info->epaddr)) + callback_info->ep = udev->ep_out[callback_info->epaddr]; + else + callback_info->ep = udev->ep_in[callback_info->epaddr]; + + if (callback_info->ep) + usb_hcd_start_reset_endpoint(callback_info); + else + callback_info->callback(callback_info); +} +EXPORT_SYMBOL_GPL(usb_start_reset_endpoint); /** * usb_disable_interface -- Disable all endpoints for an interface diff --git a/include/linux/usb.h b/include/linux/usb.h index 3aa2cd1..de84f80 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1389,6 +1389,18 @@ extern int usb_reset_configuration(struct usb_device *dev); extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate); extern void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr); +struct endpoint_reset_callback { + /* Set by driver submitting reset command */ + struct usb_device *dev; + unsigned int epaddr; + void *driver_priv; + void (*callback)(struct endpoint_reset_callback *callback); + /* Set by USB core. */ + struct usb_host_endpoint *ep; +}; + +extern void usb_start_reset_endpoint(struct endpoint_reset_callback *callback_info); + /* this request isn't really synchronous, but it belongs with the others */ extern int usb_driver_set_configuration(struct usb_device *udev, int config); -- 1.5.6.5 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html