|
@@ -877,6 +877,60 @@ static int hub_hub_status(struct usb_hub *hub,
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static int hub_set_port_link_state(struct usb_hub *hub, int port1,
|
|
|
+ unsigned int link_status)
|
|
|
+{
|
|
|
+ return set_port_feature(hub->hdev,
|
|
|
+ port1 | (link_status << 3),
|
|
|
+ USB_PORT_FEAT_LINK_STATE);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * If USB 3.0 ports are placed into the Disabled state, they will no longer
|
|
|
+ * detect any device connects or disconnects. This is generally not what the
|
|
|
+ * USB core wants, since it expects a disabled port to produce a port status
|
|
|
+ * change event when a new device connects.
|
|
|
+ *
|
|
|
+ * Instead, set the link state to Disabled, wait for the link to settle into
|
|
|
+ * that state, clear any change bits, and then put the port into the RxDetect
|
|
|
+ * state.
|
|
|
+ */
|
|
|
+static int hub_usb3_port_disable(struct usb_hub *hub, int port1)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ int total_time;
|
|
|
+ u16 portchange, portstatus;
|
|
|
+
|
|
|
+ if (!hub_is_superspeed(hub->hdev))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ ret = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_SS_DISABLED);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
|
|
|
+ port1, ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Wait for the link to enter the disabled state. */
|
|
|
+ for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
|
|
|
+ ret = hub_port_status(hub, port1, &portstatus, &portchange);
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ if ((portstatus & USB_PORT_STAT_LINK_STATE) ==
|
|
|
+ USB_SS_PORT_LS_SS_DISABLED)
|
|
|
+ break;
|
|
|
+ if (total_time >= HUB_DEBOUNCE_TIMEOUT)
|
|
|
+ break;
|
|
|
+ msleep(HUB_DEBOUNCE_STEP);
|
|
|
+ }
|
|
|
+ if (total_time >= HUB_DEBOUNCE_TIMEOUT)
|
|
|
+ dev_warn(hub->intfdev, "Could not disable port %d after %d ms\n",
|
|
|
+ port1, total_time);
|
|
|
+
|
|
|
+ return hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_RX_DETECT);
|
|
|
+}
|
|
|
+
|
|
|
static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
|
|
|
{
|
|
|
struct usb_device *hdev = hub->hdev;
|
|
@@ -885,8 +939,13 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
|
|
|
if (hub->ports[port1 - 1]->child && set_state)
|
|
|
usb_set_device_state(hub->ports[port1 - 1]->child,
|
|
|
USB_STATE_NOTATTACHED);
|
|
|
- if (!hub->error && !hub_is_superspeed(hub->hdev))
|
|
|
- ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
|
|
|
+ if (!hub->error) {
|
|
|
+ if (hub_is_superspeed(hub->hdev))
|
|
|
+ ret = hub_usb3_port_disable(hub, port1);
|
|
|
+ else
|
|
|
+ ret = clear_port_feature(hdev, port1,
|
|
|
+ USB_PORT_FEAT_ENABLE);
|
|
|
+ }
|
|
|
if (ret)
|
|
|
dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
|
|
|
port1, ret);
|
|
@@ -2440,7 +2499,7 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
|
|
|
#define HUB_SHORT_RESET_TIME 10
|
|
|
#define HUB_BH_RESET_TIME 50
|
|
|
#define HUB_LONG_RESET_TIME 200
|
|
|
-#define HUB_RESET_TIMEOUT 500
|
|
|
+#define HUB_RESET_TIMEOUT 800
|
|
|
|
|
|
static int hub_port_reset(struct usb_hub *hub, int port1,
|
|
|
struct usb_device *udev, unsigned int delay, bool warm);
|
|
@@ -2475,6 +2534,10 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
|
|
|
if (ret < 0)
|
|
|
return ret;
|
|
|
|
|
|
+ /* The port state is unknown until the reset completes. */
|
|
|
+ if ((portstatus & USB_PORT_STAT_RESET))
|
|
|
+ goto delay;
|
|
|
+
|
|
|
/*
|
|
|
* Some buggy devices require a warm reset to be issued even
|
|
|
* when the port appears not to be connected.
|
|
@@ -2520,11 +2583,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
|
|
|
if ((portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
return -ENOTCONN;
|
|
|
|
|
|
- /* if we`ve finished resetting, then break out of
|
|
|
- * the loop
|
|
|
- */
|
|
|
- if (!(portstatus & USB_PORT_STAT_RESET) &&
|
|
|
- (portstatus & USB_PORT_STAT_ENABLE)) {
|
|
|
+ if ((portstatus & USB_PORT_STAT_ENABLE)) {
|
|
|
if (hub_is_wusb(hub))
|
|
|
udev->speed = USB_SPEED_WIRELESS;
|
|
|
else if (hub_is_superspeed(hub->hdev))
|
|
@@ -2538,10 +2597,15 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
|
|
|
return 0;
|
|
|
}
|
|
|
} else {
|
|
|
- if (portchange & USB_PORT_STAT_C_BH_RESET)
|
|
|
- return 0;
|
|
|
+ if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
|
|
|
+ hub_port_warm_reset_required(hub,
|
|
|
+ portstatus))
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
+delay:
|
|
|
/* switch to the long delay after two short delay failures */
|
|
|
if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
|
|
|
delay = HUB_LONG_RESET_TIME;
|
|
@@ -2565,14 +2629,11 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
|
|
|
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;
|
|
|
- }
|
|
|
- }
|
|
|
+ /* The xHC may think the device is already reset,
|
|
|
+ * so ignore the status.
|
|
|
+ */
|
|
|
+ if (hcd->driver->reset_device)
|
|
|
+ hcd->driver->reset_device(hcd, udev);
|
|
|
}
|
|
|
/* FALL THROUGH */
|
|
|
case -ENOTCONN:
|
|
@@ -2580,16 +2641,16 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
|
|
|
clear_port_feature(hub->hdev,
|
|
|
port1, USB_PORT_FEAT_C_RESET);
|
|
|
/* FIXME need disconnect() for NOTATTACHED device */
|
|
|
- if (warm) {
|
|
|
+ if (hub_is_superspeed(hub->hdev)) {
|
|
|
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 {
|
|
|
+ }
|
|
|
+ if (!warm)
|
|
|
usb_set_device_state(udev, *status
|
|
|
? USB_STATE_NOTATTACHED
|
|
|
: USB_STATE_DEFAULT);
|
|
|
- }
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
@@ -4638,9 +4699,14 @@ static void hub_events(void)
|
|
|
* SS.Inactive state.
|
|
|
*/
|
|
|
if (hub_port_warm_reset_required(hub, portstatus)) {
|
|
|
+ int status;
|
|
|
+
|
|
|
dev_dbg(hub_dev, "warm reset port %d\n", i);
|
|
|
- hub_port_reset(hub, i, NULL,
|
|
|
+ status = hub_port_reset(hub, i, NULL,
|
|
|
HUB_BH_RESET_TIME, true);
|
|
|
+ if (status < 0)
|
|
|
+ hub_port_disable(hub, i, 1);
|
|
|
+ connect_change = 0;
|
|
|
}
|
|
|
|
|
|
if (connect_change)
|