diff options
-rw-r--r-- | drivers/usb/core/generic.c | 2 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 184 | ||||
-rw-r--r-- | drivers/usb/core/usb.h | 1 |
3 files changed, 93 insertions, 94 deletions
diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 4cbe7b33951..b2fc2b11525 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -219,8 +219,6 @@ static int generic_resume(struct usb_device *udev) */ if (!udev->parent) rc = hcd_bus_resume(udev); - else if (udev->reset_resume) - rc = usb_reset_suspended_device(udev); else rc = usb_port_resume(udev); return rc; diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0b8ed414d5c..c4cdb69a6e9 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -31,6 +31,12 @@ #include "hcd.h" #include "hub.h" +#ifdef CONFIG_USB_PERSIST +#define USB_PERSIST 1 +#else +#define USB_PERSIST 0 +#endif + struct usb_hub { struct device *intfdev; /* the "interface" device */ struct usb_device *hdev; @@ -1080,72 +1086,6 @@ void usb_set_device_state(struct usb_device *udev, spin_unlock_irqrestore(&device_state_lock, flags); } - -#ifdef CONFIG_PM - -/** - * usb_reset_suspended_device - reset a suspended device instead of resuming it - * @udev: device to be reset instead of resumed - * - * If a host controller doesn't maintain VBUS suspend current during a - * system sleep or is reset when the system wakes up, all the USB - * power sessions below it will be broken. This is especially troublesome - * for mass-storage devices containing mounted filesystems, since the - * device will appear to have disconnected and all the memory mappings - * to it will be lost. - * - * As an alternative, this routine attempts to recover power sessions for - * devices that are still present by resetting them instead of resuming - * them. If all goes well, the devices will appear to persist across the - * the interruption of the power sessions. - * - * This facility is inherently dangerous. Although usb_reset_device() - * makes every effort to insure that the same device is present after the - * reset as before, it cannot provide a 100% guarantee. Furthermore it's - * quite possible for a device to remain unaltered but its media to be - * changed. If the user replaces a flash memory card while the system is - * asleep, he will have only himself to blame when the filesystem on the - * new card is corrupted and the system crashes. - */ -int usb_reset_suspended_device(struct usb_device *udev) -{ - int rc = 0; - - dev_dbg(&udev->dev, "usb %sresume\n", "reset-"); - - /* After we're done the device won't be suspended any more. - * In addition, the reset won't work if udev->state is SUSPENDED. - */ - usb_set_device_state(udev, udev->actconfig - ? USB_STATE_CONFIGURED - : USB_STATE_ADDRESS); - - /* Root hubs don't need to be (and can't be) reset */ - if (udev->parent) - rc = usb_reset_device(udev); - return rc; -} - -/** - * usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power - * @rhdev: struct usb_device for the root hub - * - * The USB host controller driver calls this function when its root hub - * is resumed and Vbus power has been interrupted or the controller - * has been reset. The routine marks @rhdev as having lost power. When - * the hub driver is resumed it will take notice; if CONFIG_USB_PERSIST - * is enabled then it will carry out power-session recovery, otherwise - * it will disconnect all the child devices. - */ -void usb_root_hub_lost_power(struct usb_device *rhdev) -{ - dev_warn(&rhdev->dev, "root hub lost power or was reset\n"); - rhdev->reset_resume = 1; -} -EXPORT_SYMBOL_GPL(usb_root_hub_lost_power); - -#endif /* CONFIG_PM */ - static void choose_address(struct usb_device *udev) { int devnum; @@ -1672,18 +1612,22 @@ int usb_port_suspend(struct usb_device *udev) /* * If the USB "suspend" state is in use (rather than "global suspend"), * many devices will be individually taken out of suspend state using - * special" resume" signaling. These routines kick in shortly after + * special "resume" signaling. This routine kicks in shortly after * hardware resume signaling is finished, either because of selective * resume (by host) or remote wakeup (by device) ... now see what changed * in the tree that's rooted at this device. + * + * If @udev->reset_resume is set then the device is reset before the + * status check is done. */ static int finish_port_resume(struct usb_device *udev) { - int status; + int status = 0; u16 devstatus; /* caller owns the udev device lock */ - dev_dbg(&udev->dev, "finish resume\n"); + dev_dbg(&udev->dev, "finish %sresume\n", + udev->reset_resume ? "reset-" : ""); /* usb ch9 identifies four variants of SUSPENDED, based on what * state the device resumes to. Linux currently won't see the @@ -1694,13 +1638,23 @@ static int finish_port_resume(struct usb_device *udev) ? USB_STATE_CONFIGURED : USB_STATE_ADDRESS); + /* 10.5.4.5 says not to reset a suspended port if the attached + * device is enabled for remote wakeup. Hence the reset + * operation is carried out here, after the port has been + * resumed. + */ + if (udev->reset_resume) + status = usb_reset_device(udev); + /* 10.5.4.5 says be sure devices in the tree are still there. * For now let's assume the device didn't go crazy on resume, * and device drivers will know about any resume quirks. */ - status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus); - if (status >= 0) - status = (status == 2 ? 0 : -ENODEV); + if (status == 0) { + status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus); + if (status >= 0) + status = (status == 2 ? 0 : -ENODEV); + } if (status) { dev_dbg(&udev->dev, "gone after usb resume? status %d\n", @@ -1735,6 +1689,28 @@ static int finish_port_resume(struct usb_device *udev) * the host and the device is the same as it was when the device * suspended. * + * If CONFIG_USB_PERSIST and @udev->reset_resume are both set then this + * routine won't check that the port is still enabled. Furthermore, + * if @udev->reset_resume is set then finish_port_resume() above will + * reset @udev. The end result is that a broken power session can be + * recovered and @udev will appear to persist across a loss of VBUS power. + * + * For example, if a host controller doesn't maintain VBUS suspend current + * during a system sleep or is reset when the system wakes up, all the USB + * power sessions below it will be broken. This is especially troublesome + * for mass-storage devices containing mounted filesystems, since the + * device will appear to have disconnected and all the memory mappings + * to it will be lost. Using the USB_PERSIST facility, the device can be + * made to appear as if it had not disconnected. + * + * This facility is inherently dangerous. Although usb_reset_device() + * makes every effort to insure that the same device is present after the + * reset as before, it cannot provide a 100% guarantee. Furthermore it's + * quite possible for a device to remain unaltered but its media to be + * changed. If the user replaces a flash memory card while the system is + * asleep, he will have only himself to blame when the filesystem on the + * new card is corrupted and the system crashes. + * * Returns 0 on success, else negative errno. */ int usb_port_resume(struct usb_device *udev) @@ -1743,6 +1719,7 @@ int usb_port_resume(struct usb_device *udev) int port1 = udev->portnum; int status; u16 portchange, portstatus; + unsigned mask_flags, want_flags; /* Skip the initial Clear-Suspend step for a remote wakeup */ status = hub_port_status(hub, port1, &portstatus, &portchange); @@ -1765,20 +1742,23 @@ int usb_port_resume(struct usb_device *udev) udev->auto_pm ? "auto-" : ""); msleep(25); -#define LIVE_FLAGS ( USB_PORT_STAT_POWER \ - | USB_PORT_STAT_ENABLE \ - | USB_PORT_STAT_CONNECTION) - /* Virtual root hubs can trigger on GET_PORT_STATUS to * stop resume signaling. Then finish the resume * sequence. */ status = hub_port_status(hub, port1, &portstatus, &portchange); -SuspendCleared: - if (status < 0 - || (portstatus & LIVE_FLAGS) != LIVE_FLAGS - || (portstatus & USB_PORT_STAT_SUSPEND) != 0 - ) { + + SuspendCleared: + if (USB_PERSIST && udev->reset_resume) + want_flags = USB_PORT_STAT_POWER + | USB_PORT_STAT_CONNECTION; + else + want_flags = USB_PORT_STAT_POWER + | USB_PORT_STAT_CONNECTION + | USB_PORT_STAT_ENABLE; + mask_flags = want_flags | USB_PORT_STAT_SUSPEND; + + if (status < 0 || (portstatus & mask_flags) != want_flags) { dev_dbg(hub->intfdev, "port %d status %04x.%04x after resume, %d\n", port1, portchange, portstatus, status); @@ -1790,18 +1770,19 @@ SuspendCleared: USB_PORT_FEAT_C_SUSPEND); /* TRSMRCY = 10 msec */ msleep(10); - status = finish_port_resume(udev); } } - if (status < 0) { - dev_dbg(&udev->dev, "can't resume, status %d\n", status); - hub_port_logical_disconnect(hub, port1); - } clear_bit(port1, hub->busy_bits); if (!hub->hdev->parent && !hub->busy_bits[0]) usb_enable_root_hub_irq(hub->hdev->bus); + if (status == 0) + status = finish_port_resume(udev); + if (status < 0) { + dev_dbg(&udev->dev, "can't resume, status %d\n", status); + hub_port_logical_disconnect(hub, port1); + } return status; } @@ -1830,7 +1811,14 @@ int usb_port_suspend(struct usb_device *udev) int usb_port_resume(struct usb_device *udev) { - return 0; + int status = 0; + + /* However we may need to do a reset-resume */ + if (udev->reset_resume) { + dev_dbg(&udev->dev, "reset-resume\n"); + status = usb_reset_device(udev); + } + return status; } static inline int remote_wakeup(struct usb_device *udev) @@ -1886,8 +1874,6 @@ static int hub_resume(struct usb_interface *intf) #ifdef CONFIG_USB_PERSIST -#define USB_PERSIST 1 - /* For "persistent-device" resets we must mark the child devices for reset * and turn off a possible connect-change status (so khubd won't disconnect * them later). @@ -1910,8 +1896,6 @@ static void mark_children_for_reset_resume(struct usb_hub *hub) #else -#define USB_PERSIST 0 - static inline void mark_children_for_reset_resume(struct usb_hub *hub) { } @@ -1936,6 +1920,24 @@ static int hub_reset_resume(struct usb_interface *intf) return 0; } +/** + * usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power + * @rhdev: struct usb_device for the root hub + * + * The USB host controller driver calls this function when its root hub + * is resumed and Vbus power has been interrupted or the controller + * has been reset. The routine marks @rhdev as having lost power. When + * the hub driver is resumed it will take notice; if CONFIG_USB_PERSIST + * is enabled then it will carry out power-session recovery, otherwise + * it will disconnect all the child devices. + */ +void usb_root_hub_lost_power(struct usb_device *rhdev) +{ + dev_warn(&rhdev->dev, "root hub lost power or was reset\n"); + rhdev->reset_resume = 1; +} +EXPORT_SYMBOL_GPL(usb_root_hub_lost_power); + #else /* CONFIG_PM */ static inline int remote_wakeup(struct usb_device *udev) diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index a547499c45f..ad5fa0338f4 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -36,7 +36,6 @@ extern void usb_host_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); -extern int usb_reset_suspended_device(struct usb_device *udev); extern int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg); extern int usb_external_resume_device(struct usb_device *udev); |