|
@@ -2535,77 +2535,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
|
|
|
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.
|
|
|
- */
|
|
|
- if (!warm) {
|
|
|
- /*
|
|
|
- * Some buggy devices can cause an NEC host controller
|
|
|
- * to transition to the "Error" state after a hot port
|
|
|
- * reset. This will show up as the port state in
|
|
|
- * "Inactive", and the port may also report a
|
|
|
- * disconnect. Forcing a warm port reset seems to make
|
|
|
- * the device work.
|
|
|
- *
|
|
|
- * See https://bugzilla.kernel.org/show_bug.cgi?id=41752
|
|
|
- */
|
|
|
- if (hub_port_warm_reset_required(hub, portstatus)) {
|
|
|
- int ret;
|
|
|
-
|
|
|
- if ((portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
- clear_port_feature(hub->hdev, port1,
|
|
|
- USB_PORT_FEAT_C_CONNECTION);
|
|
|
- if (portchange & USB_PORT_STAT_C_LINK_STATE)
|
|
|
- clear_port_feature(hub->hdev, port1,
|
|
|
- USB_PORT_FEAT_C_PORT_LINK_STATE);
|
|
|
- if (portchange & USB_PORT_STAT_C_RESET)
|
|
|
- clear_port_feature(hub->hdev, port1,
|
|
|
- USB_PORT_FEAT_C_RESET);
|
|
|
- dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
|
|
|
- port1);
|
|
|
- ret = hub_port_reset(hub, port1,
|
|
|
- udev, HUB_BH_RESET_TIME,
|
|
|
- true);
|
|
|
- if ((portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
- clear_port_feature(hub->hdev, port1,
|
|
|
- USB_PORT_FEAT_C_CONNECTION);
|
|
|
- return ret;
|
|
|
- }
|
|
|
- /* Device went away? */
|
|
|
- if (!(portstatus & USB_PORT_STAT_CONNECTION))
|
|
|
- return -ENOTCONN;
|
|
|
-
|
|
|
- /* bomb out completely if the connection bounced */
|
|
|
- if ((portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
- return -ENOTCONN;
|
|
|
-
|
|
|
- if ((portstatus & USB_PORT_STAT_ENABLE)) {
|
|
|
- if (hub_is_wusb(hub))
|
|
|
- udev->speed = USB_SPEED_WIRELESS;
|
|
|
- else if (hub_is_superspeed(hub->hdev))
|
|
|
- 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 (!(portstatus & USB_PORT_STAT_CONNECTION) ||
|
|
|
- hub_port_warm_reset_required(hub,
|
|
|
- portstatus))
|
|
|
- return -ENOTCONN;
|
|
|
-
|
|
|
- return 0;
|
|
|
- }
|
|
|
+ if (!(portstatus & USB_PORT_STAT_RESET))
|
|
|
+ break;
|
|
|
|
|
|
-delay:
|
|
|
/* switch to the long delay after two short delay failures */
|
|
|
if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
|
|
|
delay = HUB_LONG_RESET_TIME;
|
|
@@ -2615,20 +2547,54 @@ delay:
|
|
|
port1, warm ? "warm " : "", delay);
|
|
|
}
|
|
|
|
|
|
- return -EBUSY;
|
|
|
+ if ((portstatus & USB_PORT_STAT_RESET))
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ if (hub_port_warm_reset_required(hub, portstatus))
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ /* Device went away? */
|
|
|
+ if (!(portstatus & USB_PORT_STAT_CONNECTION))
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ /* bomb out completely if the connection bounced. A USB 3.0
|
|
|
+ * connection may bounce if multiple warm resets were issued,
|
|
|
+ * but the device may have successfully re-connected. Ignore it.
|
|
|
+ */
|
|
|
+ if (!hub_is_superspeed(hub->hdev) &&
|
|
|
+ (portchange & USB_PORT_STAT_C_CONNECTION))
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ if (!(portstatus & USB_PORT_STAT_ENABLE))
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ if (!udev)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (hub_is_wusb(hub))
|
|
|
+ udev->speed = USB_SPEED_WIRELESS;
|
|
|
+ else if (hub_is_superspeed(hub->hdev))
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
static void hub_port_finish_reset(struct usb_hub *hub, int port1,
|
|
|
- struct usb_device *udev, int *status, bool warm)
|
|
|
+ struct usb_device *udev, int *status)
|
|
|
{
|
|
|
switch (*status) {
|
|
|
case 0:
|
|
|
- if (!warm) {
|
|
|
- struct usb_hcd *hcd;
|
|
|
- /* TRSTRCY = 10 ms; plus some extra */
|
|
|
- msleep(10 + 40);
|
|
|
+ /* TRSTRCY = 10 ms; plus some extra */
|
|
|
+ msleep(10 + 40);
|
|
|
+ if (udev) {
|
|
|
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
|
|
|
+
|
|
|
update_devnum(udev, 0);
|
|
|
- hcd = bus_to_hcd(udev->bus);
|
|
|
/* The xHC may think the device is already reset,
|
|
|
* so ignore the status.
|
|
|
*/
|
|
@@ -2640,14 +2606,15 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
|
|
|
case -ENODEV:
|
|
|
clear_port_feature(hub->hdev,
|
|
|
port1, USB_PORT_FEAT_C_RESET);
|
|
|
- /* FIXME need disconnect() for NOTATTACHED device */
|
|
|
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);
|
|
|
+ clear_port_feature(hub->hdev, port1,
|
|
|
+ USB_PORT_FEAT_C_CONNECTION);
|
|
|
}
|
|
|
- if (!warm)
|
|
|
+ if (udev)
|
|
|
usb_set_device_state(udev, *status
|
|
|
? USB_STATE_NOTATTACHED
|
|
|
: USB_STATE_DEFAULT);
|
|
@@ -2660,18 +2627,30 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|
|
struct usb_device *udev, unsigned int delay, bool warm)
|
|
|
{
|
|
|
int i, status;
|
|
|
+ u16 portchange, portstatus;
|
|
|
|
|
|
- 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)) {
|
|
|
+ if (warm) {
|
|
|
dev_err(hub->intfdev, "only USB3 hub support "
|
|
|
"warm reset\n");
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
+ /* 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 (!warm) {
|
|
|
+ /*
|
|
|
+ * If the caller hasn't explicitly requested a warm reset,
|
|
|
+ * double check and see if one is needed.
|
|
|
+ */
|
|
|
+ status = hub_port_status(hub, port1,
|
|
|
+ &portstatus, &portchange);
|
|
|
+ if (status < 0)
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ if (hub_port_warm_reset_required(hub, portstatus))
|
|
|
+ warm = true;
|
|
|
}
|
|
|
|
|
|
/* Reset the port */
|
|
@@ -2692,10 +2671,33 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|
|
status);
|
|
|
}
|
|
|
|
|
|
- /* return on disconnect or reset */
|
|
|
+ /* Check for disconnect or reset */
|
|
|
if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
|
|
|
- hub_port_finish_reset(hub, port1, udev, &status, warm);
|
|
|
- goto done;
|
|
|
+ hub_port_finish_reset(hub, port1, udev, &status);
|
|
|
+
|
|
|
+ if (!hub_is_superspeed(hub->hdev))
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If a USB 3.0 device migrates from reset to an error
|
|
|
+ * state, re-issue the warm reset.
|
|
|
+ */
|
|
|
+ if (hub_port_status(hub, port1,
|
|
|
+ &portstatus, &portchange) < 0)
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ if (!hub_port_warm_reset_required(hub, portstatus))
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If the port is in SS.Inactive or Compliance Mode, the
|
|
|
+ * hot or warm reset failed. Try another warm reset.
|
|
|
+ */
|
|
|
+ if (!warm) {
|
|
|
+ dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
|
|
|
+ port1);
|
|
|
+ warm = true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
dev_dbg (hub->intfdev,
|
|
@@ -2709,7 +2711,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
|
|
|
port1);
|
|
|
|
|
|
done:
|
|
|
- if (!warm)
|
|
|
+ if (!hub_is_superspeed(hub->hdev))
|
|
|
up_read(&ehci_cf_port_reset_rwsem);
|
|
|
|
|
|
return status;
|
|
@@ -2945,9 +2947,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
|
|
|
|
|
|
/* see 7.1.7.6 */
|
|
|
if (hub_is_superspeed(hub->hdev))
|
|
|
- status = set_port_feature(hub->hdev,
|
|
|
- port1 | (USB_SS_PORT_LS_U3 << 3),
|
|
|
- USB_PORT_FEAT_LINK_STATE);
|
|
|
+ status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U3);
|
|
|
else
|
|
|
status = set_port_feature(hub->hdev, port1,
|
|
|
USB_PORT_FEAT_SUSPEND);
|
|
@@ -3117,9 +3117,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
|
|
|
|
|
|
/* see 7.1.7.7; affects power usage, but not budgeting */
|
|
|
if (hub_is_superspeed(hub->hdev))
|
|
|
- status = set_port_feature(hub->hdev,
|
|
|
- port1 | (USB_SS_PORT_LS_U0 << 3),
|
|
|
- USB_PORT_FEAT_LINK_STATE);
|
|
|
+ status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0);
|
|
|
else
|
|
|
status = clear_port_feature(hub->hdev,
|
|
|
port1, USB_PORT_FEAT_SUSPEND);
|
|
@@ -4700,12 +4698,21 @@ static void hub_events(void)
|
|
|
*/
|
|
|
if (hub_port_warm_reset_required(hub, portstatus)) {
|
|
|
int status;
|
|
|
+ struct usb_device *udev =
|
|
|
+ hub->ports[i - 1]->child;
|
|
|
|
|
|
dev_dbg(hub_dev, "warm reset port %d\n", i);
|
|
|
- status = hub_port_reset(hub, i, NULL,
|
|
|
- HUB_BH_RESET_TIME, true);
|
|
|
- if (status < 0)
|
|
|
- hub_port_disable(hub, i, 1);
|
|
|
+ if (!udev) {
|
|
|
+ status = hub_port_reset(hub, i,
|
|
|
+ NULL, HUB_BH_RESET_TIME,
|
|
|
+ true);
|
|
|
+ if (status < 0)
|
|
|
+ hub_port_disable(hub, i, 1);
|
|
|
+ } else {
|
|
|
+ usb_lock_device(udev);
|
|
|
+ status = usb_reset_device(udev);
|
|
|
+ usb_unlock_device(udev);
|
|
|
+ }
|
|
|
connect_change = 0;
|
|
|
}
|
|
|
|