diff options
author | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2012-01-24 14:46:50 -0500 |
---|---|---|
committer | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2012-02-14 15:12:27 -0500 |
commit | 72937e1e342f5631d08df4ef0629e55bdcf74c76 (patch) | |
tree | 3e8138fbb3746f0c1d7112f67c06b0d424be050a /drivers/usb | |
parent | 4ee823b83bc9851743fab756c76b27d6a1e2472b (diff) |
USB: Set wakeup bits for all children hubs.
This patch takes care of the race condition between the Function Wake
Device Notification and the auto-suspend timeout for this situation:
Roothub
| (U3)
hub A
| (U3)
hub B
| (U3)
device C
When device C signals a resume, the xHCI driver will set the wakeup_bits
for the roothub port that hub A is attached to. However, since USB 3.0
hubs do not set a link state change bit on device-initiated resume, hub
A will not indicate a port event when polled. Without this patch, khubd
will notice the wakeup-bits are set for the roothub port, it will resume
hub A, and then it will poll the events bits for hub A and notice that
nothing has changed. Then it will be suspended after 2 seconds.
Change hub_activate() to look at the port link state for each USB 3.0
hub port, and set hub->change_bits if the link state is U0, indicating
the device has finished resume. Change the resume function called by
hub_events(), hub_handle_remote_wakeup(), to check the link status
for resume instead of just the port's wakeup_bits.
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/hub.c | 22 |
1 files changed, 15 insertions, 7 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 1c32bbac9862..994aa8853bac 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -853,12 +853,19 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) | |||
853 | set_bit(port1, hub->change_bits); | 853 | set_bit(port1, hub->change_bits); |
854 | 854 | ||
855 | } else if (portstatus & USB_PORT_STAT_ENABLE) { | 855 | } else if (portstatus & USB_PORT_STAT_ENABLE) { |
856 | bool port_resumed = (portstatus & | ||
857 | USB_PORT_STAT_LINK_STATE) == | ||
858 | USB_SS_PORT_LS_U0; | ||
856 | /* The power session apparently survived the resume. | 859 | /* The power session apparently survived the resume. |
857 | * If there was an overcurrent or suspend change | 860 | * If there was an overcurrent or suspend change |
858 | * (i.e., remote wakeup request), have khubd | 861 | * (i.e., remote wakeup request), have khubd |
859 | * take care of it. | 862 | * take care of it. Look at the port link state |
863 | * for USB 3.0 hubs, since they don't have a suspend | ||
864 | * change bit, and they don't set the port link change | ||
865 | * bit on device-initiated resume. | ||
860 | */ | 866 | */ |
861 | if (portchange) | 867 | if (portchange || (hub_is_superspeed(hub->hdev) && |
868 | port_resumed)) | ||
862 | set_bit(port1, hub->change_bits); | 869 | set_bit(port1, hub->change_bits); |
863 | 870 | ||
864 | } else if (udev->persist_enabled) { | 871 | } else if (udev->persist_enabled) { |
@@ -3509,7 +3516,7 @@ done: | |||
3509 | 3516 | ||
3510 | /* Returns 1 if there was a remote wakeup and a connect status change. */ | 3517 | /* Returns 1 if there was a remote wakeup and a connect status change. */ |
3511 | static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, | 3518 | static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, |
3512 | u16 portchange) | 3519 | u16 portstatus, u16 portchange) |
3513 | { | 3520 | { |
3514 | struct usb_device *hdev; | 3521 | struct usb_device *hdev; |
3515 | struct usb_device *udev; | 3522 | struct usb_device *udev; |
@@ -3524,8 +3531,8 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, | |||
3524 | clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND); | 3531 | clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND); |
3525 | } else { | 3532 | } else { |
3526 | if (!udev || udev->state != USB_STATE_SUSPENDED || | 3533 | if (!udev || udev->state != USB_STATE_SUSPENDED || |
3527 | !test_and_clear_bit(udev->portnum, | 3534 | (portstatus & USB_PORT_STAT_LINK_STATE) != |
3528 | hub->wakeup_bits)) | 3535 | USB_SS_PORT_LS_U0) |
3529 | return 0; | 3536 | return 0; |
3530 | } | 3537 | } |
3531 | 3538 | ||
@@ -3638,7 +3645,7 @@ static void hub_events(void) | |||
3638 | if (test_bit(i, hub->busy_bits)) | 3645 | if (test_bit(i, hub->busy_bits)) |
3639 | continue; | 3646 | continue; |
3640 | connect_change = test_bit(i, hub->change_bits); | 3647 | connect_change = test_bit(i, hub->change_bits); |
3641 | wakeup_change = test_bit(i, hub->wakeup_bits); | 3648 | wakeup_change = test_and_clear_bit(i, hub->wakeup_bits); |
3642 | if (!test_and_clear_bit(i, hub->event_bits) && | 3649 | if (!test_and_clear_bit(i, hub->event_bits) && |
3643 | !connect_change && !wakeup_change) | 3650 | !connect_change && !wakeup_change) |
3644 | continue; | 3651 | continue; |
@@ -3681,7 +3688,8 @@ static void hub_events(void) | |||
3681 | } | 3688 | } |
3682 | } | 3689 | } |
3683 | 3690 | ||
3684 | if (hub_handle_remote_wakeup(hub, i, portchange)) | 3691 | if (hub_handle_remote_wakeup(hub, i, |
3692 | portstatus, portchange)) | ||
3685 | connect_change = 1; | 3693 | connect_change = 1; |
3686 | 3694 | ||
3687 | if (portchange & USB_PORT_STAT_C_OVERCURRENT) { | 3695 | if (portchange & USB_PORT_STAT_C_OVERCURRENT) { |