Commit 78d9a487 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 64b3d6d1
...@@ -201,6 +201,7 @@ static int usb_probe_interface(struct device *dev) ...@@ -201,6 +201,7 @@ static int usb_probe_interface(struct device *dev)
intf = to_usb_interface(dev); intf = to_usb_interface(dev);
udev = interface_to_usbdev(intf); udev = interface_to_usbdev(intf);
intf->needs_binding = 0;
if (udev->authorized == 0) { if (udev->authorized == 0) {
dev_err(&intf->dev, "Device is not authorized for usage\n"); dev_err(&intf->dev, "Device is not authorized for usage\n");
...@@ -311,6 +312,7 @@ int usb_driver_claim_interface(struct usb_driver *driver, ...@@ -311,6 +312,7 @@ int usb_driver_claim_interface(struct usb_driver *driver,
dev->driver = &driver->drvwrap.driver; dev->driver = &driver->drvwrap.driver;
usb_set_intfdata(iface, priv); usb_set_intfdata(iface, priv);
iface->needs_binding = 0;
usb_pm_lock(udev); usb_pm_lock(udev);
iface->condition = USB_INTERFACE_BOUND; iface->condition = USB_INTERFACE_BOUND;
...@@ -772,6 +774,104 @@ void usb_deregister(struct usb_driver *driver) ...@@ -772,6 +774,104 @@ void usb_deregister(struct usb_driver *driver)
} }
EXPORT_SYMBOL_GPL(usb_deregister); 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 #ifdef CONFIG_PM
/* Caller has locked udev's pm_mutex */ /* Caller has locked udev's pm_mutex */
...@@ -841,7 +941,7 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg) ...@@ -841,7 +941,7 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg)
goto done; goto done;
driver = to_usb_driver(intf->dev.driver); driver = to_usb_driver(intf->dev.driver);
if (driver->suspend && driver->resume) { if (driver->suspend) {
status = driver->suspend(intf, msg); status = driver->suspend(intf, msg);
if (status == 0) if (status == 0)
mark_quiesced(intf); mark_quiesced(intf);
...@@ -849,12 +949,10 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg) ...@@ -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", dev_err(&intf->dev, "%s error %d\n",
"suspend", status); "suspend", status);
} else { } else {
/* /* Later we will unbind the driver and reprobe */
* FIXME else if there's no suspend method, disconnect... intf->needs_binding = 1;
* Not possible if auto_pm is set... dev_warn(&intf->dev, "no %s for driver %s?\n",
*/ "suspend", driver->name);
dev_warn(&intf->dev, "no suspend for driver %s?\n",
driver->name);
mark_quiesced(intf); mark_quiesced(intf);
} }
...@@ -878,10 +976,12 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume) ...@@ -878,10 +976,12 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
goto done; goto done;
/* Can't resume it if it doesn't have a driver. */ /* Can't resume it if it doesn't have a driver. */
if (intf->condition == USB_INTERFACE_UNBOUND) { if (intf->condition == USB_INTERFACE_UNBOUND)
status = -ENOTCONN; goto done;
/* Don't resume if the interface is marked for rebinding */
if (intf->needs_binding)
goto done; goto done;
}
driver = to_usb_driver(intf->dev.driver); driver = to_usb_driver(intf->dev.driver);
if (reset_resume) { if (reset_resume) {
...@@ -891,7 +991,7 @@ static int usb_resume_interface(struct usb_interface *intf, int 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", dev_err(&intf->dev, "%s error %d\n",
"reset_resume", status); "reset_resume", status);
} else { } else {
/* status = -EOPNOTSUPP; */ intf->needs_binding = 1;
dev_warn(&intf->dev, "no %s for driver %s?\n", dev_warn(&intf->dev, "no %s for driver %s?\n",
"reset_resume", driver->name); "reset_resume", driver->name);
} }
...@@ -902,7 +1002,7 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume) ...@@ -902,7 +1002,7 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
dev_err(&intf->dev, "%s error %d\n", dev_err(&intf->dev, "%s error %d\n",
"resume", status); "resume", status);
} else { } else {
/* status = -EOPNOTSUPP; */ intf->needs_binding = 1;
dev_warn(&intf->dev, "no %s for driver %s?\n", dev_warn(&intf->dev, "no %s for driver %s?\n",
"resume", driver->name); "resume", driver->name);
} }
...@@ -910,11 +1010,10 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume) ...@@ -910,11 +1010,10 @@ static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
done: done:
dev_vdbg(&intf->dev, "%s: status %d\n", __func__, status); dev_vdbg(&intf->dev, "%s: status %d\n", __func__, status);
if (status == 0) if (status == 0 && intf->condition == USB_INTERFACE_BOUND)
mark_active(intf); mark_active(intf);
/* FIXME: Unbind the driver and reprobe if the resume failed /* Later we will unbind the driver and/or reprobe, if necessary */
* (not possible if auto_pm is set) */
return status; return status;
} }
...@@ -1470,6 +1569,7 @@ int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg) ...@@ -1470,6 +1569,7 @@ int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg)
{ {
int status; int status;
do_unbind_rebind(udev, DO_UNBIND);
usb_pm_lock(udev); usb_pm_lock(udev);
udev->auto_pm = 0; udev->auto_pm = 0;
status = usb_suspend_both(udev, msg); status = usb_suspend_both(udev, msg);
...@@ -1497,6 +1597,7 @@ int usb_external_resume_device(struct usb_device *udev) ...@@ -1497,6 +1597,7 @@ int usb_external_resume_device(struct usb_device *udev)
status = usb_resume_both(udev); status = usb_resume_both(udev);
udev->last_busy = jiffies; udev->last_busy = jiffies;
usb_pm_unlock(udev); usb_pm_unlock(udev);
do_unbind_rebind(udev, DO_REBIND);
/* Now that the device is awake, we can start trying to autosuspend /* Now that the device is awake, we can start trying to autosuspend
* it again. */ * it again. */
......
...@@ -3367,6 +3367,11 @@ re_enumerate: ...@@ -3367,6 +3367,11 @@ re_enumerate:
* this from a driver probe() routine after downloading new firmware. * this from a driver probe() routine after downloading new firmware.
* For calls that might not occur during probe(), drivers should lock * For calls that might not occur during probe(), drivers should lock
* the device using usb_lock_device_for_reset(). * 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) int usb_reset_device(struct usb_device *udev)
{ {
...@@ -3388,12 +3393,17 @@ 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) { for (i = 0; i < config->desc.bNumInterfaces; ++i) {
struct usb_interface *cintf = config->interface[i]; struct usb_interface *cintf = config->interface[i];
struct usb_driver *drv; struct usb_driver *drv;
int unbind = 0;
if (cintf->dev.driver) { if (cintf->dev.driver) {
drv = to_usb_driver(cintf->dev.driver); drv = to_usb_driver(cintf->dev.driver);
if (drv->pre_reset) if (drv->pre_reset && drv->post_reset)
(drv->pre_reset)(cintf); unbind = (drv->pre_reset)(cintf);
/* FIXME: Unbind if pre_reset returns an error or isn't defined */ 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) ...@@ -3404,13 +3414,18 @@ int usb_reset_device(struct usb_device *udev)
for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) { for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
struct usb_interface *cintf = config->interface[i]; struct usb_interface *cintf = config->interface[i];
struct usb_driver *drv; 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); drv = to_usb_driver(cintf->dev.driver);
if (drv->post_reset) if (drv->post_reset)
(drv->post_reset)(cintf); rebind = (drv->post_reset)(cintf);
/* FIXME: Unbind if post_reset returns an error or isn't defined */ else if (cintf->condition ==
USB_INTERFACE_BOUND)
rebind = 1;
} }
if (rebind)
usb_rebind_intf(cintf);
} }
} }
......
...@@ -29,6 +29,8 @@ extern int usb_choose_configuration(struct usb_device *udev); ...@@ -29,6 +29,8 @@ extern int usb_choose_configuration(struct usb_device *udev);
extern void usb_kick_khubd(struct usb_device *dev); extern void usb_kick_khubd(struct usb_device *dev);
extern int usb_match_device(struct usb_device *dev, extern int usb_match_device(struct usb_device *dev,
const struct usb_device_id *id); 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 int usb_hub_init(void);
extern void usb_hub_cleanup(void); extern void usb_hub_cleanup(void);
......
...@@ -160,6 +160,7 @@ struct usb_interface { ...@@ -160,6 +160,7 @@ struct usb_interface {
unsigned is_active:1; /* the interface is not suspended */ unsigned is_active:1; /* the interface is not suspended */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */ unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */ 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 dev; /* interface specific device info */
struct device *usb_dev; struct device *usb_dev;
......
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