Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: force warm reset to break link re-connect livelock

Resuming a powered down port sometimes results in the port state being
stuck in the training sequence.

hub 3-0:1.0: debounce: port 1: total 2000ms stable 0ms status 0x2e0
port1: can't get reconnection after setting port power on, status -110
hub 3-0:1.0: port 1 status 0000.02e0 after resume, -19
usb 3-1: can't resume, status -19
hub 3-0:1.0: logical disconnect on port 1

In the case above we wait for the port re-connect timeout of 2 seconds
and observe that the port status is USB_SS_PORT_LS_POLLING (although it
is likely toggling between this state and USB_SS_PORT_LS_RX_DETECT).
This is indicative of a case where the device is failing to progress the
link training state machine.

It is resolved by issuing a warm reset to get the hub and device link
state machines back in sync.

hub 3-0:1.0: debounce: port 1: total 2000ms stable 0ms status 0x2e0
usb usb3: port1 usb_port_runtime_resume requires warm reset
hub 3-0:1.0: port 1 not warm reset yet, waiting 50ms
usb 3-1: reset SuperSpeed USB device number 2 using xhci_hcd

After a reconnect timeout when we expect the device to be present, force
a warm reset of the device. Note that we can not simply look at the
link status to determine if a warm reset is required as any of the
training states USB_SS_PORT_LS_POLLING, USB_SS_PORT_LS_RX_DETECT, or
USB_SS_PORT_LS_COMP_MOD are valid states that do not indicate the need
for warm reset by themselves.

Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Cc: Vincent Palatin <vpalatin@chromium.org>
Cc: Lan Tianyu <tianyu.lan@intel.com>
Cc: Ksenia Ragiadakou <burzalodowa@gmail.com>
Cc: Vivek Gautam <gautam.vivek@samsung.com>
Cc: Douglas Anderson <dianders@chromium.org>
Cc: Felipe Balbi <balbi@ti.com>
Cc: Sunil Joshi <joshi@samsung.com>
Cc: Hans de Goede <hdegoede@redhat.com>
Acked-by: Julius Werner <jwerner@chromium.org>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Dan Williams and committed by
Greg Kroah-Hartman
3cd12f91 51df62ff

+39 -20
+25 -11
drivers/usb/core/hub.c
··· 2587 2587 /* Is a USB 3.0 port in the Inactive or Compliance Mode state? 2588 2588 * Port worm reset is required to recover 2589 2589 */ 2590 - static bool hub_port_warm_reset_required(struct usb_hub *hub, u16 portstatus) 2590 + static bool hub_port_warm_reset_required(struct usb_hub *hub, int port1, 2591 + u16 portstatus) 2591 2592 { 2592 - return hub_is_superspeed(hub->hdev) && 2593 - (((portstatus & USB_PORT_STAT_LINK_STATE) == 2594 - USB_SS_PORT_LS_SS_INACTIVE) || 2595 - ((portstatus & USB_PORT_STAT_LINK_STATE) == 2596 - USB_SS_PORT_LS_COMP_MOD)) ; 2593 + u16 link_state; 2594 + 2595 + if (!hub_is_superspeed(hub->hdev)) 2596 + return false; 2597 + 2598 + if (test_bit(port1, hub->warm_reset_bits)) 2599 + return true; 2600 + 2601 + link_state = portstatus & USB_PORT_STAT_LINK_STATE; 2602 + return link_state == USB_SS_PORT_LS_SS_INACTIVE 2603 + || link_state == USB_SS_PORT_LS_COMP_MOD; 2597 2604 } 2598 2605 2599 2606 static int hub_port_wait_reset(struct usb_hub *hub, int port1, ··· 2637 2630 if ((portstatus & USB_PORT_STAT_RESET)) 2638 2631 return -EBUSY; 2639 2632 2640 - if (hub_port_warm_reset_required(hub, portstatus)) 2633 + if (hub_port_warm_reset_required(hub, port1, portstatus)) 2641 2634 return -ENOTCONN; 2642 2635 2643 2636 /* Device went away? */ ··· 2737 2730 if (status < 0) 2738 2731 goto done; 2739 2732 2740 - if (hub_port_warm_reset_required(hub, portstatus)) 2733 + if (hub_port_warm_reset_required(hub, port1, portstatus)) 2741 2734 warm = true; 2742 2735 } 2736 + clear_bit(port1, hub->warm_reset_bits); 2743 2737 2744 2738 /* Reset the port */ 2745 2739 for (i = 0; i < PORT_RESET_TRIES; i++) { ··· 2777 2769 &portstatus, &portchange) < 0) 2778 2770 goto done; 2779 2771 2780 - if (!hub_port_warm_reset_required(hub, portstatus)) 2772 + if (!hub_port_warm_reset_required(hub, port1, 2773 + portstatus)) 2781 2774 goto done; 2782 2775 2783 2776 /* ··· 2865 2856 { 2866 2857 struct usb_port *port_dev = hub->ports[port1 - 1]; 2867 2858 2859 + /* Is a warm reset needed to recover the connection? */ 2860 + if (status == 0 && udev->reset_resume 2861 + && hub_port_warm_reset_required(hub, port1, portstatus)) { 2862 + /* pass */; 2863 + } 2868 2864 /* Is the device still present? */ 2869 - if (status || port_is_suspended(hub, portstatus) || 2865 + else if (status || port_is_suspended(hub, portstatus) || 2870 2866 !port_is_power_on(hub, portstatus) || 2871 2867 !(portstatus & USB_PORT_STAT_CONNECTION)) { 2872 2868 if (status >= 0) ··· 4886 4872 * Warm reset a USB3 protocol port if it's in 4887 4873 * SS.Inactive state. 4888 4874 */ 4889 - if (hub_port_warm_reset_required(hub, portstatus)) { 4875 + if (hub_port_warm_reset_required(hub, port1, portstatus)) { 4890 4876 dev_dbg(&port_dev->dev, "do warm reset\n"); 4891 4877 if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) 4892 4878 || udev->state == USB_STATE_NOTATTACHED) {
+2
drivers/usb/core/hub.h
··· 52 52 unsigned long power_bits[1]; /* ports that are powered */ 53 53 unsigned long child_usage_bits[1]; /* ports powered on for 54 54 children */ 55 + unsigned long warm_reset_bits[1]; /* ports requesting warm 56 + reset recovery */ 55 57 #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ 56 58 #error event_bits[] is too short! 57 59 #endif
+12 -9
drivers/usb/core/port.c
··· 103 103 msleep(hub_power_on_good_delay(hub)); 104 104 if (udev && !retval) { 105 105 /* 106 - * Attempt to wait for usb hub port to be reconnected in order 107 - * to make the resume procedure successful. The device may have 108 - * disconnected while the port was powered off, so ignore the 109 - * return status. 106 + * Our preference is to simply wait for the port to reconnect, 107 + * as that is the lowest latency method to restart the port. 108 + * However, there are cases where toggling port power results in 109 + * the host port and the device port getting out of sync causing 110 + * a link training live lock. Upon timeout, flag the port as 111 + * needing warm reset recovery (to be performed later by 112 + * usb_port_resume() as requested via usb_wakeup_notification()) 110 113 */ 111 - retval = hub_port_debounce_be_connected(hub, port1); 112 - if (retval < 0) 113 - dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n", 114 - retval); 115 - retval = 0; 114 + if (hub_port_debounce_be_connected(hub, port1) < 0) { 115 + dev_dbg(&port_dev->dev, "reconnect timeout\n"); 116 + if (hub_is_superspeed(hdev)) 117 + set_bit(port1, hub->warm_reset_bits); 118 + } 116 119 117 120 /* Force the child awake to revalidate after the power loss. */ 118 121 if (!test_and_set_bit(port1, hub->child_usage_bits)) {