Skip to content

Commit

Permalink
USB: Set wakeup bits for all children hubs.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Sarah Sharp committed Feb 14, 2012
1 parent 4ee823b commit 72937e1
Showing 1 changed file with 15 additions and 7 deletions.
22 changes: 15 additions & 7 deletions drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -853,12 +853,19 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
set_bit(port1, hub->change_bits);

} else if (portstatus & USB_PORT_STAT_ENABLE) {
bool port_resumed = (portstatus &
USB_PORT_STAT_LINK_STATE) ==
USB_SS_PORT_LS_U0;
/* The power session apparently survived the resume.
* If there was an overcurrent or suspend change
* (i.e., remote wakeup request), have khubd
* take care of it.
* take care of it. Look at the port link state
* for USB 3.0 hubs, since they don't have a suspend
* change bit, and they don't set the port link change
* bit on device-initiated resume.
*/
if (portchange)
if (portchange || (hub_is_superspeed(hub->hdev) &&
port_resumed))
set_bit(port1, hub->change_bits);

} else if (udev->persist_enabled) {
Expand Down Expand Up @@ -3509,7 +3516,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,

/* Returns 1 if there was a remote wakeup and a connect status change. */
static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
u16 portchange)
u16 portstatus, u16 portchange)
{
struct usb_device *hdev;
struct usb_device *udev;
Expand All @@ -3524,8 +3531,8 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
} else {
if (!udev || udev->state != USB_STATE_SUSPENDED ||
!test_and_clear_bit(udev->portnum,
hub->wakeup_bits))
(portstatus & USB_PORT_STAT_LINK_STATE) !=
USB_SS_PORT_LS_U0)
return 0;
}

Expand Down Expand Up @@ -3638,7 +3645,7 @@ static void hub_events(void)
if (test_bit(i, hub->busy_bits))
continue;
connect_change = test_bit(i, hub->change_bits);
wakeup_change = test_bit(i, hub->wakeup_bits);
wakeup_change = test_and_clear_bit(i, hub->wakeup_bits);
if (!test_and_clear_bit(i, hub->event_bits) &&
!connect_change && !wakeup_change)
continue;
Expand Down Expand Up @@ -3681,7 +3688,8 @@ static void hub_events(void)
}
}

if (hub_handle_remote_wakeup(hub, i, portchange))
if (hub_handle_remote_wakeup(hub, i,
portstatus, portchange))
connect_change = 1;

if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
Expand Down

0 comments on commit 72937e1

Please sign in to comment.