#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>
* 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);
#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)
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)
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
if (hub)
- kick_khubd(hub);
+ kick_hub_wq(hub);
}
/*
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);
hub->nerrors = 0;
/* Something happened, let khubd figure it out */
- kick_khubd(hub);
+ kick_hub_wq(hub);
resubmit:
if (hub->quiescing)
*/
set_bit(port1, hub->change_bits);
- kick_khubd(hub);
+ kick_hub_wq(hub);
}
/**
&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)
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;
}
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);
hub_port_connect_change(hub, port1, portstatus, portchange);
}
-static void hub_event(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;
u16 hubchange;
int i, ret;
- /* 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;
- }
-
- 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 = 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... */
* disconnected while waiting for the lock to succeed. */
usb_lock_device(hdev);
if (unlikely(hub->disconnected))
- goto out_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;
+ 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;
+ goto out_hdev_lock;
}
/* If this is an inactive hub, do nothing */
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:
+out_hdev_lock:
usb_unlock_device(hdev);
- kref_put(&hub->kref, hub_release);
-}
-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_event();
- wait_event_freezable(khubd_wait,
- !list_empty(&hub_event_list) ||
- kthread_should_stop());
- } while (!kthread_should_stop() || !list_empty(&hub_event_list));
-
- 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[] = {
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.
+ *
+ * Also we use ordered workqueue because the code is not ready
+ * for parallel execution of hub events, see choose_devnum().
+ */
+ hub_wq = alloc_ordered_workqueue("usb_hub_wq", WQ_FREEZABLE);
+ 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