aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2008-04-28 11:06:55 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2008-07-21 18:15:51 -0400
commit8808f00c7adfc8dc0b797c34ec03490b237fce4e (patch)
tree0062a4de8f9957faa51b96bb17351c3ca48c41a1 /drivers/usb/core
parent6ee0b270c733027b2b716b1c80b9aced41e08d20 (diff)
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: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/driver.c13
-rw-r--r--drivers/usb/core/hub.c62
2 files changed, 53 insertions, 22 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index bf1585b203ca..0a0e8cea0afc 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1537,14 +1537,11 @@ static int usb_resume(struct device *dev)
1537 udev = to_usb_device(dev); 1537 udev = to_usb_device(dev);
1538 1538
1539 /* If udev->skip_sys_resume is set then udev was already suspended 1539 /* If udev->skip_sys_resume is set then udev was already suspended
1540 * when the system suspend started, so we don't want to resume 1540 * when the system sleep started, so we don't want to resume it
1541 * udev during this system wakeup. However a reset-resume counts 1541 * during this system wakeup.
1542 * as a wakeup event, so allow a reset-resume to occur if remote 1542 */
1543 * wakeup is enabled. */ 1543 if (udev->skip_sys_resume)
1544 if (udev->skip_sys_resume) { 1544 return 0;
1545 if (!(udev->reset_resume && udev->do_remote_wakeup))
1546 return -EHOSTUNREACH;
1547 }
1548 return usb_external_resume_device(udev); 1545 return usb_external_resume_device(udev);
1549} 1546}
1550 1547
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 054a76dc5d5b..8ea095e59099 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -690,18 +690,11 @@ static void hub_restart(struct usb_hub *hub, enum hub_activation_type type)
690 set_bit(port1, hub->change_bits); 690 set_bit(port1, hub->change_bits);
691 691
692 } else if (udev->persist_enabled) { 692 } else if (udev->persist_enabled) {
693 /* Turn off the status changes to prevent khubd
694 * from disconnecting the device.
695 */
696 if (portchange & USB_PORT_STAT_C_ENABLE)
697 clear_port_feature(hub->hdev, port1,
698 USB_PORT_FEAT_C_ENABLE);
699 if (portchange & USB_PORT_STAT_C_CONNECTION)
700 clear_port_feature(hub->hdev, port1,
701 USB_PORT_FEAT_C_CONNECTION);
702#ifdef CONFIG_PM 693#ifdef CONFIG_PM
703 udev->reset_resume = 1; 694 udev->reset_resume = 1;
704#endif 695#endif
696 set_bit(port1, hub->change_bits);
697
705 } else { 698 } else {
706 /* The power session is gone; tell khubd */ 699 /* The power session is gone; tell khubd */
707 usb_set_device_state(udev, USB_STATE_NOTATTACHED); 700 usb_set_device_state(udev, USB_STATE_NOTATTACHED);
@@ -2075,17 +2068,16 @@ int usb_port_resume(struct usb_device *udev)
2075 return status; 2068 return status;
2076} 2069}
2077 2070
2071/* caller has locked udev */
2078static int remote_wakeup(struct usb_device *udev) 2072static int remote_wakeup(struct usb_device *udev)
2079{ 2073{
2080 int status = 0; 2074 int status = 0;
2081 2075
2082 usb_lock_device(udev);
2083 if (udev->state == USB_STATE_SUSPENDED) { 2076 if (udev->state == USB_STATE_SUSPENDED) {
2084 dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-"); 2077 dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-");
2085 usb_mark_last_busy(udev); 2078 usb_mark_last_busy(udev);
2086 status = usb_external_resume_device(udev); 2079 status = usb_external_resume_device(udev);
2087 } 2080 }
2088 usb_unlock_device(udev);
2089 return status; 2081 return status;
2090} 2082}
2091 2083
@@ -2632,6 +2624,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
2632 struct usb_hcd *hcd = bus_to_hcd(hdev->bus); 2624 struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
2633 unsigned wHubCharacteristics = 2625 unsigned wHubCharacteristics =
2634 le16_to_cpu(hub->descriptor->wHubCharacteristics); 2626 le16_to_cpu(hub->descriptor->wHubCharacteristics);
2627 struct usb_device *udev;
2635 int status, i; 2628 int status, i;
2636 2629
2637 dev_dbg (hub_dev, 2630 dev_dbg (hub_dev,
@@ -2666,8 +2659,45 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
2666 } 2659 }
2667 } 2660 }
2668 2661
2662 /* Try to resuscitate an existing device */
2663 udev = hdev->children[port1-1];
2664 if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
2665 udev->state != USB_STATE_NOTATTACHED) {
2666
2667 usb_lock_device(udev);
2668 if (portstatus & USB_PORT_STAT_ENABLE) {
2669 status = 0; /* Nothing to do */
2670 } else if (!udev->persist_enabled) {
2671 status = -ENODEV; /* Mustn't resuscitate */
2672
2673#ifdef CONFIG_USB_SUSPEND
2674 } else if (udev->state == USB_STATE_SUSPENDED) {
2675 /* For a suspended device, treat this as a
2676 * remote wakeup event.
2677 */
2678 if (udev->do_remote_wakeup)
2679 status = remote_wakeup(udev);
2680
2681 /* Otherwise leave it be; devices can't tell the
2682 * difference between suspended and disabled.
2683 */
2684 else
2685 status = 0;
2686#endif
2687
2688 } else {
2689 status = usb_reset_composite_device(udev, NULL);
2690 }
2691 usb_unlock_device(udev);
2692
2693 if (status == 0) {
2694 clear_bit(port1, hub->change_bits);
2695 return;
2696 }
2697 }
2698
2669 /* Disconnect any existing devices under this port */ 2699 /* Disconnect any existing devices under this port */
2670 if (hdev->children[port1-1]) 2700 if (udev)
2671 usb_disconnect(&hdev->children[port1-1]); 2701 usb_disconnect(&hdev->children[port1-1]);
2672 clear_bit(port1, hub->change_bits); 2702 clear_bit(port1, hub->change_bits);
2673 2703
@@ -2685,7 +2715,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
2685 } 2715 }
2686 2716
2687 for (i = 0; i < SET_CONFIG_TRIES; i++) { 2717 for (i = 0; i < SET_CONFIG_TRIES; i++) {
2688 struct usb_device *udev;
2689 2718
2690 /* reallocate for each attempt, since references 2719 /* reallocate for each attempt, since references
2691 * to the previous one can escape in various ways 2720 * to the previous one can escape in various ways
@@ -2944,11 +2973,16 @@ static void hub_events(void)
2944 } 2973 }
2945 2974
2946 if (portchange & USB_PORT_STAT_C_SUSPEND) { 2975 if (portchange & USB_PORT_STAT_C_SUSPEND) {
2976 struct usb_device *udev;
2977
2947 clear_port_feature(hdev, i, 2978 clear_port_feature(hdev, i,
2948 USB_PORT_FEAT_C_SUSPEND); 2979 USB_PORT_FEAT_C_SUSPEND);
2949 if (hdev->children[i-1]) { 2980 udev = hdev->children[i-1];
2981 if (udev) {
2982 usb_lock_device(udev);
2950 ret = remote_wakeup(hdev-> 2983 ret = remote_wakeup(hdev->
2951 children[i-1]); 2984 children[i-1]);
2985 usb_unlock_device(udev);
2952 if (ret < 0) 2986 if (ret < 0)
2953 connect_change = 1; 2987 connect_change = 1;
2954 } else { 2988 } else {