Commit 75d7cf72 authored by Andiry Xu's avatar Andiry Xu Committed by Greg Kroah-Hartman

usbcore: refine warm reset logic

Current waiting time for warm(BH) reset in hub_port_warm_reset() is too short
for xHC host to complete the warm reset and report a BH reset change.

This patch increases the waiting time for warm reset and merges the function
into hub_port_reset(), so it can handle both cold reset and warm reset, and
factor out hub_port_finish_reset() to make the code looks cleaner.

This fixes the issue that driver fails to clear BH reset change and port is
"dead".
Signed-off-by: default avatarAndiry Xu <andiry.xu@amd.com>
Acked-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d7826599
...@@ -2025,11 +2025,12 @@ static unsigned hub_is_wusb(struct usb_hub *hub) ...@@ -2025,11 +2025,12 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
#define HUB_ROOT_RESET_TIME 50 /* times are in msec */ #define HUB_ROOT_RESET_TIME 50 /* times are in msec */
#define HUB_SHORT_RESET_TIME 10 #define HUB_SHORT_RESET_TIME 10
#define HUB_BH_RESET_TIME 50
#define HUB_LONG_RESET_TIME 200 #define HUB_LONG_RESET_TIME 200
#define HUB_RESET_TIMEOUT 500 #define HUB_RESET_TIMEOUT 500
static int hub_port_wait_reset(struct usb_hub *hub, int port1, static int hub_port_wait_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay) struct usb_device *udev, unsigned int delay, bool warm)
{ {
int delay_time, ret; int delay_time, ret;
u16 portstatus; u16 portstatus;
...@@ -2046,6 +2047,11 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2046,6 +2047,11 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if (ret < 0) if (ret < 0)
return ret; return ret;
/*
* Some buggy devices require a warm reset to be issued even
* when the port appears not to be connected.
*/
if (!warm) {
/* Device went away? */ /* Device went away? */
if (!(portstatus & USB_PORT_STAT_CONNECTION)) if (!(portstatus & USB_PORT_STAT_CONNECTION))
return -ENOTCONN; return -ENOTCONN;
...@@ -2054,7 +2060,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2054,7 +2060,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if ((portchange & USB_PORT_STAT_C_CONNECTION)) if ((portchange & USB_PORT_STAT_C_CONNECTION))
return -ENOTCONN; return -ENOTCONN;
/* if we`ve finished resetting, then break out of the loop */ /* if we`ve finished resetting, then break out of
* the loop
*/
if (!(portstatus & USB_PORT_STAT_RESET) && if (!(portstatus & USB_PORT_STAT_RESET) &&
(portstatus & USB_PORT_STAT_ENABLE)) { (portstatus & USB_PORT_STAT_ENABLE)) {
if (hub_is_wusb(hub)) if (hub_is_wusb(hub))
...@@ -2069,122 +2077,121 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2069,122 +2077,121 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
udev->speed = USB_SPEED_FULL; udev->speed = USB_SPEED_FULL;
return 0; return 0;
} }
} else {
if (portchange & USB_PORT_STAT_C_BH_RESET)
return 0;
}
/* switch to the long delay after two short delay failures */ /* switch to the long delay after two short delay failures */
if (delay_time >= 2 * HUB_SHORT_RESET_TIME) if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
delay = HUB_LONG_RESET_TIME; delay = HUB_LONG_RESET_TIME;
dev_dbg (hub->intfdev, dev_dbg (hub->intfdev,
"port %d not reset yet, waiting %dms\n", "port %d not %sreset yet, waiting %dms\n",
port1, delay); port1, warm ? "warm " : "", delay);
} }
return -EBUSY; return -EBUSY;
} }
static int hub_port_reset(struct usb_hub *hub, int port1, static void hub_port_finish_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay) struct usb_device *udev, int *status, bool warm)
{ {
int i, status; switch (*status) {
struct usb_hcd *hcd;
hcd = bus_to_hcd(udev->bus);
/* Block EHCI CF initialization during the port reset.
* Some companion controllers don't like it when they mix.
*/
down_read(&ehci_cf_port_reset_rwsem);
/* Reset the port */
for (i = 0; i < PORT_RESET_TRIES; i++) {
status = set_port_feature(hub->hdev,
port1, USB_PORT_FEAT_RESET);
if (status)
dev_err(hub->intfdev,
"cannot reset port %d (err = %d)\n",
port1, status);
else {
status = hub_port_wait_reset(hub, port1, udev, delay);
if (status && status != -ENOTCONN)
dev_dbg(hub->intfdev,
"port_wait_reset: err = %d\n",
status);
}
/* return on disconnect or reset */
switch (status) {
case 0: case 0:
if (!warm) {
struct usb_hcd *hcd;
/* TRSTRCY = 10 ms; plus some extra */ /* TRSTRCY = 10 ms; plus some extra */
msleep(10 + 40); msleep(10 + 40);
update_devnum(udev, 0); update_devnum(udev, 0);
hcd = bus_to_hcd(udev->bus);
if (hcd->driver->reset_device) { if (hcd->driver->reset_device) {
status = hcd->driver->reset_device(hcd, udev); *status = hcd->driver->reset_device(hcd, udev);
if (status < 0) { if (*status < 0) {
dev_err(&udev->dev, "Cannot reset " dev_err(&udev->dev, "Cannot reset "
"HCD device state\n"); "HCD device state\n");
break; break;
} }
} }
}
/* FALL THROUGH */ /* FALL THROUGH */
case -ENOTCONN: case -ENOTCONN:
case -ENODEV: case -ENODEV:
clear_port_feature(hub->hdev, clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_C_RESET); port1, USB_PORT_FEAT_C_RESET);
/* FIXME need disconnect() for NOTATTACHED device */ /* FIXME need disconnect() for NOTATTACHED device */
usb_set_device_state(udev, status if (warm) {
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_BH_PORT_RESET);
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_PORT_LINK_STATE);
} else {
usb_set_device_state(udev, *status
? USB_STATE_NOTATTACHED ? USB_STATE_NOTATTACHED
: USB_STATE_DEFAULT); : USB_STATE_DEFAULT);
goto done;
} }
break;
dev_dbg (hub->intfdev,
"port %d not enabled, trying reset again...\n",
port1);
delay = HUB_LONG_RESET_TIME;
} }
dev_err (hub->intfdev,
"Cannot enable port %i. Maybe the USB cable is bad?\n",
port1);
done:
up_read(&ehci_cf_port_reset_rwsem);
return status;
} }
/* Warm reset a USB3 protocol port */ /* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */
static int hub_port_warm_reset(struct usb_hub *hub, int port) static int hub_port_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay, bool warm)
{ {
int ret; int i, status;
u16 portstatus, portchange;
if (!warm) {
/* Block EHCI CF initialization during the port reset.
* Some companion controllers don't like it when they mix.
*/
down_read(&ehci_cf_port_reset_rwsem);
} else {
if (!hub_is_superspeed(hub->hdev)) { if (!hub_is_superspeed(hub->hdev)) {
dev_err(hub->intfdev, "only USB3 hub support warm reset\n"); dev_err(hub->intfdev, "only USB3 hub support "
"warm reset\n");
return -EINVAL; return -EINVAL;
} }
}
/* Warm reset the port */ /* Reset the port */
ret = set_port_feature(hub->hdev, for (i = 0; i < PORT_RESET_TRIES; i++) {
port, USB_PORT_FEAT_BH_PORT_RESET); status = set_port_feature(hub->hdev, port1, (warm ?
if (ret) { USB_PORT_FEAT_BH_PORT_RESET :
dev_err(hub->intfdev, "cannot warm reset port %d\n", port); USB_PORT_FEAT_RESET));
return ret; if (status) {
dev_err(hub->intfdev,
"cannot %sreset port %d (err = %d)\n",
warm ? "warm " : "", port1, status);
} else {
status = hub_port_wait_reset(hub, port1, udev, delay,
warm);
if (status && status != -ENOTCONN)
dev_dbg(hub->intfdev,
"port_wait_reset: err = %d\n",
status);
} }
msleep(20); /* return on disconnect or reset */
ret = hub_port_status(hub, port, &portstatus, &portchange); if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
hub_port_finish_reset(hub, port1, udev, &status, warm);
goto done;
}
if (portchange & USB_PORT_STAT_C_RESET) dev_dbg (hub->intfdev,
clear_port_feature(hub->hdev, port, USB_PORT_FEAT_C_RESET); "port %d not enabled, trying %sreset again...\n",
port1, warm ? "warm " : "");
delay = HUB_LONG_RESET_TIME;
}
if (portchange & USB_PORT_STAT_C_BH_RESET) dev_err (hub->intfdev,
clear_port_feature(hub->hdev, port, "Cannot enable port %i. Maybe the USB cable is bad?\n",
USB_PORT_FEAT_C_BH_PORT_RESET); port1);
if (portchange & USB_PORT_STAT_C_LINK_STATE) done:
clear_port_feature(hub->hdev, port, if (!warm)
USB_PORT_FEAT_C_PORT_LINK_STATE); up_read(&ehci_cf_port_reset_rwsem);
return ret; return status;
} }
/* Check if a port is power on */ /* Check if a port is power on */
...@@ -2814,7 +2821,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, ...@@ -2814,7 +2821,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
/* Reset the device; full speed may morph to high speed */ /* Reset the device; full speed may morph to high speed */
/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */ /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
retval = hub_port_reset(hub, port1, udev, delay); retval = hub_port_reset(hub, port1, udev, delay, false);
if (retval < 0) /* error or disconnect */ if (retval < 0) /* error or disconnect */
goto fail; goto fail;
/* success, speed is known */ /* success, speed is known */
...@@ -2935,7 +2942,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, ...@@ -2935,7 +2942,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
buf->bMaxPacketSize0; buf->bMaxPacketSize0;
kfree(buf); kfree(buf);
retval = hub_port_reset(hub, port1, udev, delay); retval = hub_port_reset(hub, port1, udev, delay, false);
if (retval < 0) /* error or disconnect */ if (retval < 0) /* error or disconnect */
goto fail; goto fail;
if (oldspeed != udev->speed) { if (oldspeed != udev->speed) {
...@@ -3556,7 +3563,8 @@ static void hub_events(void) ...@@ -3556,7 +3563,8 @@ static void hub_events(void)
(portstatus & USB_PORT_STAT_LINK_STATE) (portstatus & USB_PORT_STAT_LINK_STATE)
== USB_SS_PORT_LS_SS_INACTIVE) { == USB_SS_PORT_LS_SS_INACTIVE) {
dev_dbg(hub_dev, "warm reset port %d\n", i); dev_dbg(hub_dev, "warm reset port %d\n", i);
hub_port_warm_reset(hub, i); hub_port_reset(hub, i, NULL,
HUB_BH_RESET_TIME, true);
} }
if (connect_change) if (connect_change)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment