summaryrefslogtreecommitdiff
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c431
1 files changed, 198 insertions, 233 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index d481c99a20d7..11e80ac31324 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -22,9 +22,8 @@
#include <linux/usb/hcd.h>
#include <linux/usb/otg.h>
#include <linux/usb/quirks.h>
-#include <linux/kthread.h>
+#include <linux/workqueue.h>
#include <linux/mutex.h>
-#include <linux/freezer.h>
#include <linux/random.h>
#include <linux/pm_qos.h>
@@ -32,6 +31,7 @@
#include <asm/byteorder.h>
#include "hub.h"
+#include "otg_whitelist.h"
#define USB_VENDOR_GENESYS_LOGIC 0x05e3
#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01
@@ -41,14 +41,9 @@
* change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */
static DEFINE_SPINLOCK(device_state_lock);
-/* khubd's worklist and its lock */
-static DEFINE_SPINLOCK(hub_event_lock);
-static LIST_HEAD(hub_event_list); /* List of hubs needing servicing */
-
-/* Wakes up khubd */
-static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
-
-static struct task_struct *khubd_task;
+/* workqueue to process hub events */
+static struct workqueue_struct *hub_wq;
+static void hub_event(struct work_struct *work);
/* synchronize hub-port add/remove and peering operations */
DEFINE_MUTEX(usb_port_peer_mutex);
@@ -104,6 +99,7 @@ EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
#define HUB_DEBOUNCE_STEP 25
#define HUB_DEBOUNCE_STABLE 100
+static void hub_release(struct kref *kref);
static int usb_reset_and_verify_device(struct usb_device *udev);
static inline char *portspeed(struct usb_hub *hub, int portstatus)
@@ -575,28 +571,39 @@ static int hub_port_status(struct usb_hub *hub, int port1,
return ret;
}
-static void kick_khubd(struct usb_hub *hub)
+static void kick_hub_wq(struct usb_hub *hub)
{
- unsigned long flags;
+ struct usb_interface *intf;
- spin_lock_irqsave(&hub_event_lock, flags);
- if (!hub->disconnected && list_empty(&hub->event_list)) {
- list_add_tail(&hub->event_list, &hub_event_list);
+ if (hub->disconnected || work_pending(&hub->events))
+ return;
- /* Suppress autosuspend until khubd runs */
- usb_autopm_get_interface_no_resume(
- to_usb_interface(hub->intfdev));
- wake_up(&khubd_wait);
- }
- spin_unlock_irqrestore(&hub_event_lock, flags);
+ /*
+ * Suppress autosuspend until the event is proceed.
+ *
+ * Be careful and make sure that the symmetric operation is
+ * always called. We are here only when there is no pending
+ * work for this hub. Therefore put the interface either when
+ * the new work is called or when it is canceled.
+ */
+ intf = to_usb_interface(hub->intfdev);
+ usb_autopm_get_interface_no_resume(intf);
+ kref_get(&hub->kref);
+
+ if (queue_work(hub_wq, &hub->events))
+ return;
+
+ /* the work has already been scheduled */
+ usb_autopm_put_interface_async(intf);
+ kref_put(&hub->kref, hub_release);
}
-void usb_kick_khubd(struct usb_device *hdev)
+void usb_kick_hub_wq(struct usb_device *hdev)
{
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
if (hub)
- kick_khubd(hub);
+ kick_hub_wq(hub);
}
/*
@@ -618,7 +625,7 @@ void usb_wakeup_notification(struct usb_device *hdev,
hub = usb_hub_to_struct_hub(hdev);
if (hub) {
set_bit(portnum, hub->wakeup_bits);
- kick_khubd(hub);
+ kick_hub_wq(hub);
}
}
EXPORT_SYMBOL_GPL(usb_wakeup_notification);
@@ -645,7 +652,7 @@ static void hub_irq(struct urb *urb)
hub->error = status;
/* FALL THROUGH */
- /* let khubd handle things */
+ /* let hub_wq handle things */
case 0: /* we got data: port status changed */
bits = 0;
for (i = 0; i < urb->actual_length; ++i)
@@ -657,8 +664,8 @@ static void hub_irq(struct urb *urb)
hub->nerrors = 0;
- /* Something happened, let khubd figure it out */
- kick_khubd(hub);
+ /* Something happened, let hub_wq figure it out */
+ kick_hub_wq(hub);
resubmit:
if (hub->quiescing)
@@ -688,7 +695,7 @@ hub_clear_tt_buffer (struct usb_device *hdev, u16 devinfo, u16 tt)
}
/*
- * enumeration blocks khubd for a long time. we use keventd instead, since
+ * enumeration blocks hub_wq for a long time. we use keventd instead, since
* long blocking there is the exception, not the rule. accordingly, HCDs
* talking to TTs must queue control transfers (not just bulk and iso), so
* both can talk to the same hub concurrently.
@@ -954,7 +961,7 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
/*
* Disable a port and mark a logical connect-change event, so that some
- * time later khubd will disconnect() any existing usb_device on the port
+ * time later hub_wq will disconnect() any existing usb_device on the port
* and will re-enumerate if there actually is a device attached.
*/
static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
@@ -967,12 +974,12 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
* - SRP saves power that way
* - ... new call, TBD ...
* That's easy if this hub can switch power per-port, and
- * khubd reactivates the port later (timer, SRP, etc).
+ * hub_wq reactivates the port later (timer, SRP, etc).
* Powerdown must be optional, because of reset/DFU.
*/
set_bit(port1, hub->change_bits);
- kick_khubd(hub);
+ kick_hub_wq(hub);
}
/**
@@ -980,7 +987,7 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
* @udev: device to be disabled and removed
* Context: @udev locked, must be able to sleep.
*
- * After @udev's port has been disabled, khubd is notified and it will
+ * After @udev's port has been disabled, hub_wq is notified and it will
* see that the device has been disconnected. When the device is
* physically unplugged and something is plugged in, the events will
* be received and processed normally.
@@ -1100,7 +1107,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
init2:
/*
- * Check each port and set hub->change_bits to let khubd know
+ * Check each port and set hub->change_bits to let hub_wq know
* which ports need attention.
*/
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
@@ -1167,7 +1174,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
clear_bit(port1, hub->removed_bits);
if (!udev || udev->state == USB_STATE_NOTATTACHED) {
- /* Tell khubd to disconnect the device or
+ /* Tell hub_wq to disconnect the device or
* check for a new connection
*/
if (udev || (portstatus & USB_PORT_STAT_CONNECTION) ||
@@ -1180,7 +1187,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
USB_SS_PORT_LS_U0;
/* The power session apparently survived the resume.
* If there was an overcurrent or suspend change
- * (i.e., remote wakeup request), have khubd
+ * (i.e., remote wakeup request), have hub_wq
* take care of it. Look at the port link state
* for USB 3.0 hubs, since they don't have a suspend
* change bit, and they don't set the port link change
@@ -1201,7 +1208,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
set_bit(port1, hub->change_bits);
} else {
- /* The power session is gone; tell khubd */
+ /* The power session is gone; tell hub_wq */
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
set_bit(port1, hub->change_bits);
}
@@ -1209,10 +1216,10 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
/* If no port-status-change flags were set, we don't need any
* debouncing. If flags were set we can try to debounce the
- * ports all at once right now, instead of letting khubd do them
+ * ports all at once right now, instead of letting hub_wq do them
* one at a time later on.
*
- * If any port-status changes do occur during this delay, khubd
+ * If any port-status changes do occur during this delay, hub_wq
* will see them later and handle them normally.
*/
if (need_debounce_delay) {
@@ -1240,7 +1247,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
&hub->leds, LED_CYCLE_PERIOD);
/* Scan all ports that need attention */
- kick_khubd(hub);
+ kick_hub_wq(hub);
/* Allow autosuspend if it was suppressed */
if (type <= HUB_INIT3)
@@ -1273,7 +1280,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
cancel_delayed_work_sync(&hub->init_work);
- /* khubd and related activity won't re-trigger */
+ /* hub_wq and related activity won't re-trigger */
hub->quiescing = 1;
if (type != HUB_SUSPEND) {
@@ -1284,7 +1291,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
}
}
- /* Stop khubd and related activity */
+ /* Stop hub_wq and related activity */
usb_kill_urb(hub->urb);
if (hub->has_indicators)
cancel_delayed_work_sync(&hub->leds);
@@ -1606,7 +1613,7 @@ static int hub_configure(struct usb_hub *hub,
if (ret < 0)
goto fail;
- /* Update the HCD's internal representation of this hub before khubd
+ /* Update the HCD's internal representation of this hub before hub_wq
* starts getting port status changes for devices under the hub.
*/
if (hcd->driver->update_hub_device) {
@@ -1634,6 +1641,7 @@ static void hub_release(struct kref *kref)
{
struct usb_hub *hub = container_of(kref, struct usb_hub, kref);
+ usb_put_dev(hub->hdev);
usb_put_intf(to_usb_interface(hub->intfdev));
kfree(hub);
}
@@ -1646,14 +1654,11 @@ static void hub_disconnect(struct usb_interface *intf)
struct usb_device *hdev = interface_to_usbdev(intf);
int port1;
- /* Take the hub off the event list and don't let it be added again */
- spin_lock_irq(&hub_event_lock);
- if (!list_empty(&hub->event_list)) {
- list_del_init(&hub->event_list);
- usb_autopm_put_interface_no_suspend(intf);
- }
+ /*
+ * Stop adding new hub events. We do not want to block here and thus
+ * will not try to remove any pending work item.
+ */
hub->disconnected = 1;
- spin_unlock_irq(&hub_event_lock);
/* Disconnect all children and quiesce the hub */
hub->error = 0;
@@ -1793,12 +1798,13 @@ descriptor_error:
}
kref_init(&hub->kref);
- INIT_LIST_HEAD(&hub->event_list);
hub->intfdev = &intf->dev;
hub->hdev = hdev;
INIT_DELAYED_WORK(&hub->leds, led_work);
INIT_DELAYED_WORK(&hub->init_work, NULL);
+ INIT_WORK(&hub->events, hub_event);
usb_get_intf(intf);
+ usb_get_dev(hdev);
usb_set_intfdata (intf, hub);
intf->needs_remote_wakeup = 1;
@@ -1983,8 +1989,10 @@ void usb_set_device_state(struct usb_device *udev,
|| new_state == USB_STATE_SUSPENDED)
; /* No change to wakeup settings */
else if (new_state == USB_STATE_CONFIGURED)
- wakeup = udev->actconfig->desc.bmAttributes
- & USB_CONFIG_ATT_WAKEUP;
+ wakeup = (udev->quirks &
+ USB_QUIRK_IGNORE_REMOTE_WAKEUP) ? 0 :
+ udev->actconfig->desc.bmAttributes &
+ USB_CONFIG_ATT_WAKEUP;
else
wakeup = 0;
}
@@ -2037,7 +2045,8 @@ static void choose_devnum(struct usb_device *udev)
int devnum;
struct usb_bus *bus = udev->bus;
- /* If khubd ever becomes multithreaded, this will need a lock */
+ /* be safe when more hub events are proceed in parallel */
+ mutex_lock(&bus->usb_address0_mutex);
if (udev->wusb) {
devnum = udev->portnum + 1;
BUG_ON(test_bit(devnum, bus->devmap.devicemap));
@@ -2055,6 +2064,7 @@ static void choose_devnum(struct usb_device *udev)
set_bit(devnum, bus->devmap.devicemap);
udev->devnum = devnum;
}
+ mutex_unlock(&bus->usb_address0_mutex);
}
static void release_devnum(struct usb_device *udev)
@@ -2205,9 +2215,6 @@ static void announce_device(struct usb_device *udev)
static inline void announce_device(struct usb_device *udev) { }
#endif
-#ifdef CONFIG_USB_OTG
-#include "otg_whitelist.h"
-#endif
/**
* usb_enumerate_device_otg - FIXME (usbcore-internal)
@@ -2267,21 +2274,6 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
}
}
}
-
- if (!is_targeted(udev)) {
-
- /* Maybe it can talk to us, though we can't talk to it.
- * (Includes HNP test device.)
- */
- if (udev->bus->b_hnp_enable || udev->bus->is_b_host) {
- err = usb_port_suspend(udev, PMSG_SUSPEND);
- if (err < 0)
- dev_dbg(&udev->dev, "HNP fail, %d\n", err);
- }
- err = -ENOTSUPP;
- goto fail;
- }
-fail:
#endif
return err;
}
@@ -2304,6 +2296,7 @@ fail:
static int usb_enumerate_device(struct usb_device *udev)
{
int err;
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
if (udev->config == NULL) {
err = usb_get_configuration(udev);
@@ -2325,6 +2318,20 @@ static int usb_enumerate_device(struct usb_device *udev)
if (err < 0)
return err;
+ if (IS_ENABLED(CONFIG_USB_OTG_WHITELIST) && hcd->tpl_support &&
+ !is_targeted(udev)) {
+ /* Maybe it can talk to us, though we can't talk to it.
+ * (Includes HNP test device.)
+ */
+ if (IS_ENABLED(CONFIG_USB_OTG) && (udev->bus->b_hnp_enable
+ || udev->bus->is_b_host)) {
+ err = usb_port_suspend(udev, PMSG_AUTO_SUSPEND);
+ if (err < 0)
+ dev_dbg(&udev->dev, "HNP fail, %d\n", err);
+ }
+ return -ENOTSUPP;
+ }
+
usb_detect_interface_quirks(udev);
return 0;
@@ -3070,7 +3077,7 @@ static unsigned wakeup_enabled_descendants(struct usb_device *udev)
* Once VBUS drop breaks the circuit, the port it's using has to go through
* normal re-enumeration procedures, starting with enabling VBUS power.
* Other than re-initializing the hub (plug/unplug, except for root hubs),
- * Linux (2.6) currently has NO mechanisms to initiate that: no khubd
+ * Linux (2.6) currently has NO mechanisms to initiate that: no hub_wq
* timer, no SRP, no requests through sysfs.
*
* If Runtime PM isn't enabled or used, non-SuperSpeed devices may not get
@@ -3212,7 +3219,7 @@ static int finish_port_resume(struct usb_device *udev)
/* usb ch9 identifies four variants of SUSPENDED, based on what
* state the device resumes to. Linux currently won't see the
* first two on the host side; they'd be inside hub_port_init()
- * during many timeouts, but khubd can't suspend until later.
+ * during many timeouts, but hub_wq can't suspend until later.
*/
usb_set_device_state(udev, udev->actconfig
? USB_STATE_CONFIGURED
@@ -3577,7 +3584,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
dev_dbg(&intf->dev, "%s\n", __func__);
- /* stop khubd and related activity */
+ /* stop hub_wq and related activity */
hub_quiesce(hub, HUB_SUSPEND);
return 0;
}
@@ -4461,8 +4468,8 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
if (retval)
goto fail;
- if (hcd->phy && !hdev->parent)
- usb_phy_notify_connect(hcd->phy, udev->speed);
+ if (hcd->usb_phy && !hdev->parent)
+ usb_phy_notify_connect(hcd->usb_phy, udev->speed);
/*
* Some superspeed devices have finished the link training process
@@ -4538,6 +4545,9 @@ check_highspeed (struct usb_hub *hub, struct usb_device *udev, int port1)
struct usb_qualifier_descriptor *qual;
int status;
+ if (udev->quirks & USB_QUIRK_DEVICE_QUALIFIER)
+ return;
+
qual = kmalloc (sizeof *qual, GFP_KERNEL);
if (qual == NULL)
return;
@@ -4617,9 +4627,9 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
/* Disconnect any existing devices under this port */
if (udev) {
- if (hcd->phy && !hdev->parent &&
+ if (hcd->usb_phy && !hdev->parent &&
!(portstatus & USB_PORT_STAT_CONNECTION))
- usb_phy_notify_disconnect(hcd->phy, udev->speed);
+ usb_phy_notify_disconnect(hcd->usb_phy, udev->speed);
usb_disconnect(&port_dev->child);
}
@@ -4970,10 +4980,10 @@ static void port_event(struct usb_hub *hub, int port1)
* 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
+ * before hub_wq runs and sees the disconnect, and the
* device may be an unknown state.
*
- * If the port went through SS.Inactive without khubd
+ * If the port went through SS.Inactive without hub_wq
* seeing it the C_LINK_STATE change flag will be set,
* and we reset the dev to put it in a known state.
*/
@@ -4992,10 +5002,8 @@ 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(struct work_struct *work)
{
- struct list_head *tmp;
struct usb_device *hdev;
struct usb_interface *intf;
struct usb_hub *hub;
@@ -5004,166 +5012,117 @@ 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) {
+ hub = container_of(work, struct usb_hub, events);
+ 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_hdev_lock;
+
+ /* 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_hdev_lock;
+ }
+
+ /* Autoresume */
+ ret = usb_autopm_get_interface(intf);
+ if (ret) {
+ dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
+ goto out_hdev_lock;
+ }
- /* 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;
- }
+ /* If this is an inactive hub, do nothing */
+ if (hub->quiescing)
+ goto out_autopm;
- tmp = hub_event_list.next;
- list_del_init(tmp);
-
- hub = list_entry(tmp, struct usb_hub, event_list);
- kref_get(&hub->kref);
- hdev = hub->hdev;
- usb_get_dev(hdev);
- spin_unlock_irq(&hub_event_lock);
-
- 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 (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;
-
- if (hub->error) {
- dev_dbg (hub_dev, "resetting for error %d\n",
- hub->error);
+ hub->nerrors = 0;
+ hub->error = 0;
+ }
- ret = usb_reset_device(hdev);
- if (ret) {
- dev_dbg (hub_dev,
- "error resetting hub: %d\n", ret);
- goto loop_autopm;
- }
+ /* deal with port status changes */
+ for (i = 1; i <= hdev->maxchild; i++) {
+ struct usb_port *port_dev = hub->ports[i - 1];
- 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);
- usb_put_dev(hdev);
- kref_put(&hub->kref, hub_release);
-
- } /* end while (1) */
-}
-
-static int hub_thread(void *__unused)
-{
- /* khubd needs to be freezable to avoid interfering with USB-PERSIST
- * port handover. Otherwise it might see that a full-speed device
- * was gone before the EHCI controller had handed its port over to
- * the companion full-speed controller.
- */
- set_freezable();
-
- do {
- hub_events();
- wait_event_freezable(khubd_wait,
- !list_empty(&hub_event_list) ||
- kthread_should_stop());
- } while (!kthread_should_stop() || !list_empty(&hub_event_list));
+out_autopm:
+ /* Balance the usb_autopm_get_interface() above */
+ usb_autopm_put_interface_no_suspend(intf);
+out_hdev_lock:
+ usb_unlock_device(hdev);
- pr_debug("%s: khubd exiting\n", usbcore_name);
- return 0;
+ /* Balance the stuff in kick_hub_wq() and allow autosuspend */
+ usb_autopm_put_interface(intf);
+ kref_put(&hub->kref, hub_release);
}
static const struct usb_device_id hub_id_table[] = {
@@ -5203,20 +5162,26 @@ int usb_hub_init(void)
return -1;
}
- khubd_task = kthread_run(hub_thread, NULL, "khubd");
- if (!IS_ERR(khubd_task))
+ /*
+ * The workqueue needs to be freezable to avoid interfering with
+ * USB-PERSIST port handover. Otherwise it might see that a full-speed
+ * device was gone before the EHCI controller had handed its port
+ * over to the companion full-speed controller.
+ */
+ hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
+ if (hub_wq)
return 0;
/* Fall through if kernel_thread failed */
usb_deregister(&hub_driver);
- printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);
+ pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name);
return -1;
}
void usb_hub_cleanup(void)
{
- kthread_stop(khubd_task);
+ destroy_workqueue(hub_wq);
/*
* Hub resources are freed for us by usb_deregister. It calls
@@ -5325,7 +5290,7 @@ static int descriptors_changed(struct usb_device *udev,
* former operating configuration. If the reset fails, or the device's
* descriptors change from their values before the reset, or the original
* configuration and altsettings cannot be restored, a flag will be set
- * telling khubd to pretend the device has been disconnected and then
+ * telling hub_wq to pretend the device has been disconnected and then
* re-connected. All drivers will be unbound, and the device will be
* re-enumerated and probed all over again.
*