USB 3.0 hubs don't have a port suspend change bit (that bit is now reserved). Instead, when a port resumes, the hub sets the port link state change bit. (An xHCI roothub does have a resume change bit, but we don't pass it up to the USB core because it's different behavior from external USB 3.0 hubs.) The xHCI driver was initially trying to sweep the port resume under the rug, by setting the link state to U0 when a resume was detected, clearing the link state change, and asking the hub driver to poll the roothub. That works fine if there's a resuming USB 3.0 device attached to the roothub. However, if the resuming device is behind an external USB 3.0 hub, khubd looks at the root ports, notices there's no new changes to the port status bits, and then suspends the USB 3.0 hub again. Change the xHCI driver and hub port initialization to pass on the port link state change to hub_events(). Make sure that function will treat the hub as having children that need resumed if the link state change bit is set, just like it would if a USB 2.0 hub had the port suspend change bit set. There are specific error cases where the xHCI roothub will set PLC, but I don't think that will cause issues. XXX: We could clear PLC in hub_activate() when hub_activate() is called for a hub resume only (HUB_RESUME). I'm not sure about the port state machine here. Alan? Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> Cc: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx> --- drivers/usb/core/hub.c | 24 +++++++++++++----------- drivers/usb/host/xhci-ring.c | 3 --- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 39f3e4c..3ad7e89 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -807,11 +807,11 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_ENABLE); } - if (portchange & USB_PORT_STAT_C_LINK_STATE) { + /* We can't clear the link state change here because it's the + * only indication we have a resume on a port! + */ + if (portchange & USB_PORT_STAT_C_LINK_STATE) need_debounce_delay = true; - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_C_PORT_LINK_STATE); - } if ((portchange & USB_PORT_STAT_C_BH_RESET) && hub_is_superspeed(hub->hdev)) { @@ -3569,11 +3569,17 @@ static void hub_events(void) } } - if (portchange & USB_PORT_STAT_C_SUSPEND) { + if ((portchange & USB_PORT_STAT_C_SUSPEND) || + (hub_is_superspeed(hdev) && + (portchange & USB_PORT_STAT_C_LINK_STATE))) { struct usb_device *udev; - clear_port_feature(hdev, i, - USB_PORT_FEAT_C_SUSPEND); + if (!hub_is_superspeed(hdev)) + clear_port_feature(hdev, i, + USB_PORT_FEAT_C_SUSPEND); + else + clear_port_feature(hdev, i, + USB_PORT_FEAT_C_PORT_LINK_STATE); udev = hdev->children[i-1]; if (udev) { /* TRSMRCY = 10 msec */ @@ -3625,10 +3631,6 @@ static void hub_events(void) clear_port_feature(hdev, i, USB_PORT_FEAT_C_BH_PORT_RESET); } - if (portchange & USB_PORT_STAT_C_LINK_STATE) { - clear_port_feature(hub->hdev, i, - USB_PORT_FEAT_C_PORT_LINK_STATE); - } if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) { dev_warn(hub_dev, "config error on port %d\n", diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1ef2bf1..d532f0c 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1350,9 +1350,6 @@ static void handle_port_status(struct xhci_hcd *xhci, } xhci_ring_device(xhci, slot_id); xhci_dbg(xhci, "resume SS port %d finished\n", port_id); - /* Clear PORT_PLC */ - xhci_test_and_clear_bit(xhci, port_array, - faked_port_index, PORT_PLC); } else { xhci_dbg(xhci, "resume HS port %d\n", port_id); bus_state->resume_done[faked_port_index] = jiffies + -- 1.7.5.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html