|
@@ -4413,66 +4413,15 @@ hub_power_remaining (struct usb_hub *hub)
|
|
|
return remaining;
|
|
|
}
|
|
|
|
|
|
-/* Handle physical or logical connection change events.
|
|
|
- * This routine is called when:
|
|
|
- * a port connection-change occurs;
|
|
|
- * a port enable-change occurs (often caused by EMI);
|
|
|
- * usb_reset_and_verify_device() encounters changed descriptors (as from
|
|
|
- * a firmware download)
|
|
|
- * caller already locked the hub
|
|
|
- */
|
|
|
-static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
|
|
- u16 portstatus, u16 portchange)
|
|
|
+static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
|
|
|
+ u16 portchange)
|
|
|
{
|
|
|
+ int status, i;
|
|
|
+ unsigned unit_load;
|
|
|
struct usb_device *hdev = hub->hdev;
|
|
|
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
|
|
|
struct usb_port *port_dev = hub->ports[port1 - 1];
|
|
|
- struct usb_device *udev;
|
|
|
- int status, i;
|
|
|
- unsigned unit_load;
|
|
|
-
|
|
|
- dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n",
|
|
|
- portstatus, portchange, portspeed(hub, portstatus));
|
|
|
-
|
|
|
- if (hub->has_indicators) {
|
|
|
- set_port_led(hub, port1, HUB_LED_AUTO);
|
|
|
- hub->indicator[port1-1] = INDICATOR_AUTO;
|
|
|
- }
|
|
|
-
|
|
|
-#ifdef CONFIG_USB_OTG
|
|
|
- /* during HNP, don't repeat the debounce */
|
|
|
- if (hdev->bus->is_b_host)
|
|
|
- portchange &= ~(USB_PORT_STAT_C_CONNECTION |
|
|
|
- USB_PORT_STAT_C_ENABLE);
|
|
|
-#endif
|
|
|
-
|
|
|
- /* Try to resuscitate an existing device */
|
|
|
- udev = port_dev->child;
|
|
|
- if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
|
|
|
- udev->state != USB_STATE_NOTATTACHED) {
|
|
|
- usb_lock_device(udev);
|
|
|
- if (portstatus & USB_PORT_STAT_ENABLE) {
|
|
|
- status = 0; /* Nothing to do */
|
|
|
-
|
|
|
-#ifdef CONFIG_PM_RUNTIME
|
|
|
- } else if (udev->state == USB_STATE_SUSPENDED &&
|
|
|
- udev->persist_enabled) {
|
|
|
- /* For a suspended device, treat this as a
|
|
|
- * remote wakeup event.
|
|
|
- */
|
|
|
- status = usb_remote_wakeup(udev);
|
|
|
-#endif
|
|
|
-
|
|
|
- } else {
|
|
|
- status = -ENODEV; /* Don't resuscitate */
|
|
|
- }
|
|
|
- usb_unlock_device(udev);
|
|
|
-
|
|
|
- if (status == 0) {
|
|
|
- clear_bit(port1, hub->change_bits);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
+ struct usb_device *udev = port_dev->child;
|
|
|
|
|
|
/* Disconnect any existing devices under this port */
|
|
|
if (udev) {
|
|
@@ -4481,7 +4430,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
|
|
usb_phy_notify_disconnect(hcd->phy, udev->speed);
|
|
|
usb_disconnect(&port_dev->child);
|
|
|
}
|
|
|
- clear_bit(port1, hub->change_bits);
|
|
|
|
|
|
/* We can forget about a "removed" device when there's a physical
|
|
|
* disconnect or the connect status changes.
|
|
@@ -4663,6 +4611,65 @@ done:
|
|
|
hub_port_disable(hub, port1, 1);
|
|
|
if (hcd->driver->relinquish_port && !hub->hdev->parent)
|
|
|
hcd->driver->relinquish_port(hcd, port1);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/* Handle physical or logical connection change events.
|
|
|
+ * This routine is called when:
|
|
|
+ * a port connection-change occurs;
|
|
|
+ * a port enable-change occurs (often caused by EMI);
|
|
|
+ * usb_reset_and_verify_device() encounters changed descriptors (as from
|
|
|
+ * a firmware download)
|
|
|
+ * caller already locked the hub
|
|
|
+ */
|
|
|
+static void hub_port_connect_change(struct usb_hub *hub, int port1,
|
|
|
+ u16 portstatus, u16 portchange)
|
|
|
+{
|
|
|
+ struct usb_port *port_dev = hub->ports[port1 - 1];
|
|
|
+ struct usb_device *udev = port_dev->child;
|
|
|
+ int status = -ENODEV;
|
|
|
+
|
|
|
+ dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", portstatus,
|
|
|
+ portchange, portspeed(hub, portstatus));
|
|
|
+
|
|
|
+ if (hub->has_indicators) {
|
|
|
+ set_port_led(hub, port1, HUB_LED_AUTO);
|
|
|
+ hub->indicator[port1-1] = INDICATOR_AUTO;
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef CONFIG_USB_OTG
|
|
|
+ /* during HNP, don't repeat the debounce */
|
|
|
+ if (hub->hdev->bus->is_b_host)
|
|
|
+ portchange &= ~(USB_PORT_STAT_C_CONNECTION |
|
|
|
+ USB_PORT_STAT_C_ENABLE);
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* Try to resuscitate an existing device */
|
|
|
+ if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
|
|
|
+ udev->state != USB_STATE_NOTATTACHED) {
|
|
|
+ if (portstatus & USB_PORT_STAT_ENABLE) {
|
|
|
+ status = 0; /* Nothing to do */
|
|
|
+#ifdef CONFIG_PM_RUNTIME
|
|
|
+ } else if (udev->state == USB_STATE_SUSPENDED &&
|
|
|
+ udev->persist_enabled) {
|
|
|
+ /* For a suspended device, treat this as a
|
|
|
+ * remote wakeup event.
|
|
|
+ */
|
|
|
+ usb_lock_device(udev);
|
|
|
+ status = usb_remote_wakeup(udev);
|
|
|
+ usb_unlock_device(udev);
|
|
|
+#endif
|
|
|
+ } else {
|
|
|
+ /* Don't resuscitate */;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ clear_bit(port1, hub->change_bits);
|
|
|
+
|
|
|
+ if (status == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ hub_port_connect(hub, port1, portstatus, portchange);
|
|
|
}
|
|
|
|
|
|
/* Returns 1 if there was a remote wakeup and a connect status change. */
|
|
@@ -4705,6 +4712,121 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
|
|
|
return connect_change;
|
|
|
}
|
|
|
|
|
|
+static void port_event(struct usb_hub *hub, int port1)
|
|
|
+{
|
|
|
+ int connect_change, reset_device = 0;
|
|
|
+ struct usb_port *port_dev = hub->ports[port1 - 1];
|
|
|
+ struct usb_device *udev = port_dev->child;
|
|
|
+ struct usb_device *hdev = hub->hdev;
|
|
|
+ u16 portstatus, portchange;
|
|
|
+
|
|
|
+ connect_change = test_bit(port1, hub->change_bits);
|
|
|
+ clear_bit(port1, hub->event_bits);
|
|
|
+ clear_bit(port1, hub->wakeup_bits);
|
|
|
+
|
|
|
+ if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
|
|
+ usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
|
|
|
+ connect_change = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (portchange & USB_PORT_STAT_C_ENABLE) {
|
|
|
+ if (!connect_change)
|
|
|
+ dev_dbg(&port_dev->dev, "enable change, status %08x\n",
|
|
|
+ portstatus);
|
|
|
+ usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * EM interference sometimes causes badly shielded USB devices
|
|
|
+ * to be shutdown by the hub, this hack enables them again.
|
|
|
+ * Works at least with mouse driver.
|
|
|
+ */
|
|
|
+ if (!(portstatus & USB_PORT_STAT_ENABLE)
|
|
|
+ && !connect_change && udev) {
|
|
|
+ dev_err(&port_dev->dev, "disabled by hub (EMI?), re-enabling...\n");
|
|
|
+ connect_change = 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
|
|
|
+ u16 status = 0, unused;
|
|
|
+
|
|
|
+ dev_dbg(&port_dev->dev, "over-current change\n");
|
|
|
+ usb_clear_port_feature(hdev, port1,
|
|
|
+ USB_PORT_FEAT_C_OVER_CURRENT);
|
|
|
+ msleep(100); /* Cool down */
|
|
|
+ hub_power_on(hub, true);
|
|
|
+ hub_port_status(hub, port1, &status, &unused);
|
|
|
+ if (status & USB_PORT_STAT_OVERCURRENT)
|
|
|
+ dev_err(&port_dev->dev, "over-current condition\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (portchange & USB_PORT_STAT_C_RESET) {
|
|
|
+ dev_dbg(&port_dev->dev, "reset change\n");
|
|
|
+ usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_RESET);
|
|
|
+ }
|
|
|
+ if ((portchange & USB_PORT_STAT_C_BH_RESET)
|
|
|
+ && hub_is_superspeed(hdev)) {
|
|
|
+ dev_dbg(&port_dev->dev, "warm reset change\n");
|
|
|
+ usb_clear_port_feature(hdev, port1,
|
|
|
+ USB_PORT_FEAT_C_BH_PORT_RESET);
|
|
|
+ }
|
|
|
+ if (portchange & USB_PORT_STAT_C_LINK_STATE) {
|
|
|
+ dev_dbg(&port_dev->dev, "link state change\n");
|
|
|
+ usb_clear_port_feature(hdev, port1,
|
|
|
+ USB_PORT_FEAT_C_PORT_LINK_STATE);
|
|
|
+ }
|
|
|
+ if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
|
|
|
+ dev_warn(&port_dev->dev, "config error\n");
|
|
|
+ usb_clear_port_feature(hdev, port1,
|
|
|
+ USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange))
|
|
|
+ connect_change = 1;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Warm reset a USB3 protocol port if it's in
|
|
|
+ * SS.Inactive state.
|
|
|
+ */
|
|
|
+ if (hub_port_warm_reset_required(hub, portstatus)) {
|
|
|
+ dev_dbg(&port_dev->dev, "do warm reset\n");
|
|
|
+ if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
|
|
|
+ || udev->state == USB_STATE_NOTATTACHED) {
|
|
|
+ if (hub_port_reset(hub, port1, NULL,
|
|
|
+ HUB_BH_RESET_TIME, true) < 0)
|
|
|
+ hub_port_disable(hub, port1, 1);
|
|
|
+ } else
|
|
|
+ reset_device = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * On disconnect USB3 protocol ports transit from U0 to
|
|
|
+ * SS.Inactive to Rx.Detect. If this happens a warm-
|
|
|
+ * reset is not needed, but a (re)connect may happen
|
|
|
+ * before khubd runs and sees the disconnect, and the
|
|
|
+ * device may be an unknown state.
|
|
|
+ *
|
|
|
+ * If the port went through SS.Inactive without khubd
|
|
|
+ * seeing it the C_LINK_STATE change flag will be set,
|
|
|
+ * and we reset the dev to put it in a known state.
|
|
|
+ */
|
|
|
+ if (reset_device || (udev && hub_is_superspeed(hub->hdev)
|
|
|
+ && (portchange & USB_PORT_STAT_C_LINK_STATE)
|
|
|
+ && (portstatus & USB_PORT_STAT_CONNECTION))) {
|
|
|
+ usb_lock_device(udev);
|
|
|
+ usb_reset_device(udev);
|
|
|
+ usb_unlock_device(udev);
|
|
|
+ connect_change = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (connect_change)
|
|
|
+ hub_port_connect_change(hub, port1, portstatus, portchange);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
static void hub_events(void)
|
|
|
{
|
|
|
struct list_head *tmp;
|
|
@@ -4714,10 +4836,7 @@ static void hub_events(void)
|
|
|
struct device *hub_dev;
|
|
|
u16 hubstatus;
|
|
|
u16 hubchange;
|
|
|
- u16 portstatus;
|
|
|
- u16 portchange;
|
|
|
int i, ret;
|
|
|
- int connect_change, wakeup_change;
|
|
|
|
|
|
/*
|
|
|
* We restart the list every time to avoid a deadlock with
|
|
@@ -4791,135 +4910,12 @@ static void hub_events(void)
|
|
|
|
|
|
/* deal with port status changes */
|
|
|
for (i = 1; i <= hdev->maxchild; i++) {
|
|
|
- struct usb_port *port_dev = hub->ports[i - 1];
|
|
|
- struct usb_device *udev = port_dev->child;
|
|
|
-
|
|
|
- if (test_bit(i, hub->busy_bits))
|
|
|
- continue;
|
|
|
- connect_change = test_bit(i, hub->change_bits);
|
|
|
- wakeup_change = test_and_clear_bit(i, hub->wakeup_bits);
|
|
|
- if (!test_and_clear_bit(i, hub->event_bits) &&
|
|
|
- !connect_change && !wakeup_change)
|
|
|
- continue;
|
|
|
-
|
|
|
- ret = hub_port_status(hub, i,
|
|
|
- &portstatus, &portchange);
|
|
|
- if (ret < 0)
|
|
|
- continue;
|
|
|
-
|
|
|
- if (portchange & USB_PORT_STAT_C_CONNECTION) {
|
|
|
- usb_clear_port_feature(hdev, i,
|
|
|
- USB_PORT_FEAT_C_CONNECTION);
|
|
|
- connect_change = 1;
|
|
|
- }
|
|
|
-
|
|
|
- if (portchange & USB_PORT_STAT_C_ENABLE) {
|
|
|
- if (!connect_change)
|
|
|
- dev_dbg(&port_dev->dev,
|
|
|
- "enable change, status %08x\n",
|
|
|
- portstatus);
|
|
|
- usb_clear_port_feature(hdev, i,
|
|
|
- USB_PORT_FEAT_C_ENABLE);
|
|
|
-
|
|
|
- /*
|
|
|
- * EM interference sometimes causes badly
|
|
|
- * shielded USB devices to be shutdown by
|
|
|
- * the hub, this hack enables them again.
|
|
|
- * Works at least with mouse driver.
|
|
|
- */
|
|
|
- if (!(portstatus & USB_PORT_STAT_ENABLE)
|
|
|
- && !connect_change && udev) {
|
|
|
- dev_err(&port_dev->dev,
|
|
|
- "disabled by hub (EMI?), re-enabling...\n");
|
|
|
- connect_change = 1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (hub_handle_remote_wakeup(hub, i,
|
|
|
- portstatus, portchange))
|
|
|
- connect_change = 1;
|
|
|
-
|
|
|
- if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
|
|
|
- u16 status = 0;
|
|
|
- u16 unused;
|
|
|
-
|
|
|
- dev_dbg(&port_dev->dev, "over-current change\n");
|
|
|
- usb_clear_port_feature(hdev, i,
|
|
|
- USB_PORT_FEAT_C_OVER_CURRENT);
|
|
|
- msleep(100); /* Cool down */
|
|
|
- hub_power_on(hub, true);
|
|
|
- hub_port_status(hub, i, &status, &unused);
|
|
|
- if (status & USB_PORT_STAT_OVERCURRENT)
|
|
|
- dev_err(&port_dev->dev,
|
|
|
- "over-current condition\n");
|
|
|
- }
|
|
|
-
|
|
|
- if (portchange & USB_PORT_STAT_C_RESET) {
|
|
|
- dev_dbg(&port_dev->dev, "reset change\n");
|
|
|
- usb_clear_port_feature(hdev, i,
|
|
|
- USB_PORT_FEAT_C_RESET);
|
|
|
- }
|
|
|
- if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
|
|
|
- hub_is_superspeed(hub->hdev)) {
|
|
|
- dev_dbg(&port_dev->dev, "warm reset change\n");
|
|
|
- usb_clear_port_feature(hdev, i,
|
|
|
- USB_PORT_FEAT_C_BH_PORT_RESET);
|
|
|
- }
|
|
|
- if (portchange & USB_PORT_STAT_C_LINK_STATE) {
|
|
|
- usb_clear_port_feature(hub->hdev, i,
|
|
|
- USB_PORT_FEAT_C_PORT_LINK_STATE);
|
|
|
- }
|
|
|
- if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
|
|
|
- dev_warn(&port_dev->dev, "config error\n");
|
|
|
- usb_clear_port_feature(hub->hdev, i,
|
|
|
- USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
|
|
|
- }
|
|
|
-
|
|
|
- /* Warm reset a USB3 protocol port if it's in
|
|
|
- * SS.Inactive state.
|
|
|
- */
|
|
|
- if (hub_port_warm_reset_required(hub, portstatus)) {
|
|
|
- int status;
|
|
|
-
|
|
|
- dev_dbg(&port_dev->dev, "warm reset\n");
|
|
|
- if (!udev ||
|
|
|
- !(portstatus & USB_PORT_STAT_CONNECTION) ||
|
|
|
- udev->state == USB_STATE_NOTATTACHED) {
|
|
|
- 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;
|
|
|
- }
|
|
|
- /*
|
|
|
- * On disconnect USB3 protocol ports transit from U0 to
|
|
|
- * SS.Inactive to Rx.Detect. If this happens a warm-
|
|
|
- * reset is not needed, but a (re)connect may happen
|
|
|
- * before khubd runs and sees the disconnect, and the
|
|
|
- * device may be an unknown state.
|
|
|
- *
|
|
|
- * If the port went through SS.Inactive without khubd
|
|
|
- * seeing it the C_LINK_STATE change flag will be set,
|
|
|
- * and we reset the dev to put it in a known state.
|
|
|
- */
|
|
|
- } else if (udev && hub_is_superspeed(hub->hdev) &&
|
|
|
- (portchange & USB_PORT_STAT_C_LINK_STATE) &&
|
|
|
- (portstatus & USB_PORT_STAT_CONNECTION)) {
|
|
|
- usb_lock_device(udev);
|
|
|
- usb_reset_device(udev);
|
|
|
- usb_unlock_device(udev);
|
|
|
- connect_change = 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (connect_change)
|
|
|
- hub_port_connect_change(hub, i,
|
|
|
- portstatus, portchange);
|
|
|
- } /* end for i */
|
|
|
+ if (!test_bit(i, hub->busy_bits)
|
|
|
+ && (test_bit(i, hub->event_bits)
|
|
|
+ || test_bit(i, hub->change_bits)
|
|
|
+ || test_bit(i, hub->wakeup_bits)))
|
|
|
+ port_event(hub, i);
|
|
|
+ }
|
|
|
|
|
|
/* deal with hub status changes */
|
|
|
if (test_and_clear_bit(0, hub->event_bits) == 0)
|