aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2008-06-23 16:00:40 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2008-07-21 15:16:40 -0700
commit78d9a487ee961c356e1a934d9a92eca38ffb3a70 (patch)
treea49a862bf45b1aae6ecd746ac8aa0f48fdfa0917
parent64b3d6d11948cc71ff12124dcb693392a32f1bf4 (diff)
USB: Force unbinding of drivers lacking reset_resume or other methods
This patch (as1024) takes care of a FIXME issue: Drivers that don't have the necessary suspend, resume, reset_resume, pre_reset, or post_reset methods will be unbound and their interface reprobed when one of the unsupported events occurs. This is made slightly more difficult by the fact that bind operations won't work during a system sleep transition. So instead the code has to defer the operation until the transition ends. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/core/driver.c131
-rw-r--r--drivers/usb/core/hub.c27
-rw-r--r--drivers/usb/core/usb.h2
-rw-r--r--include/linux/usb.h1
4 files changed, 140 insertions, 21 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 8da1a56659b..ddb54e14a5c 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -201,6 +201,7 @@ static int usb_probe_interface(struct device *dev)
intf = to_usb_interface(dev);
udev = interface_to_usbdev(intf);
+ intf->needs_binding = 0;
if (udev->authorized == 0) {
dev_err(&intf->dev, "Device is not authorized for usage\n");
@@ -311,6 +312,7 @@ int usb_driver_claim_interface(struct usb_driver *driver,
dev->driver = &driver->drvwrap.driver;
usb_set_intfdata(iface, priv);
+ iface->needs_binding = 0;
usb_pm_lock(udev);
iface->condition = USB_INTERFACE_BOUND;
@@ -772,6 +774,104 @@ void usb_deregister(struct usb_driver *driver)
}
EXPORT_SYMBOL_GPL(usb_deregister);
+
+/* Forced unbinding of a USB interface driver, either because
+ * it doesn't support pre_reset/post_reset/reset_resume or
+ * because it doesn't support suspend/resume.
+ *
+ * The caller must hold @intf's device's lock, but not its pm_mutex
+ * and not @intf->dev.sem.
+ */
+void usb_forced_unbind_intf(struct usb_interface *intf)
+{
+ struct usb_driver *driver = to_usb_driver(intf->dev.driver);
+
+ dev_dbg(&intf->dev, "forced unbind\n");
+ usb_driver_release_interface(driver, intf);
+
+ /* Mark the interface for later rebinding */
+ intf->needs_binding = 1;
+}
+
+/* Delayed forced unbinding of a USB interface driver and scan
+ * for rebinding.
+ *
+ * The caller must hold @intf's device's lock, but not its pm_mutex
+ * and not @intf->dev.sem.
+ *
+ * FIXME: The caller must block system sleep transitions.
+ */
+void usb_rebind_intf(struct usb_interface *intf)
+{
+ int rc;
+
+ /* Delayed unbind of an existing driver */
+ if (intf->dev.driver) {
+ struct usb_driver *driver =
+ to_usb_driver(intf->dev.driver);
+
+ dev_dbg(&intf->dev, "forced unbind\n");
+ usb_driver_release_interface(driver, intf);
+ }
+
+ /* Try to rebind the interface */
+ intf->needs_binding = 0;
+ rc = device_attach(&intf->dev);
+ if (rc < 0)
+ dev_warn(&intf->dev, "rebind failed: %d\n", rc);
+}
+
+#define DO_UNBIND 0
+#define DO_REBIND 1
+
+/* Unbind drivers for @udev's interfaces that don't support suspend/resume,
+ * or rebind interfaces that have been unbound, according to @action.
+ *
+ * The caller must hold @udev's device lock.
+ * FIXME: For rebinds, the caller must block system sleep transitions.
+ */
+static void do_unbind_rebind(struct usb_device *udev, int action)
+{
+ struct usb_host_config *config;
+ int i;
+ struct usb_interface *intf;
+ struct usb_driver *drv;
+
+ config = udev->actconfig;
+ if (config) {
+ for (i = 0; i < config->desc.bNumInterfaces; ++i) {
+ intf = config->interface[i];
+ switch (action) {
+ case DO_UNBIND:
+ if (intf->dev.driver) {
+ drv = to_usb_driver(intf->dev.driver);
+ if (!drv->suspend || !drv->resume)
+ usb_forced_unbind_intf(intf);
+ }
+ break;
+ case DO_REBIND:
+ if (intf->needs_binding) {
+
+ /* FIXME: The next line is needed because we are going to probe
+ * the interface, but as far as the PM core is concerned the
+ * interface is still suspended. The problem wouldn't exist
+ * if we could rebind the interface during the interface's own
+ * resume() call, but at the time the usb_device isn't locked!
+ *
+ * The real solution will be to carry this out during the device's
+ * complete() callback. Until that is implemented, we have to
+ * use this hack.
+ */
+// intf->dev.power.sleeping = 0;
+
+ usb_rebind_intf(intf);
+ }
+ break;
+ }
+ }
+ }
+}
+
#ifdef CONFIG_PM
/* Caller has locked udev's pm_mutex */
@@ -841,7 +941,7 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg)
goto done;
driver = to_usb_driver(intf->dev.driver);
- if (driver->suspend && driver->resume) {
+ if (driver->suspend) {
status = driver->suspend(intf, msg);
if (status == 0)
mark_quiesced(intf);
@@ -849,12 +949,10 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg)
dev_err(&intf->dev, "%s error %d\n",
"suspend", status);
} else {
- /*
- * FIXME else if there's no suspend method, disconnect...
- * Not possible if auto_pm is set...
- */
- dev_warn(&intf->dev, "no suspend for driver %s?\n",
- driver->name);
+ /* Later we will unbind the driver and reprobe */
+ intf->needs_binding = 1;
+ dev_warn(&intf->dev, "no %s for driver %s?\n",
+ "suspend", driver->name);
mark_quiesced(intf);
}
@@ -878,10 +976,12 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
goto done;
/* Can't resume it if it doesn't have a driver. */
- if (intf->condition == USB_INTERFACE_UNBOUND) {
- status = -ENOTCONN;
+ if (intf->condition == USB_INTERFACE_UNBOUND)
+ goto done;
+
+ /* Don't resume if the interface is marked for rebinding */
+ if (intf->needs_binding)
goto done;
- }
driver = to_usb_driver(intf->dev.driver);
if (reset_resume) {
@@ -891,7 +991,7 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
dev_err(&intf->dev, "%s error %d\n",
"reset_resume", status);
} else {
- /* status = -EOPNOTSUPP; */
+ intf->needs_binding = 1;
dev_warn(&intf->dev, "no %s for driver %s?\n",
"reset_resume", driver->name);
}
@@ -902,7 +1002,7 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
dev_err(&intf->dev, "%s error %d\n",
"resume", status);
} else {
- /* status = -EOPNOTSUPP; */
+ intf->needs_binding = 1;
dev_warn(&intf->dev, "no %s for driver %s?\n",
"resume", driver->name);
}
@@ -910,11 +1010,10 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
done:
dev_vdbg(&intf->dev, "%s: status %d\n", __func__, status);
- if (status == 0)
+ if (status == 0 && intf->condition == USB_INTERFACE_BOUND)
mark_active(intf);
- /* FIXME: Unbind the driver and reprobe if the resume failed
- * (not possible if auto_pm is set) */
+ /* Later we will unbind the driver and/or reprobe, if necessary */
return status;
}
@@ -1470,6 +1569,7 @@ int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg)
{
int status;
+ do_unbind_rebind(udev, DO_UNBIND);
usb_pm_lock(udev);
udev->auto_pm = 0;
status = usb_suspend_both(udev, msg);
@@ -1497,6 +1597,7 @@ int usb_external_resume_device(struct usb_device *udev)
status = usb_resume_both(udev);
udev->last_busy = jiffies;
usb_pm_unlock(udev);
+ do_unbind_rebind(udev, DO_REBIND);
/* Now that the device is awake, we can start trying to autosuspend
* it again. */
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index bb3ecc4c08f..f1efabbc1ca 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -3367,6 +3367,11 @@ re_enumerate:
* this from a driver probe() routine after downloading new firmware.
* For calls that might not occur during probe(), drivers should lock
* the device using usb_lock_device_for_reset().
+ *
+ * If an interface is currently being probed or disconnected, we assume
+ * its driver knows how to handle resets. For all other interfaces,
+ * if the driver doesn't have pre_reset and post_reset methods then
+ * we attempt to unbind it and rebind afterward.
*/
int usb_reset_device(struct usb_device *udev)
{
@@ -3388,12 +3393,17 @@ int usb_reset_device(struct usb_device *udev)
for (i = 0; i < config->desc.bNumInterfaces; ++i) {
struct usb_interface *cintf = config->interface[i];
struct usb_driver *drv;
+ int unbind = 0;
if (cintf->dev.driver) {
drv = to_usb_driver(cintf->dev.driver);
- if (drv->pre_reset)
- (drv->pre_reset)(cintf);
- /* FIXME: Unbind if pre_reset returns an error or isn't defined */
+ if (drv->pre_reset && drv->post_reset)
+ unbind = (drv->pre_reset)(cintf);
+ else if (cintf->condition ==
+ USB_INTERFACE_BOUND)
+ unbind = 1;
+ if (unbind)
+ usb_forced_unbind_intf(cintf);
}
}
}
@@ -3404,13 +3414,18 @@ int usb_reset_device(struct usb_device *udev)
for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
struct usb_interface *cintf = config->interface[i];
struct usb_driver *drv;
+ int rebind = cintf->needs_binding;
- if (cintf->dev.driver) {
+ if (!rebind && cintf->dev.driver) {
drv = to_usb_driver(cintf->dev.driver);
if (drv->post_reset)
- (drv->post_reset)(cintf);
- /* FIXME: Unbind if post_reset returns an error or isn't defined */
+ rebind = (drv->post_reset)(cintf);
+ else if (cintf->condition ==
+ USB_INTERFACE_BOUND)
+ rebind = 1;
}
+ if (rebind)
+ usb_rebind_intf(cintf);
}
}
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 1a8bc21c335..d3eb0a29bca 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -29,6 +29,8 @@ extern int usb_choose_configuration(struct usb_device *udev);
extern void usb_kick_khubd(struct usb_device *dev);
extern int usb_match_device(struct usb_device *dev,
const struct usb_device_id *id);
+extern void usb_forced_unbind_intf(struct usb_interface *intf);
+extern void usb_rebind_intf(struct usb_interface *intf);
extern int usb_hub_init(void);
extern void usb_hub_cleanup(void);
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 3cc8db5254d..5811c5da69f 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -160,6 +160,7 @@ struct usb_interface {
unsigned is_active:1; /* the interface is not suspended */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
+ unsigned needs_binding:1; /* needs delayed unbind/rebind */
struct device dev; /* interface specific device info */
struct device *usb_dev;