diff mbox series

usb: hub: avoid warm port reset during USB3 disconnect

Message ID 20211210111653.1378381-1-mathias.nyman@linux.intel.com
State New
Headers show
Series usb: hub: avoid warm port reset during USB3 disconnect | expand

Commit Message

Mathias Nyman Dec. 10, 2021, 11:16 a.m. UTC
During disconnect USB-3 ports often go via SS.Inactive link error state
before the missing terminations are noticed, and link finally goes to
RxDetect state

Avoid immediately warm-resetting ports in SS.Inactive state.
Let ports settle for a while and re-read the link status a few times 20ms
apart to see if the ports transitions out of SS.Inactive.

According to USB 3.x spec 7.5.2, a port in SS.Inactive should
automatically check for missing far-end receiver termination every
12 ms (SSInactiveQuietTimeout)

The futile multiple warm reset retries of a disconnected device takes
a lot of time, also the resetting of a removed devices has caused cases
where the reset bit got stuck for a long time on xHCI roothub.
This lead to issues in detecting new devices connected to the same port
shortly after.

Tested-by: Mark Pearson <markpearson@lenovo.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
---
 drivers/usb/core/hub.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

Comments

Wohl, Tobias Dec. 27, 2021, 12:35 p.m. UTC | #1
Hi Mathias,

I have encountered the problem that my device (zynq ultrascale+ platform) ends up
performing warm resets when disconnecting USB3 devices and this patch helped with that.
Nevertheless a much higher value for the define DETECT_DISCONNECT_TRIES was needed to detect
the disconnect status early:

[  211.847722] xhci-hcd xhci-hcd.0.auto: Port change event, 2-1, id 2, portsc: 0x4012c1
[  211.847739] xhci-hcd xhci-hcd.0.auto: handle_port_status: starting port polling.
[  211.847767] hub 2-0:1.0: state 7 ports 1 chg 0000 evt 0002
[  211.847786] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x4012c1, return 0x4002c1
[  211.847805] usb usb2-port1: link state change
[  211.847817] xhci-hcd xhci-hcd.0.auto: clear port1 link state change, portsc: 0x12c1
[  211.872383] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  211.872415] usb usb2-port1: Wait for inactive link disconnect detect
[  211.900342] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  211.900372] usb usb2-port1: Wait for inactive link disconnect detect
[  211.928342] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  211.928374] usb usb2-port1: Wait for inactive link disconnect detect
[  211.948323] xhci-hcd xhci-hcd.0.auto: xhci_hub_status_data: stopping port polling.
[  211.956338] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  211.956383] usb usb2-port1: Wait for inactive link disconnect detect
[  211.984345] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  211.984378] usb usb2-port1: Wait for inactive link disconnect detect
[  212.012406] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.012442] usb usb2-port1: Wait for inactive link disconnect detect
[  212.040542] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.040586] usb usb2-port1: Wait for inactive link disconnect detect
[  212.068343] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.068391] usb usb2-port1: Wait for inactive link disconnect detect
[  212.096344] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.096376] usb usb2-port1: Wait for inactive link disconnect detect
[  212.124343] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.124373] usb usb2-port1: Wait for inactive link disconnect detect
[  212.152352] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.152391] usb usb2-port1: Wait for inactive link disconnect detect
[  212.180341] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.180371] usb usb2-port1: Wait for inactive link disconnect detect
[  212.208366] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.208748] usb usb2-port1: Wait for inactive link disconnect detect
[  212.224375] xhci-hcd xhci-hcd.0.auto: Can't queue urb, port error, link inactive
[  212.236342] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x12c1, return 0x2c1
[  212.236373] usb usb2-port1: Wait for inactive link disconnect detect
[  212.251650] xhci-hcd xhci-hcd.0.auto: Port change event, 2-1, id 2, portsc: 0x202a0
[  212.251661] xhci-hcd xhci-hcd.0.auto: handle_port_status: starting port polling.
[  212.264374] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x202a0, return 0x102a0
[  212.264400] usb usb2-port1: Wait for inactive link disconnect detect
[  212.264442] xhci-hcd xhci-hcd.0.auto: Can't queue urb, port error, link inactive
[  212.264451] usb 2-1: Disable of device-initiated U1 failed.
[  212.264477] xhci-hcd xhci-hcd.0.auto: Can't queue urb, port error, link inactive
[  212.264484] usb 2-1: Disable of device-initiated U2 failed.
[  212.264493] xhci-hcd xhci-hcd.0.auto: Set up evaluate context for LPM MEL change.
[  212.264504] xhci-hcd xhci-hcd.0.auto: // Ding dong!
[  212.264529] xhci-hcd xhci-hcd.0.auto: Successful evaluate context command
[  212.264545] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x202a0, return 0x102a0
[  212.264570] xhci-hcd xhci-hcd.0.auto: set port reset, actual port 2-1 status  = 0x202b0
[  212.264599] hub 2-0:1.0: state 7 ports 1 chg 0000 evt 0002
[  212.332342] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x2a02a0, return 0x3102a0
[  212.332390] usb usb2-port1: not reset yet, waiting 60ms
[  212.400355] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x2a02a0, return 0x3102a0
[  212.400395] usb usb2-port1: not reset yet, waiting 200ms
[  212.416387] usb 1-1.2-port2: indicator off status 0
[  212.416470] usb 1-1.2-port3: indicator green status 0
[  212.416535] usb 1-1.2-port4: indicator green status 0
[  212.416617] usb 1-1.2-port5: indicator off status 0
[  212.416682] usb 1-1.2-port6: indicator off status 0
[  212.608338] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x2a02a0, return 0x3102a0
[  212.608376] usb usb2-port1: not reset yet, waiting 200ms
[  212.816348] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x2a02a0, return 0x3102a0
[  212.816404] usb usb2-port1: not reset yet, waiting 200ms
[  213.024340] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x2a02a0, return 0x3102a0
[  213.024376] usb usb2-port1: not reset yet, waiting 200ms
[  213.024388] xhci-hcd xhci-hcd.0.auto: clear port1 reset change, portsc: 0xa02a0
[  213.024411] xhci-hcd xhci-hcd.0.auto: clear port1 warm(BH) reset change, portsc: 0x202a0
[  213.024434] xhci-hcd xhci-hcd.0.auto: clear port1 link state change, portsc: 0x202a0
[  213.024456] xhci-hcd xhci-hcd.0.auto: clear port1 connect change, portsc: 0x2a0
[  213.024479] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x2a0, return 0x2a0
[  213.024506] xhci-hcd xhci-hcd.0.auto: CTRL: TypeReq=0x2303 val=0x5 idx=0x301 len=0 ==> -19
[  213.024527] usb usb2-port1: logical disconnect
[  213.024537] xhci-hcd xhci-hcd.0.auto: CTRL: TypeReq=0x2303 val=0x5 idx=0x301 len=0 ==> -19
[  213.024583] xhci-hcd xhci-hcd.0.auto: Get port status 2-1 read: 0x2a0, return 0x2a0
[  213.024636] usb usb2-port1: status 02a0, change 0000, 5.0 Gb/s
[  213.024645] usb 2-1: USB disconnect, device number 5

In some rare cases even

#define DETECT_DISCONNECT_TRIES 20

was not enough. As I want to make sure that the warm resets will never be performed for
a disconnected device (as this takes a long time), I want to enhance the value for
DETECT_DISCONNECT_TRIES for my application.

Are there any issues to expect when setting a too high value for DETECT_DISCONNECT_TRIES?

Thanks,

Tobias

On 10.12.2021 12:16, Mathias Nyman wrote:

> During disconnect USB-3 ports often go via SS.Inactive link error state
> before the missing terminations are noticed, and link finally goes to
> RxDetect state
>
> Avoid immediately warm-resetting ports in SS.Inactive state.
> Let ports settle for a while and re-read the link status a few times 20ms
> apart to see if the ports transitions out of SS.Inactive.
>
> According to USB 3.x spec 7.5.2, a port in SS.Inactive should
> automatically check for missing far-end receiver termination every
> 12 ms (SSInactiveQuietTimeout)
>
> The futile multiple warm reset retries of a disconnected device takes
> a lot of time, also the resetting of a removed devices has caused cases
> where the reset bit got stuck for a long time on xHCI roothub.
> This lead to issues in detecting new devices connected to the same port
> shortly after.
>
> Tested-by: Mark Pearson <markpearson@lenovo.com>
> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
> ---
>   drivers/usb/core/hub.c | 24 +++++++++++++++++++-----
>   1 file changed, 19 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 00070a8a6507..e907dfa0ca6d 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -2777,6 +2777,8 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
>   #define PORT_INIT_TRIES		4
>   #endif	/* CONFIG_USB_FEW_INIT_RETRIES */
>   
> +#define DETECT_DISCONNECT_TRIES 5
> +
>   #define HUB_ROOT_RESET_TIME	60	/* times are in msec */
>   #define HUB_SHORT_RESET_TIME	10
>   #define HUB_BH_RESET_TIME	50
> @@ -5543,6 +5545,7 @@ static void port_event(struct usb_hub *hub, int port1)
>   	struct usb_device *udev = port_dev->child;
>   	struct usb_device *hdev = hub->hdev;
>   	u16 portstatus, portchange;
> +	int i = 0;
>   
>   	connect_change = test_bit(port1, hub->change_bits);
>   	clear_bit(port1, hub->event_bits);
> @@ -5619,17 +5622,27 @@ static void port_event(struct usb_hub *hub, int port1)
>   		connect_change = 1;
>   
>   	/*
> -	 * Warm reset a USB3 protocol port if it's in
> -	 * SS.Inactive state.
> +	 * Avoid trying to recover a USB3 SS.Inactive port with a warm reset if
> +	 * the device was disconnected. A 12ms disconnect detect timer in
> +	 * SS.Inactive state transitions the port to RxDetect automatically.
> +	 * SS.Inactive link error state is common during device disconnect.
>   	 */
> -	if (hub_port_warm_reset_required(hub, port1, portstatus)) {
> -		dev_dbg(&port_dev->dev, "do warm reset\n");
> -		if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
> +	while (hub_port_warm_reset_required(hub, port1, portstatus)) {
> +		if ((i++ < DETECT_DISCONNECT_TRIES) && udev) {
> +			u16 unused;
> +
> +			msleep(20);
> +			hub_port_status(hub, port1, &portstatus, &unused);
> +			dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n");
> +			continue;
> +		} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
>   				|| udev->state == USB_STATE_NOTATTACHED) {
> +			dev_dbg(&port_dev->dev, "do warm reset, port only\n");
>   			if (hub_port_reset(hub, port1, NULL,
>   					HUB_BH_RESET_TIME, true) < 0)
>   				hub_port_disable(hub, port1, 1);
>   		} else {
> +			dev_dbg(&port_dev->dev, "do warm reset, full device\n");
>   			usb_unlock_port(port_dev);
>   			usb_lock_device(udev);
>   			usb_reset_device(udev);
> @@ -5637,6 +5650,7 @@ static void port_event(struct usb_hub *hub, int port1)
>   			usb_lock_port(port_dev);
>   			connect_change = 0;
>   		}
> +		break;
>   	}
>   
>   	if (connect_change)
diff mbox series

Patch

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 00070a8a6507..e907dfa0ca6d 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2777,6 +2777,8 @@  static unsigned hub_is_wusb(struct usb_hub *hub)
 #define PORT_INIT_TRIES		4
 #endif	/* CONFIG_USB_FEW_INIT_RETRIES */
 
+#define DETECT_DISCONNECT_TRIES 5
+
 #define HUB_ROOT_RESET_TIME	60	/* times are in msec */
 #define HUB_SHORT_RESET_TIME	10
 #define HUB_BH_RESET_TIME	50
@@ -5543,6 +5545,7 @@  static void port_event(struct usb_hub *hub, int port1)
 	struct usb_device *udev = port_dev->child;
 	struct usb_device *hdev = hub->hdev;
 	u16 portstatus, portchange;
+	int i = 0;
 
 	connect_change = test_bit(port1, hub->change_bits);
 	clear_bit(port1, hub->event_bits);
@@ -5619,17 +5622,27 @@  static void port_event(struct usb_hub *hub, int port1)
 		connect_change = 1;
 
 	/*
-	 * Warm reset a USB3 protocol port if it's in
-	 * SS.Inactive state.
+	 * Avoid trying to recover a USB3 SS.Inactive port with a warm reset if
+	 * the device was disconnected. A 12ms disconnect detect timer in
+	 * SS.Inactive state transitions the port to RxDetect automatically.
+	 * SS.Inactive link error state is common during device disconnect.
 	 */
-	if (hub_port_warm_reset_required(hub, port1, portstatus)) {
-		dev_dbg(&port_dev->dev, "do warm reset\n");
-		if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
+	while (hub_port_warm_reset_required(hub, port1, portstatus)) {
+		if ((i++ < DETECT_DISCONNECT_TRIES) && udev) {
+			u16 unused;
+
+			msleep(20);
+			hub_port_status(hub, port1, &portstatus, &unused);
+			dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n");
+			continue;
+		} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
 				|| udev->state == USB_STATE_NOTATTACHED) {
+			dev_dbg(&port_dev->dev, "do warm reset, port only\n");
 			if (hub_port_reset(hub, port1, NULL,
 					HUB_BH_RESET_TIME, true) < 0)
 				hub_port_disable(hub, port1, 1);
 		} else {
+			dev_dbg(&port_dev->dev, "do warm reset, full device\n");
 			usb_unlock_port(port_dev);
 			usb_lock_device(udev);
 			usb_reset_device(udev);
@@ -5637,6 +5650,7 @@  static void port_event(struct usb_hub *hub, int port1)
 			usb_lock_port(port_dev);
 			connect_change = 0;
 		}
+		break;
 	}
 
 	if (connect_change)