diff options
| author | Dan Williams <dan.j.williams@intel.com> | 2014-05-29 15:58:46 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-07-09 18:43:12 -0400 |
| commit | 3cd12f91514da6893954de479dc60b16d3b381f4 (patch) | |
| tree | 3209b05551050ac2e1da7f79ca96bc49298f9ac9 /drivers/usb/core | |
| parent | 51df62ff74b371866c1006dee887a8e42838c1f2 (diff) | |
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>
Diffstat (limited to 'drivers/usb/core')
| -rw-r--r-- | drivers/usb/core/hub.c | 36 | ||||
| -rw-r--r-- | drivers/usb/core/hub.h | 2 | ||||
| -rw-r--r-- | drivers/usb/core/port.c | 21 |
3 files changed, 39 insertions, 20 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b90c6287bf47..88f1db27e0af 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
| @@ -2587,13 +2587,20 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
| 2587 | /* Is a USB 3.0 port in the Inactive or Compliance Mode state? | 2587 | /* Is a USB 3.0 port in the Inactive or Compliance Mode state? |
| 2588 | * Port worm reset is required to recover | 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 | u16 link_state; |
| 2593 | (((portstatus & USB_PORT_STAT_LINK_STATE) == | 2594 | |
| 2594 | USB_SS_PORT_LS_SS_INACTIVE) || | 2595 | if (!hub_is_superspeed(hub->hdev)) |
| 2595 | ((portstatus & USB_PORT_STAT_LINK_STATE) == | 2596 | return false; |
| 2596 | USB_SS_PORT_LS_COMP_MOD)) ; | 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 | static int hub_port_wait_reset(struct usb_hub *hub, int port1, | 2606 | static int hub_port_wait_reset(struct usb_hub *hub, int port1, |
| @@ -2630,7 +2637,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, | |||
| 2630 | if ((portstatus & USB_PORT_STAT_RESET)) | 2637 | if ((portstatus & USB_PORT_STAT_RESET)) |
| 2631 | return -EBUSY; | 2638 | return -EBUSY; |
| 2632 | 2639 | ||
| 2633 | if (hub_port_warm_reset_required(hub, portstatus)) | 2640 | if (hub_port_warm_reset_required(hub, port1, portstatus)) |
| 2634 | return -ENOTCONN; | 2641 | return -ENOTCONN; |
| 2635 | 2642 | ||
| 2636 | /* Device went away? */ | 2643 | /* Device went away? */ |
| @@ -2730,9 +2737,10 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
| 2730 | if (status < 0) | 2737 | if (status < 0) |
| 2731 | goto done; | 2738 | goto done; |
| 2732 | 2739 | ||
| 2733 | if (hub_port_warm_reset_required(hub, portstatus)) | 2740 | if (hub_port_warm_reset_required(hub, port1, portstatus)) |
| 2734 | warm = true; | 2741 | warm = true; |
| 2735 | } | 2742 | } |
| 2743 | clear_bit(port1, hub->warm_reset_bits); | ||
| 2736 | 2744 | ||
| 2737 | /* Reset the port */ | 2745 | /* Reset the port */ |
| 2738 | for (i = 0; i < PORT_RESET_TRIES; i++) { | 2746 | for (i = 0; i < PORT_RESET_TRIES; i++) { |
| @@ -2769,7 +2777,8 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
| 2769 | &portstatus, &portchange) < 0) | 2777 | &portstatus, &portchange) < 0) |
| 2770 | goto done; | 2778 | goto done; |
| 2771 | 2779 | ||
| 2772 | if (!hub_port_warm_reset_required(hub, portstatus)) | 2780 | if (!hub_port_warm_reset_required(hub, port1, |
| 2781 | portstatus)) | ||
| 2773 | goto done; | 2782 | goto done; |
| 2774 | 2783 | ||
| 2775 | /* | 2784 | /* |
| @@ -2856,8 +2865,13 @@ static int check_port_resume_type(struct usb_device *udev, | |||
| 2856 | { | 2865 | { |
| 2857 | struct usb_port *port_dev = hub->ports[port1 - 1]; | 2866 | struct usb_port *port_dev = hub->ports[port1 - 1]; |
| 2858 | 2867 | ||
| 2868 | /* Is a warm reset needed to recover the connection? */ | ||
| 2869 | if (status == 0 && udev->reset_resume | ||
| 2870 | && hub_port_warm_reset_required(hub, port1, portstatus)) { | ||
| 2871 | /* pass */; | ||
| 2872 | } | ||
| 2859 | /* Is the device still present? */ | 2873 | /* Is the device still present? */ |
| 2860 | if (status || port_is_suspended(hub, portstatus) || | 2874 | else if (status || port_is_suspended(hub, portstatus) || |
| 2861 | !port_is_power_on(hub, portstatus) || | 2875 | !port_is_power_on(hub, portstatus) || |
| 2862 | !(portstatus & USB_PORT_STAT_CONNECTION)) { | 2876 | !(portstatus & USB_PORT_STAT_CONNECTION)) { |
| 2863 | if (status >= 0) | 2877 | if (status >= 0) |
| @@ -4872,7 +4886,7 @@ static void port_event(struct usb_hub *hub, int port1) | |||
| 4872 | * Warm reset a USB3 protocol port if it's in | 4886 | * Warm reset a USB3 protocol port if it's in |
| 4873 | * SS.Inactive state. | 4887 | * SS.Inactive state. |
| 4874 | */ | 4888 | */ |
| 4875 | if (hub_port_warm_reset_required(hub, portstatus)) { | 4889 | if (hub_port_warm_reset_required(hub, port1, portstatus)) { |
| 4876 | dev_dbg(&port_dev->dev, "do warm reset\n"); | 4890 | dev_dbg(&port_dev->dev, "do warm reset\n"); |
| 4877 | if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) | 4891 | if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) |
| 4878 | || udev->state == USB_STATE_NOTATTACHED) { | 4892 | || udev->state == USB_STATE_NOTATTACHED) { |
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 326308e53961..c77d8778af4b 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h | |||
| @@ -52,6 +52,8 @@ struct usb_hub { | |||
| 52 | unsigned long power_bits[1]; /* ports that are powered */ | 52 | unsigned long power_bits[1]; /* ports that are powered */ |
| 53 | unsigned long child_usage_bits[1]; /* ports powered on for | 53 | unsigned long child_usage_bits[1]; /* ports powered on for |
| 54 | children */ | 54 | children */ |
| 55 | unsigned long warm_reset_bits[1]; /* ports requesting warm | ||
| 56 | reset recovery */ | ||
| 55 | #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ | 57 | #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ |
| 56 | #error event_bits[] is too short! | 58 | #error event_bits[] is too short! |
| 57 | #endif | 59 | #endif |
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index fe1b6d0967e3..cd3f9dc24a06 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c | |||
| @@ -103,16 +103,19 @@ static int usb_port_runtime_resume(struct device *dev) | |||
| 103 | msleep(hub_power_on_good_delay(hub)); | 103 | msleep(hub_power_on_good_delay(hub)); |
| 104 | if (udev && !retval) { | 104 | if (udev && !retval) { |
| 105 | /* | 105 | /* |
| 106 | * Attempt to wait for usb hub port to be reconnected in order | 106 | * Our preference is to simply wait for the port to reconnect, |
| 107 | * to make the resume procedure successful. The device may have | 107 | * as that is the lowest latency method to restart the port. |
| 108 | * disconnected while the port was powered off, so ignore the | 108 | * However, there are cases where toggling port power results in |
| 109 | * return status. | 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); | 114 | if (hub_port_debounce_be_connected(hub, port1) < 0) { |
| 112 | if (retval < 0) | 115 | dev_dbg(&port_dev->dev, "reconnect timeout\n"); |
| 113 | dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n", | 116 | if (hub_is_superspeed(hdev)) |
| 114 | retval); | 117 | set_bit(port1, hub->warm_reset_bits); |
| 115 | retval = 0; | 118 | } |
| 116 | 119 | ||
| 117 | /* Force the child awake to revalidate after the power loss. */ | 120 | /* Force the child awake to revalidate after the power loss. */ |
| 118 | if (!test_and_set_bit(port1, hub->child_usage_bits)) { | 121 | if (!test_and_set_bit(port1, hub->child_usage_bits)) { |
