|
@@ -4996,8 +4996,7 @@ static void port_event(struct usb_hub *hub, int port1)
|
|
|
hub_port_connect_change(hub, port1, portstatus, portchange);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-static void hub_events(void)
|
|
|
+static void hub_event(void)
|
|
|
{
|
|
|
struct list_head *tmp;
|
|
|
struct usb_device *hdev;
|
|
@@ -5008,144 +5007,131 @@ static void hub_events(void)
|
|
|
u16 hubchange;
|
|
|
int i, ret;
|
|
|
|
|
|
- /*
|
|
|
- * We restart the list every time to avoid a deadlock with
|
|
|
- * deleting hubs downstream from this one. This should be
|
|
|
- * safe since we delete the hub from the event list.
|
|
|
- * Not the most efficient, but avoids deadlocks.
|
|
|
- */
|
|
|
- while (1) {
|
|
|
+ /* Grab the first entry at the beginning of the list */
|
|
|
+ spin_lock_irq(&hub_event_lock);
|
|
|
+ if (list_empty(&hub_event_list)) {
|
|
|
+ spin_unlock_irq(&hub_event_lock);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- /* Grab the first entry at the beginning of the list */
|
|
|
- spin_lock_irq(&hub_event_lock);
|
|
|
- if (list_empty(&hub_event_list)) {
|
|
|
- spin_unlock_irq(&hub_event_lock);
|
|
|
- break;
|
|
|
- }
|
|
|
+ tmp = hub_event_list.next;
|
|
|
+ list_del_init(tmp);
|
|
|
|
|
|
- tmp = hub_event_list.next;
|
|
|
- list_del_init(tmp);
|
|
|
+ hub = list_entry(tmp, struct usb_hub, event_list);
|
|
|
+ kref_get(&hub->kref);
|
|
|
+ spin_unlock_irq(&hub_event_lock);
|
|
|
|
|
|
- hub = list_entry(tmp, struct usb_hub, event_list);
|
|
|
- kref_get(&hub->kref);
|
|
|
- spin_unlock_irq(&hub_event_lock);
|
|
|
+ hdev = hub->hdev;
|
|
|
+ hub_dev = hub->intfdev;
|
|
|
+ intf = to_usb_interface(hub_dev);
|
|
|
+ dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
|
|
|
+ hdev->state, hdev->maxchild,
|
|
|
+ /* NOTE: expects max 15 ports... */
|
|
|
+ (u16) hub->change_bits[0],
|
|
|
+ (u16) hub->event_bits[0]);
|
|
|
+
|
|
|
+ /* Lock the device, then check to see if we were
|
|
|
+ * disconnected while waiting for the lock to succeed. */
|
|
|
+ usb_lock_device(hdev);
|
|
|
+ if (unlikely(hub->disconnected))
|
|
|
+ goto out_disconnected;
|
|
|
+
|
|
|
+ /* If the hub has died, clean up after it */
|
|
|
+ if (hdev->state == USB_STATE_NOTATTACHED) {
|
|
|
+ hub->error = -ENODEV;
|
|
|
+ hub_quiesce(hub, HUB_DISCONNECT);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Autoresume */
|
|
|
+ ret = usb_autopm_get_interface(intf);
|
|
|
+ if (ret) {
|
|
|
+ dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
|
|
|
- hdev = hub->hdev;
|
|
|
- hub_dev = hub->intfdev;
|
|
|
- intf = to_usb_interface(hub_dev);
|
|
|
- dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
|
|
|
- hdev->state, hdev->maxchild,
|
|
|
- /* NOTE: expects max 15 ports... */
|
|
|
- (u16) hub->change_bits[0],
|
|
|
- (u16) hub->event_bits[0]);
|
|
|
-
|
|
|
- /* Lock the device, then check to see if we were
|
|
|
- * disconnected while waiting for the lock to succeed. */
|
|
|
- usb_lock_device(hdev);
|
|
|
- if (unlikely(hub->disconnected))
|
|
|
- goto loop_disconnected;
|
|
|
-
|
|
|
- /* If the hub has died, clean up after it */
|
|
|
- if (hdev->state == USB_STATE_NOTATTACHED) {
|
|
|
- hub->error = -ENODEV;
|
|
|
- hub_quiesce(hub, HUB_DISCONNECT);
|
|
|
- goto loop;
|
|
|
- }
|
|
|
+ /* If this is an inactive hub, do nothing */
|
|
|
+ if (hub->quiescing)
|
|
|
+ goto out_autopm;
|
|
|
+
|
|
|
+ if (hub->error) {
|
|
|
+ dev_dbg(hub_dev, "resetting for error %d\n", hub->error);
|
|
|
|
|
|
- /* Autoresume */
|
|
|
- ret = usb_autopm_get_interface(intf);
|
|
|
+ ret = usb_reset_device(hdev);
|
|
|
if (ret) {
|
|
|
- dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
|
|
|
- goto loop;
|
|
|
+ dev_dbg(hub_dev, "error resetting hub: %d\n", ret);
|
|
|
+ goto out_autopm;
|
|
|
}
|
|
|
|
|
|
- /* If this is an inactive hub, do nothing */
|
|
|
- if (hub->quiescing)
|
|
|
- goto loop_autopm;
|
|
|
+ hub->nerrors = 0;
|
|
|
+ hub->error = 0;
|
|
|
+ }
|
|
|
|
|
|
- if (hub->error) {
|
|
|
- dev_dbg (hub_dev, "resetting for error %d\n",
|
|
|
- hub->error);
|
|
|
+ /* deal with port status changes */
|
|
|
+ for (i = 1; i <= hdev->maxchild; i++) {
|
|
|
+ struct usb_port *port_dev = hub->ports[i - 1];
|
|
|
|
|
|
- ret = usb_reset_device(hdev);
|
|
|
- if (ret) {
|
|
|
- dev_dbg (hub_dev,
|
|
|
- "error resetting hub: %d\n", ret);
|
|
|
- goto loop_autopm;
|
|
|
- }
|
|
|
-
|
|
|
- hub->nerrors = 0;
|
|
|
- hub->error = 0;
|
|
|
+ if (test_bit(i, hub->event_bits)
|
|
|
+ || test_bit(i, hub->change_bits)
|
|
|
+ || test_bit(i, hub->wakeup_bits)) {
|
|
|
+ /*
|
|
|
+ * The get_noresume and barrier ensure that if
|
|
|
+ * the port was in the process of resuming, we
|
|
|
+ * flush that work and keep the port active for
|
|
|
+ * the duration of the port_event(). However,
|
|
|
+ * if the port is runtime pm suspended
|
|
|
+ * (powered-off), we leave it in that state, run
|
|
|
+ * an abbreviated port_event(), and move on.
|
|
|
+ */
|
|
|
+ pm_runtime_get_noresume(&port_dev->dev);
|
|
|
+ pm_runtime_barrier(&port_dev->dev);
|
|
|
+ usb_lock_port(port_dev);
|
|
|
+ port_event(hub, i);
|
|
|
+ usb_unlock_port(port_dev);
|
|
|
+ pm_runtime_put_sync(&port_dev->dev);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- /* deal with port status changes */
|
|
|
- for (i = 1; i <= hdev->maxchild; i++) {
|
|
|
- struct usb_port *port_dev = hub->ports[i - 1];
|
|
|
-
|
|
|
- if (test_bit(i, hub->event_bits)
|
|
|
- || test_bit(i, hub->change_bits)
|
|
|
- || test_bit(i, hub->wakeup_bits)) {
|
|
|
- /*
|
|
|
- * The get_noresume and barrier ensure that if
|
|
|
- * the port was in the process of resuming, we
|
|
|
- * flush that work and keep the port active for
|
|
|
- * the duration of the port_event(). However,
|
|
|
- * if the port is runtime pm suspended
|
|
|
- * (powered-off), we leave it in that state, run
|
|
|
- * an abbreviated port_event(), and move on.
|
|
|
- */
|
|
|
- pm_runtime_get_noresume(&port_dev->dev);
|
|
|
- pm_runtime_barrier(&port_dev->dev);
|
|
|
- usb_lock_port(port_dev);
|
|
|
- port_event(hub, i);
|
|
|
- usb_unlock_port(port_dev);
|
|
|
- pm_runtime_put_sync(&port_dev->dev);
|
|
|
- }
|
|
|
+ /* deal with hub status changes */
|
|
|
+ if (test_and_clear_bit(0, hub->event_bits) == 0)
|
|
|
+ ; /* do nothing */
|
|
|
+ else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
|
|
|
+ dev_err(hub_dev, "get_hub_status failed\n");
|
|
|
+ else {
|
|
|
+ if (hubchange & HUB_CHANGE_LOCAL_POWER) {
|
|
|
+ dev_dbg(hub_dev, "power change\n");
|
|
|
+ clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
|
|
|
+ if (hubstatus & HUB_STATUS_LOCAL_POWER)
|
|
|
+ /* FIXME: Is this always true? */
|
|
|
+ hub->limited_power = 1;
|
|
|
+ else
|
|
|
+ hub->limited_power = 0;
|
|
|
}
|
|
|
+ if (hubchange & HUB_CHANGE_OVERCURRENT) {
|
|
|
+ u16 status = 0;
|
|
|
+ u16 unused;
|
|
|
|
|
|
- /* deal with hub status changes */
|
|
|
- if (test_and_clear_bit(0, hub->event_bits) == 0)
|
|
|
- ; /* do nothing */
|
|
|
- else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
|
|
|
- dev_err (hub_dev, "get_hub_status failed\n");
|
|
|
- else {
|
|
|
- if (hubchange & HUB_CHANGE_LOCAL_POWER) {
|
|
|
- dev_dbg (hub_dev, "power change\n");
|
|
|
- clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
|
|
|
- if (hubstatus & HUB_STATUS_LOCAL_POWER)
|
|
|
- /* FIXME: Is this always true? */
|
|
|
- hub->limited_power = 1;
|
|
|
- else
|
|
|
- hub->limited_power = 0;
|
|
|
- }
|
|
|
- if (hubchange & HUB_CHANGE_OVERCURRENT) {
|
|
|
- u16 status = 0;
|
|
|
- u16 unused;
|
|
|
-
|
|
|
- dev_dbg(hub_dev, "over-current change\n");
|
|
|
- clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
|
|
|
- msleep(500); /* Cool down */
|
|
|
- hub_power_on(hub, true);
|
|
|
- hub_hub_status(hub, &status, &unused);
|
|
|
- if (status & HUB_STATUS_OVERCURRENT)
|
|
|
- dev_err(hub_dev, "over-current "
|
|
|
- "condition\n");
|
|
|
- }
|
|
|
+ dev_dbg(hub_dev, "over-current change\n");
|
|
|
+ clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
|
|
|
+ msleep(500); /* Cool down */
|
|
|
+ hub_power_on(hub, true);
|
|
|
+ hub_hub_status(hub, &status, &unused);
|
|
|
+ if (status & HUB_STATUS_OVERCURRENT)
|
|
|
+ dev_err(hub_dev, "over-current condition\n");
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- loop_autopm:
|
|
|
- /* Balance the usb_autopm_get_interface() above */
|
|
|
- usb_autopm_put_interface_no_suspend(intf);
|
|
|
- loop:
|
|
|
- /* Balance the usb_autopm_get_interface_no_resume() in
|
|
|
- * kick_khubd() and allow autosuspend.
|
|
|
- */
|
|
|
- usb_autopm_put_interface(intf);
|
|
|
- loop_disconnected:
|
|
|
- usb_unlock_device(hdev);
|
|
|
- kref_put(&hub->kref, hub_release);
|
|
|
-
|
|
|
- } /* end while (1) */
|
|
|
+out_autopm:
|
|
|
+ /* Balance the usb_autopm_get_interface() above */
|
|
|
+ usb_autopm_put_interface_no_suspend(intf);
|
|
|
+out:
|
|
|
+ /* Balance the usb_autopm_get_interface_no_resume() in
|
|
|
+ * kick_khubd() and allow autosuspend.
|
|
|
+ */
|
|
|
+ usb_autopm_put_interface(intf);
|
|
|
+out_disconnected:
|
|
|
+ usb_unlock_device(hdev);
|
|
|
+ kref_put(&hub->kref, hub_release);
|
|
|
}
|
|
|
|
|
|
static int hub_thread(void *__unused)
|
|
@@ -5158,7 +5144,7 @@ static int hub_thread(void *__unused)
|
|
|
set_freezable();
|
|
|
|
|
|
do {
|
|
|
- hub_events();
|
|
|
+ hub_event();
|
|
|
wait_event_freezable(khubd_wait,
|
|
|
!list_empty(&hub_event_list) ||
|
|
|
kthread_should_stop());
|