summaryrefslogtreecommitdiffstats
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorInaky Perez-Gonzalez <inaky@linux.intel.com>2008-11-13 10:31:35 -0800
committerGreg Kroah-Hartman <gregkh@suse.de>2009-01-07 09:59:53 -0800
commitdc023dceec861c60bc1d1a17a2c6496ddac26ee7 (patch)
tree908529b3eb975acb2582f679d20a81bfadc95bf4 /drivers/usb/core
parent9ac39f28b5237a629e41ccfc1f73d3a55723045c (diff)
downloadblackbird-op-linux-dc023dceec861c60bc1d1a17a2c6496ddac26ee7.tar.gz
blackbird-op-linux-dc023dceec861c60bc1d1a17a2c6496ddac26ee7.zip
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')
-rw-r--r--drivers/usb/core/driver.c23
-rw-r--r--drivers/usb/core/hub.c43
-rw-r--r--drivers/usb/core/message.c41
3 files changed, 105 insertions, 2 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 23b3c7e79d4b..7e26fb3c2759 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -184,6 +184,20 @@ static int usb_unbind_device(struct device *dev)
return 0;
}
+/*
+ * Cancel any pending scheduled resets
+ *
+ * [see usb_queue_reset_device()]
+ *
+ * Called after unconfiguring / when releasing interfaces. See
+ * comments in __usb_queue_reset_device() regarding
+ * udev->reset_running.
+ */
+static void usb_cancel_queued_reset(struct usb_interface *iface)
+{
+ if (iface->reset_running == 0)
+ cancel_work_sync(&iface->reset_ws);
+}
/* called from driver core with dev locked */
static int usb_probe_interface(struct device *dev)
@@ -242,6 +256,7 @@ static int usb_probe_interface(struct device *dev)
mark_quiesced(intf);
intf->needs_remote_wakeup = 0;
intf->condition = USB_INTERFACE_UNBOUND;
+ usb_cancel_queued_reset(intf);
} else
intf->condition = USB_INTERFACE_BOUND;
@@ -272,6 +287,7 @@ static int usb_unbind_interface(struct device *dev)
usb_disable_interface(udev, intf);
driver->disconnect(intf);
+ usb_cancel_queued_reset(intf);
/* Reset other interface state.
* We cannot do a Set-Interface if the device is suspended or
@@ -380,8 +396,10 @@ void usb_driver_release_interface(struct usb_driver *driver,
if (device_is_registered(dev)) {
iface->condition = USB_INTERFACE_UNBINDING;
device_release_driver(dev);
+ } else {
+ iface->condition = USB_INTERFACE_UNBOUND;
+ usb_cancel_queued_reset(iface);
}
-
dev->driver = NULL;
usb_set_intfdata(iface, NULL);
@@ -942,7 +960,8 @@ static int usb_suspend_interface(struct usb_device *udev,
if (udev->state == USB_STATE_NOTATTACHED || !is_active(intf))
goto done;
- if (intf->condition == USB_INTERFACE_UNBOUND) /* This can't happen */
+ /* This can happen; see usb_driver_release_interface() */
+ if (intf->condition == USB_INTERFACE_UNBOUND)
goto done;
driver = to_usb_driver(intf->dev.driver);
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 95fb3104ba4f..e65881899c8f 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -3518,3 +3518,46 @@ int usb_reset_device(struct usb_device *udev)
return ret;
}
EXPORT_SYMBOL_GPL(usb_reset_device);
+
+
+/**
+ * usb_queue_reset_device - Reset a USB device from an atomic context
+ * @iface: USB interface belonging to the device to reset
+ *
+ * This function can be used to reset a USB device from an atomic
+ * context, where usb_reset_device() won't work (as it blocks).
+ *
+ * Doing a reset via this method is functionally equivalent to calling
+ * usb_reset_device(), except for the fact that it is delayed to a
+ * workqueue. This means that any drivers bound to other interfaces
+ * might be unbound, as well as users from usbfs in user space.
+ *
+ * Corner cases:
+ *
+ * - Scheduling two resets at the same time from two different drivers
+ * attached to two different interfaces of the same device is
+ * possible; depending on how the driver attached to each interface
+ * handles ->pre_reset(), the second reset might happen or not.
+ *
+ * - If a driver is unbound and it had a pending reset, the reset will
+ * be cancelled.
+ *
+ * - This function can be called during .probe() or .disconnect()
+ * times. On return from .disconnect(), any pending resets will be
+ * cancelled.
+ *
+ * There is no no need to lock/unlock the @reset_ws as schedule_work()
+ * does its own.
+ *
+ * NOTE: We don't do any reference count tracking because it is not
+ * needed. The lifecycle of the work_struct is tied to the
+ * usb_interface. Before destroying the interface we cancel the
+ * work_struct, so the fact that work_struct is queued and or
+ * running means the interface (and thus, the device) exist and
+ * are referenced.
+ */
+void usb_queue_reset_device(struct usb_interface *iface)
+{
+ schedule_work(&iface->reset_ws);
+}
+EXPORT_SYMBOL_GPL(usb_queue_reset_device);
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",
OpenPOWER on IntegriCloud