diff options
author | Inaky Perez-Gonzalez <inaky@linux.intel.com> | 2008-11-13 21:31:35 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-01-07 20:59:53 +0300 |
commit | dc023dceec861c60bc1d1a17a2c6496ddac26ee7 (patch) | |
tree | 908529b3eb975acb2582f679d20a81bfadc95bf4 /drivers/usb/core/message.c | |
parent | 9ac39f28b5237a629e41ccfc1f73d3a55723045c (diff) | |
download | linux-dc023dceec861c60bc1d1a17a2c6496ddac26ee7.tar.xz |
USB: Introduce usb_queue_reset() to do resets from atomic contexts
This patch introduces a new call to be able to do a USB reset from an
atomic contect. This is quite helpful in USB callbacks to handle
errors (when the only thing that can be done is to do a device
reset).
It is done queuing a work struct that will do the actual reset. The
struct is "attached" to an interface so pending requests from an
interface are removed when said interface is unbound from the driver.
The call flow then becomes:
usb_queue_reset_device()
__usb_queue_reset_device() [workqueue]
usb_reset_device()
usb_probe_interface()
usb_cancel_queue_reset() [error path]
usb_unbind_interface()
usb_cancel_queue_reset()
usb_driver_release_interface()
usb_cancel_queue_reset()
Note usb_cancel_queue_reset() needs smarts to try not to unqueue when
it is actually being executed. This happens when we run the reset from
the workqueue: usb_reset_device() is called and on interface unbind
time, usb_cancel_queue_reset() would be called. That would deadlock on
cancel_work_sync(). To avoid that, we set (before running
usb_reset_device()) usb_intf->reset_running and clear it inmediately
after returning.
Patch is against 2.6.28-rc2 and depends on
http://marc.info/?l=linux-usb&m=122581634925308&w=2 (as submitted by
Alan Stern).
Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/core/message.c')
-rw-r--r-- | drivers/usb/core/message.c | 41 |
1 files changed, 41 insertions, 0 deletions
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index cc47d36798b1..aadf29f09c45 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1441,6 +1441,46 @@ static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev, return retval; } + +/* + * Internal function to queue a device reset + * + * This is initialized into the workstruct in 'struct + * usb_device->reset_ws' that is launched by + * message.c:usb_set_configuration() when initializing each 'struct + * usb_interface'. + * + * It is safe to get the USB device without reference counts because + * the life cycle of @iface is bound to the life cycle of @udev. Then, + * this function will be ran only if @iface is alive (and before + * freeing it any scheduled instances of it will have been cancelled). + * + * We need to set a flag (usb_dev->reset_running) because when we call + * the reset, the interfaces might be unbound. The current interface + * cannot try to remove the queued work as it would cause a deadlock + * (you cannot remove your work from within your executing + * workqueue). This flag lets it know, so that + * usb_cancel_queued_reset() doesn't try to do it. + * + * See usb_queue_reset_device() for more details + */ +void __usb_queue_reset_device(struct work_struct *ws) +{ + int rc; + struct usb_interface *iface = + container_of(ws, struct usb_interface, reset_ws); + struct usb_device *udev = interface_to_usbdev(iface); + + rc = usb_lock_device_for_reset(udev, iface); + if (rc >= 0) { + iface->reset_running = 1; + usb_reset_device(udev); + iface->reset_running = 0; + usb_unlock_device(udev); + } +} + + /* * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated @@ -1611,6 +1651,7 @@ free_interfaces: intf->dev.type = &usb_if_device_type; intf->dev.groups = usb_interface_groups; intf->dev.dma_mask = dev->dev.dma_mask; + INIT_WORK(&intf->reset_ws, __usb_queue_reset_device); device_initialize(&intf->dev); mark_quiesced(intf); dev_set_name(&intf->dev, "%d-%s:%d.%d", |