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: Andiry Xu <andiry.xu@amd.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
d782659924
commit
75d7cf72ab
@@ -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,
|
|||||||
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,
|
|||||||
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,76 +2077,109 @@ 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);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */
|
||||||
|
static int hub_port_reset(struct usb_hub *hub, int port1,
|
||||||
|
struct usb_device *udev, unsigned int delay, bool warm)
|
||||||
|
{
|
||||||
|
int i, status;
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
dev_err(hub->intfdev, "only USB3 hub support "
|
||||||
|
"warm reset\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset the port */
|
||||||
|
for (i = 0; i < PORT_RESET_TRIES; i++) {
|
||||||
|
status = set_port_feature(hub->hdev, port1, (warm ?
|
||||||
|
USB_PORT_FEAT_BH_PORT_RESET :
|
||||||
|
USB_PORT_FEAT_RESET));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return on disconnect or reset */
|
||||||
|
if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
|
||||||
|
hub_port_finish_reset(hub, port1, udev, &status, warm);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2147,46 +2188,12 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|||||||
port1);
|
port1);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
if (!warm)
|
||||||
up_read(&ehci_cf_port_reset_rwsem);
|
up_read(&ehci_cf_port_reset_rwsem);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if a port is power on */
|
/* Check if a port is power on */
|
||||||
static int port_is_power_on(struct usb_hub *hub, unsigned portstatus)
|
static int port_is_power_on(struct usb_hub *hub, unsigned portstatus)
|
||||||
{
|
{
|
||||||
@@ -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,
|
|||||||
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)
|
|||||||
(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)
|
||||||
|
Reference in New Issue
Block a user