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,28 +2047,39 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2046,28 +2047,39 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if (ret < 0) if (ret < 0)
return ret; return ret;
/* Device went away? */ /*
if (!(portstatus & USB_PORT_STAT_CONNECTION)) * Some buggy devices require a warm reset to be issued even
return -ENOTCONN; * when the port appears not to be connected.
*/
/* bomb out completely if the connection bounced */ if (!warm) {
if ((portchange & USB_PORT_STAT_C_CONNECTION)) /* Device went away? */
return -ENOTCONN; if (!(portstatus & USB_PORT_STAT_CONNECTION))
return -ENOTCONN;
/* if we`ve finished resetting, then break out of the loop */
if (!(portstatus & USB_PORT_STAT_RESET) && /* bomb out completely if the connection bounced */
(portstatus & USB_PORT_STAT_ENABLE)) { if ((portchange & USB_PORT_STAT_C_CONNECTION))
if (hub_is_wusb(hub)) return -ENOTCONN;
udev->speed = USB_SPEED_WIRELESS;
else if (hub_is_superspeed(hub->hdev)) /* if we`ve finished resetting, then break out of
udev->speed = USB_SPEED_SUPER; * the loop
else if (portstatus & USB_PORT_STAT_HIGH_SPEED) */
udev->speed = USB_SPEED_HIGH; if (!(portstatus & USB_PORT_STAT_RESET) &&
else if (portstatus & USB_PORT_STAT_LOW_SPEED) (portstatus & USB_PORT_STAT_ENABLE)) {
udev->speed = USB_SPEED_LOW; if (hub_is_wusb(hub))
else udev->speed = USB_SPEED_WIRELESS;
udev->speed = USB_SPEED_FULL; else if (hub_is_superspeed(hub->hdev))
return 0; udev->speed = USB_SPEED_SUPER;
else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
udev->speed = USB_SPEED_HIGH;
else if (portstatus & USB_PORT_STAT_LOW_SPEED)
udev->speed = USB_SPEED_LOW;
else
udev->speed = USB_SPEED_FULL;
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 */
...@@ -2075,35 +2087,84 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2075,35 +2087,84 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
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 void hub_port_finish_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, int *status, bool warm)
{
switch (*status) {
case 0:
if (!warm) {
struct usb_hcd *hcd;
/* TRSTRCY = 10 ms; plus some extra */
msleep(10 + 40);
update_devnum(udev, 0);
hcd = bus_to_hcd(udev->bus);
if (hcd->driver->reset_device) {
*status = hcd->driver->reset_device(hcd, udev);
if (*status < 0) {
dev_err(&udev->dev, "Cannot reset "
"HCD device state\n");
break;
}
}
}
/* FALL THROUGH */
case -ENOTCONN:
case -ENODEV:
clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_C_RESET);
/* FIXME need disconnect() for NOTATTACHED device */
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_DEFAULT);
}
break;
}
}
/* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */
static int hub_port_reset(struct usb_hub *hub, int port1, static int hub_port_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay) struct usb_device *udev, unsigned int delay, bool warm)
{ {
int i, status; int i, status;
struct usb_hcd *hcd;
hcd = bus_to_hcd(udev->bus); if (!warm) {
/* Block EHCI CF initialization during the port reset. /* Block EHCI CF initialization during the port reset.
* Some companion controllers don't like it when they mix. * Some companion controllers don't like it when they mix.
*/ */
down_read(&ehci_cf_port_reset_rwsem); down_read(&ehci_cf_port_reset_rwsem);
} else {
if (!hub_is_superspeed(hub->hdev)) {
dev_err(hub->intfdev, "only USB3 hub support "
"warm reset\n");
return -EINVAL;
}
}
/* Reset the port */ /* Reset the port */
for (i = 0; i < PORT_RESET_TRIES; i++) { for (i = 0; i < PORT_RESET_TRIES; i++) {
status = set_port_feature(hub->hdev, status = set_port_feature(hub->hdev, port1, (warm ?
port1, USB_PORT_FEAT_RESET); USB_PORT_FEAT_BH_PORT_RESET :
if (status) USB_PORT_FEAT_RESET));
if (status) {
dev_err(hub->intfdev, dev_err(hub->intfdev,
"cannot reset port %d (err = %d)\n", "cannot %sreset port %d (err = %d)\n",
port1, status); warm ? "warm " : "", port1, status);
else { } else {
status = hub_port_wait_reset(hub, port1, udev, delay); status = hub_port_wait_reset(hub, port1, udev, delay,
warm);
if (status && status != -ENOTCONN) if (status && status != -ENOTCONN)
dev_dbg(hub->intfdev, dev_dbg(hub->intfdev,
"port_wait_reset: err = %d\n", "port_wait_reset: err = %d\n",
...@@ -2111,34 +2172,14 @@ static int hub_port_reset(struct usb_hub *hub, int port1, ...@@ -2111,34 +2172,14 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
} }
/* return on disconnect or reset */ /* return on disconnect or reset */
switch (status) { if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
case 0: hub_port_finish_reset(hub, port1, udev, &status, warm);
/* TRSTRCY = 10 ms; plus some extra */
msleep(10 + 40);
update_devnum(udev, 0);
if (hcd->driver->reset_device) {
status = hcd->driver->reset_device(hcd, udev);
if (status < 0) {
dev_err(&udev->dev, "Cannot reset "
"HCD device state\n");
break;
}
}
/* FALL THROUGH */
case -ENOTCONN:
case -ENODEV:
clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_C_RESET);
/* FIXME need disconnect() for NOTATTACHED device */
usb_set_device_state(udev, status
? USB_STATE_NOTATTACHED
: USB_STATE_DEFAULT);
goto done; goto done;
} }
dev_dbg (hub->intfdev, dev_dbg (hub->intfdev,
"port %d not enabled, trying reset again...\n", "port %d not enabled, trying %sreset again...\n",
port1); port1, warm ? "warm " : "");
delay = HUB_LONG_RESET_TIME; delay = HUB_LONG_RESET_TIME;
} }
...@@ -2146,45 +2187,11 @@ static int hub_port_reset(struct usb_hub *hub, int port1, ...@@ -2146,45 +2187,11 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
"Cannot enable port %i. Maybe the USB cable is bad?\n", "Cannot enable port %i. Maybe the USB cable is bad?\n",
port1); port1);
done: done:
up_read(&ehci_cf_port_reset_rwsem); if (!warm)
return status; up_read(&ehci_cf_port_reset_rwsem);
}
/* Warm reset a USB3 protocol port */
static int hub_port_warm_reset(struct usb_hub *hub, int port)
{
int ret;
u16 portstatus, portchange;
if (!hub_is_superspeed(hub->hdev)) {
dev_err(hub->intfdev, "only USB3 hub support warm reset\n");
return -EINVAL;
}
/* Warm reset the port */
ret = set_port_feature(hub->hdev,
port, USB_PORT_FEAT_BH_PORT_RESET);
if (ret) {
dev_err(hub->intfdev, "cannot warm reset port %d\n", port);
return ret;
}
msleep(20);
ret = hub_port_status(hub, port, &portstatus, &portchange);
if (portchange & USB_PORT_STAT_C_RESET)
clear_port_feature(hub->hdev, port, USB_PORT_FEAT_C_RESET);
if (portchange & USB_PORT_STAT_C_BH_RESET)
clear_port_feature(hub->hdev, port,
USB_PORT_FEAT_C_BH_PORT_RESET);
if (portchange & USB_PORT_STAT_C_LINK_STATE)
clear_port_feature(hub->hdev, port,
USB_PORT_FEAT_C_PORT_LINK_STATE);
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