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 and unbind operations won't work during a system sleep transition. So instead the code has to keep track of when such a transition is in progress and defer the operations until the transition ends. In an attempt to ameliorate this issue, the code iterates through all the USB drivers when a sleep transition is about to begin and immediately unbinds any that don't have suspend and resume methods. Signed-off-by: Alan Stern <[EMAIL PROTECTED]> --- This patch can't be applied as is; I'm posting it for discussion only. Not only does it depend on the three previous patches (as1021-as1023), it also depends on these two commits in Len Brown's ACPI tree: http://git.kernel.org/?p=linux/kernel/git/lenb/linux-acpi-2.6.git;a=commitdiff;h=abd75f7ef601a04e6feb5728b7ffd7f8a2f8c79f http://git.kernel.org/?p=linux/kernel/git/lenb/linux-acpi-2.6.git;a=commitdiff;h=bfe0e90eb67331f16a3cf916108bb4a0d8c206d7 Index: usb-2.6/include/linux/usb.h =================================================================== --- usb-2.6.orig/include/linux/usb.h +++ usb-2.6/include/linux/usb.h @@ -159,6 +159,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; /* pointer to the usb class's device, if any */ Index: usb-2.6/drivers/usb/core/usb.h =================================================================== --- usb-2.6.orig/drivers/usb/core/usb.h +++ usb-2.6/drivers/usb/core/usb.h @@ -28,6 +28,8 @@ extern int usb_choose_configuration(stru 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); @@ -38,6 +40,9 @@ extern void usb_host_cleanup(void); #ifdef CONFIG_PM +extern int usb_pm_init(void); +extern void usb_pm_cleanup(void); + extern void usb_autosuspend_work(struct work_struct *work); extern int usb_port_suspend(struct usb_device *dev); extern int usb_port_resume(struct usb_device *dev); @@ -57,6 +62,13 @@ static inline void usb_pm_unlock(struct #else +static inline int usb_pm_init(void) +{ + return 0; +} + +static inline void usb_pm_cleanup(void) {} + static inline int usb_port_suspend(struct usb_device *udev) { return 0; @@ -70,7 +82,7 @@ static inline int usb_port_resume(struct static inline void usb_pm_lock(struct usb_device *udev) {} static inline void usb_pm_unlock(struct usb_device *udev) {} -#endif +#endif /* CONFIG_PM */ #ifdef CONFIG_USB_SUSPEND Index: usb-2.6/drivers/usb/core/usb.c =================================================================== --- usb-2.6.orig/drivers/usb/core/usb.c +++ usb-2.6/drivers/usb/core/usb.c @@ -920,6 +920,9 @@ static int __init usb_init(void) retval = bus_register(&usb_bus_type); if (retval) goto bus_register_failed; + retval = usb_pm_init(); + if (retval) + goto pm_init_failed; retval = usb_host_init(); if (retval) goto host_init_failed; @@ -954,6 +957,8 @@ driver_register_failed: major_init_failed: usb_host_cleanup(); host_init_failed: + usb_pm_cleanup(); +pm_init_failed: bus_unregister(&usb_bus_type); bus_register_failed: ksuspend_usb_cleanup(); @@ -977,6 +982,7 @@ static void __exit usb_exit(void) usb_devio_cleanup(); usb_hub_cleanup(); usb_host_cleanup(); + usb_pm_cleanup(); bus_unregister(&usb_bus_type); ksuspend_usb_cleanup(); } Index: usb-2.6/drivers/usb/core/driver.c =================================================================== --- usb-2.6.orig/drivers/usb/core/driver.c +++ usb-2.6/drivers/usb/core/driver.c @@ -26,6 +26,7 @@ #include <linux/usb.h> #include <linux/usb/quirks.h> #include <linux/workqueue.h> +#include <linux/suspend.h> #include "hcd.h" #include "usb.h" @@ -201,6 +202,7 @@ static int usb_probe_interface(struct de 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"); @@ -310,6 +312,7 @@ int usb_driver_claim_interface(struct us dev->driver = &driver->drvwrap.driver; usb_set_intfdata(iface, priv); + iface->needs_binding = 0; usb_pm_lock(udev); iface->condition = USB_INTERFACE_BOUND; @@ -771,8 +774,160 @@ void usb_deregister(struct usb_driver *d } EXPORT_SYMBOL_GPL_FUTURE(usb_deregister); + +/* Boolean flag and mutex to protect it */ +static int system_sleep_in_progress; +static DEFINE_MUTEX(system_sleep_in_progress_mutex); + +/* 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 the interface's parent device's lock + * but not its pm_mutex. + */ +void usb_forced_unbind_intf(struct usb_interface *intf) +{ + struct usb_driver *driver = to_usb_driver(intf->dev.driver); + + mutex_lock(&system_sleep_in_progress_mutex); + if (!system_sleep_in_progress) { + dev_dbg(&intf->dev, "force unbind\n"); + usb_driver_release_interface(driver, intf); + } + + /* Mark the interface for delayed unbinding (if we couldn't + * acquire the rwsem above) and for later rebinding. + */ + intf->needs_binding = 1; + mutex_unlock(&system_sleep_in_progress_mutex); +} + +/* Delayed forced unbinding of a USB interface driver and scan + * for rebinding. + * + * The caller must hold the interface's parent device's lock + * but not its pm_mutex. + */ +void usb_rebind_intf(struct usb_interface *intf) +{ + int rc; + + mutex_lock(&system_sleep_in_progress_mutex); + if (system_sleep_in_progress) { + + /* Mark the interface for later unbinding/rebinding */ + intf->needs_binding = 1; + } else { + + /* Delayed unbind of an existing driver */ + if (intf->dev.driver) { + struct usb_driver *driver = + to_usb_driver(intf->dev.driver); + + dev_dbg(&intf->dev, "force unbind\n"); + usb_driver_release_interface(driver, intf); + } + + /* Try to rebind the interface */ + intf->needs_binding = 0; + rc = device_attach(&intf->dev); + } + mutex_unlock(&system_sleep_in_progress_mutex); +} + +#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. + */ +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) + usb_rebind_intf(intf); + break; + } + } + } +} + #ifdef CONFIG_PM +static int unbind_rebind_iterator(struct device *dev, void *action) +{ + struct usb_device *udev; + + if (is_usb_device(dev)) { + udev = to_usb_device(dev); + usb_lock_device(udev); + do_unbind_rebind(udev, (int) (unsigned long) action); + usb_unlock_device(udev); + } + return 0; +} + +static int usb_pm_notifier(struct notifier_block *nb, unsigned long value, + void *ptr) +{ + switch (value) { + case PM_SUSPEND_PREPARE: + case PM_HIBERNATION_PREPARE: + case PM_RESTORE_PREPARE: + bus_for_each_dev(&usb_bus_type, NULL, (void *) DO_UNBIND, + unbind_rebind_iterator); + mutex_lock(&system_sleep_in_progress_mutex); + system_sleep_in_progress = 1; + mutex_unlock(&system_sleep_in_progress_mutex); + break; + + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + mutex_lock(&system_sleep_in_progress_mutex); + system_sleep_in_progress = 0; + mutex_unlock(&system_sleep_in_progress_mutex); + bus_for_each_dev(&usb_bus_type, NULL, (void *) DO_REBIND, + unbind_rebind_iterator); + break; + } + return NOTIFY_OK; +} + +struct notifier_block usb_pm_nb = { + .notifier_call = usb_pm_notifier, +}; + +int usb_pm_init(void) +{ + return register_pm_notifier(&usb_pm_nb); +} + +void usb_pm_cleanup(void) +{ + unregister_pm_notifier(&usb_pm_nb); +} + /* Caller has locked udev's pm_mutex */ static int usb_suspend_device(struct usb_device *udev, pm_message_t msg) { @@ -854,8 +1009,8 @@ static int usb_suspend_interface(struct 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... + /* Later we will unbind the driver and reprobe */ + intf->needs_binding = 1; dev_warn(&intf->dev, "no suspend for driver %s?\n", driver->name); mark_quiesced(intf); @@ -885,6 +1040,12 @@ static int usb_resume_interface(struct u status = -ENOTCONN; goto done; } + + /* Don't resume if the interface is marked for rebinding */ + if (intf->needs_binding) { + status = -EOPNOTSUPP; + goto done; + } driver = to_usb_driver(intf->dev.driver); if (reset_resume) { @@ -894,7 +1055,8 @@ static int usb_resume_interface(struct u dev_err(&intf->dev, "%s error %d\n", "reset_resume", status); } else { - // status = -EOPNOTSUPP; + status = -EOPNOTSUPP; + intf->needs_binding = 1; dev_warn(&intf->dev, "no %s for driver %s?\n", "reset_resume", driver->name); } @@ -905,7 +1067,8 @@ static int usb_resume_interface(struct u dev_err(&intf->dev, "%s error %d\n", "resume", status); } else { - // status = -EOPNOTSUPP; + status = -EOPNOTSUPP; + intf->needs_binding = 1; dev_warn(&intf->dev, "no %s for driver %s?\n", "resume", driver->name); } @@ -916,8 +1079,7 @@ done: if (status == 0) 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; } @@ -1477,6 +1639,7 @@ int usb_external_suspend_device(struct u { int status; + do_unbind_rebind(udev, DO_UNBIND); usb_pm_lock(udev); udev->auto_pm = 0; status = usb_suspend_both(udev, msg); @@ -1504,6 +1667,7 @@ int usb_external_resume_device(struct us 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. */ Index: usb-2.6/drivers/usb/core/hub.c =================================================================== --- usb-2.6.orig/drivers/usb/core/hub.c +++ usb-2.6/drivers/usb/core/hub.c @@ -3111,6 +3111,11 @@ EXPORT_SYMBOL(usb_reset_device); * 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 @iface is not NULL and is currently being probed, we assume that + * 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_composite_device(struct usb_device *udev, struct usb_interface *iface) @@ -3136,12 +3141,16 @@ int usb_reset_composite_device(struct us 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 + unbind = 1; + if (cintf != iface && unbind) + usb_forced_unbind_intf(cintf); } } } @@ -3152,13 +3161,17 @@ int usb_reset_composite_device(struct us 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 + rebind = 1; } + if (cintf != iface && rebind) + usb_rebind_intf(cintf); } } - To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html
