|
@@ -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.
|
|
|
*
|