Commit 8808f00c authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: try to salvage lost power sessions

This patch (as1073) adds to khubd a way to recover from power-session
interruption caused by transient connect-change or enable-change
events.  After the debouncing period, khubd attempts to do a
USB-Persist-style reset or reset-resume.  If it works, the connection
will remain unscathed.

The upshot is that we will be more immune to noise caused by EMI.  The
grace period is on the order of 100 ms, so this won't permit recovery
from the "accidentally knocked the USB cable out of its socket" type
of event, but it's a start.

As an added bonus, if a device was suspended when the system goes to
sleep then we no longer need to check for power-session interruptions
when the system wakes up.  Khubd will naturally see the status change
while processing the device's parent hub and will do the right thing.

The remote_wakeup() routine is changed; now it expects the caller to
acquire the device lock rather than acquiring the lock itself.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 6ee0b270
...@@ -1537,14 +1537,11 @@ static int usb_resume(struct device *dev) ...@@ -1537,14 +1537,11 @@ static int usb_resume(struct device *dev)
udev = to_usb_device(dev); udev = to_usb_device(dev);
/* If udev->skip_sys_resume is set then udev was already suspended /* If udev->skip_sys_resume is set then udev was already suspended
* when the system suspend started, so we don't want to resume * when the system sleep started, so we don't want to resume it
* udev during this system wakeup. However a reset-resume counts * during this system wakeup.
* as a wakeup event, so allow a reset-resume to occur if remote */
* wakeup is enabled. */ if (udev->skip_sys_resume)
if (udev->skip_sys_resume) { return 0;
if (!(udev->reset_resume && udev->do_remote_wakeup))
return -EHOSTUNREACH;
}
return usb_external_resume_device(udev); return usb_external_resume_device(udev);
} }
......
...@@ -690,18 +690,11 @@ static void hub_restart(struct usb_hub *hub, enum hub_activation_type type) ...@@ -690,18 +690,11 @@ static void hub_restart(struct usb_hub *hub, enum hub_activation_type type)
set_bit(port1, hub->change_bits); set_bit(port1, hub->change_bits);
} else if (udev->persist_enabled) { } else if (udev->persist_enabled) {
/* Turn off the status changes to prevent khubd
* from disconnecting the device.
*/
if (portchange & USB_PORT_STAT_C_ENABLE)
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_ENABLE);
if (portchange & USB_PORT_STAT_C_CONNECTION)
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_CONNECTION);
#ifdef CONFIG_PM #ifdef CONFIG_PM
udev->reset_resume = 1; udev->reset_resume = 1;
#endif #endif
set_bit(port1, hub->change_bits);
} else { } else {
/* The power session is gone; tell khubd */ /* The power session is gone; tell khubd */
usb_set_device_state(udev, USB_STATE_NOTATTACHED); usb_set_device_state(udev, USB_STATE_NOTATTACHED);
...@@ -2075,17 +2068,16 @@ int usb_port_resume(struct usb_device *udev) ...@@ -2075,17 +2068,16 @@ int usb_port_resume(struct usb_device *udev)
return status; return status;
} }
/* caller has locked udev */
static int remote_wakeup(struct usb_device *udev) static int remote_wakeup(struct usb_device *udev)
{ {
int status = 0; int status = 0;
usb_lock_device(udev);
if (udev->state == USB_STATE_SUSPENDED) { if (udev->state == USB_STATE_SUSPENDED) {
dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-"); dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-");
usb_mark_last_busy(udev); usb_mark_last_busy(udev);
status = usb_external_resume_device(udev); status = usb_external_resume_device(udev);
} }
usb_unlock_device(udev);
return status; return status;
} }
...@@ -2632,6 +2624,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, ...@@ -2632,6 +2624,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
struct usb_hcd *hcd = bus_to_hcd(hdev->bus); struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
unsigned wHubCharacteristics = unsigned wHubCharacteristics =
le16_to_cpu(hub->descriptor->wHubCharacteristics); le16_to_cpu(hub->descriptor->wHubCharacteristics);
struct usb_device *udev;
int status, i; int status, i;
dev_dbg (hub_dev, dev_dbg (hub_dev,
...@@ -2666,8 +2659,45 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, ...@@ -2666,8 +2659,45 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
} }
} }
/* Try to resuscitate an existing device */
udev = hdev->children[port1-1];
if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
udev->state != USB_STATE_NOTATTACHED) {
usb_lock_device(udev);
if (portstatus & USB_PORT_STAT_ENABLE) {
status = 0; /* Nothing to do */
} else if (!udev->persist_enabled) {
status = -ENODEV; /* Mustn't resuscitate */
#ifdef CONFIG_USB_SUSPEND
} else if (udev->state == USB_STATE_SUSPENDED) {
/* For a suspended device, treat this as a
* remote wakeup event.
*/
if (udev->do_remote_wakeup)
status = remote_wakeup(udev);
/* Otherwise leave it be; devices can't tell the
* difference between suspended and disabled.
*/
else
status = 0;
#endif
} else {
status = usb_reset_composite_device(udev, NULL);
}
usb_unlock_device(udev);
if (status == 0) {
clear_bit(port1, hub->change_bits);
return;
}
}
/* Disconnect any existing devices under this port */ /* Disconnect any existing devices under this port */
if (hdev->children[port1-1]) if (udev)
usb_disconnect(&hdev->children[port1-1]); usb_disconnect(&hdev->children[port1-1]);
clear_bit(port1, hub->change_bits); clear_bit(port1, hub->change_bits);
...@@ -2685,7 +2715,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, ...@@ -2685,7 +2715,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
} }
for (i = 0; i < SET_CONFIG_TRIES; i++) { for (i = 0; i < SET_CONFIG_TRIES; i++) {
struct usb_device *udev;
/* reallocate for each attempt, since references /* reallocate for each attempt, since references
* to the previous one can escape in various ways * to the previous one can escape in various ways
...@@ -2944,11 +2973,16 @@ static void hub_events(void) ...@@ -2944,11 +2973,16 @@ static void hub_events(void)
} }
if (portchange & USB_PORT_STAT_C_SUSPEND) { if (portchange & USB_PORT_STAT_C_SUSPEND) {
struct usb_device *udev;
clear_port_feature(hdev, i, clear_port_feature(hdev, i,
USB_PORT_FEAT_C_SUSPEND); USB_PORT_FEAT_C_SUSPEND);
if (hdev->children[i-1]) { udev = hdev->children[i-1];
if (udev) {
usb_lock_device(udev);
ret = remote_wakeup(hdev-> ret = remote_wakeup(hdev->
children[i-1]); children[i-1]);
usb_unlock_device(udev);
if (ret < 0) if (ret < 0)
connect_change = 1; connect_change = 1;
} else { } else {
......
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