[PATCH v2 12/14] USB: convert khubd to a workqueue

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Simply a cleanup as khubd open codes several facilities that are
provided by workqueue.  Note that the body of hub_events() is kept as a
loop to minimize thrash and help the readability of the patch.

This does reorder the actions in hub_quiesce().  Since the
->disconnected flag is removed we wait to flush the workqueue until
after the urb has been killed, and we know that kick_khubd() will not be
called again.  The kref for the usb_hub is also removed since we can
flush the workqueue and do not need to hold a reference while the hub
object while is being processed.

Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hub.c |  128 +++++++++++-------------------------------------
 drivers/usb/core/hub.h |    4 --
 2 files changed, 29 insertions(+), 103 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 7ba9b654a6fc..033535171ae6 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -22,11 +22,10 @@
 #include <linux/usb/hcd.h>
 #include <linux/usb/otg.h>
 #include <linux/usb/quirks.h>
-#include <linux/kthread.h>
 #include <linux/mutex.h>
-#include <linux/freezer.h>
 #include <linux/random.h>
 #include <linux/pm_qos.h>
+#include <linux/workqueue.h>
 
 #include <asm/uaccess.h>
 #include <asm/byteorder.h>
@@ -53,14 +52,7 @@ static inline int hub_is_superspeed(struct usb_device *hdev)
  * 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;
+static struct workqueue_struct *khubd_wq;
 
 /* cycle leds on hubs that aren't blinking for attention */
 static bool blinkenlights = 0;
@@ -577,18 +569,12 @@ static int hub_port_status(struct usb_hub *hub, int port1,
 
 static void kick_khubd(struct usb_hub *hub)
 {
-	unsigned long	flags;
-
-	spin_lock_irqsave(&hub_event_lock, flags);
-	if (!hub->disconnected && list_empty(&hub->event_list)) {
-		list_add_tail(&hub->event_list, &hub_event_list);
+	struct usb_interface *intf = to_usb_interface(hub->intfdev);
 
-		/* 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 khubd runs */
+	usb_autopm_get_interface_no_resume(intf);
+	if (!queue_work(khubd_wq, &hub->event_work))
+		usb_autopm_put_interface_async(intf);
 }
 
 void usb_kick_khubd(struct usb_device *hdev)
@@ -1559,10 +1545,8 @@ fail_keep_maxchild:
 	return ret;
 }
 
-static void hub_release(struct kref *kref)
+static void hub_release(struct usb_hub *hub)
 {
-	struct usb_hub *hub = container_of(kref, struct usb_hub, kref);
-
 	usb_put_intf(to_usb_interface(hub->intfdev));
 	kfree(hub);
 }
@@ -1575,19 +1559,13 @@ static void hub_disconnect(struct usb_interface *intf)
 	struct usb_device *hdev = interface_to_usbdev(intf);
 	int i;
 
-	/* 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);
-	}
-	hub->disconnected = 1;
-	spin_unlock_irq(&hub_event_lock);
-
 	/* Disconnect all children and quiesce the hub */
 	hub->error = 0;
 	hub_quiesce(hub, HUB_DISCONNECT);
 
+	if (cancel_work_sync(&hub->event_work))
+		usb_autopm_put_interface(intf);
+
 	usb_set_intfdata (intf, NULL);
 
 	for (i = 0; i < hdev->maxchild; i++)
@@ -1604,9 +1582,10 @@ static void hub_disconnect(struct usb_interface *intf)
 	kfree(hub->buffer);
 
 	pm_suspend_ignore_children(&intf->dev, false);
-	kref_put(&hub->kref, hub_release);
+	hub_release(hub);
 }
 
+static void hub_event(struct work_struct *);
 static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
 {
 	struct usb_host_interface *desc;
@@ -1696,8 +1675,7 @@ descriptor_error:
 		return -ENOMEM;
 	}
 
-	kref_init(&hub->kref);
-	INIT_LIST_HEAD(&hub->event_list);
+	INIT_WORK(&hub->event_work, hub_event);
 	hub->intfdev = &intf->dev;
 	hub->hdev = hdev;
 	INIT_DELAYED_WORK(&hub->leds, led_work);
@@ -4613,46 +4591,21 @@ static void put_port_pm_sync(struct usb_port *port_dev)
 #define for_each_port_pm_sync(i, p, h) \
 	for (i = 1; (p = get_port_pm_sync(h, i)); i++, put_port_pm_sync(p))
 
-static void hub_events(void)
+static void hub_event(struct work_struct *w)
 {
-	struct list_head *tmp;
-	struct usb_device *hdev;
-	struct usb_port *port_dev;
-	struct usb_interface *intf;
-	struct usb_hub *hub;
-	struct device *hub_dev;
+	struct usb_hub *hub = container_of(w, struct usb_hub, event_work);
+	struct usb_device *hdev = hub->hdev;
+	struct device *hub_dev = hub->intfdev;
+	struct usb_interface *intf = to_usb_interface(hub_dev);
+	int i, ret;
 	u16 hubstatus;
 	u16 hubchange;
 	u16 portstatus;
 	u16 portchange;
-	int i, ret;
+	struct usb_port *port_dev;
 	int connect_change, wakeup_change;
 
-	/*
-	 *  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);
-			break;
-		}
-
-		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);
-
-		hdev = hub->hdev;
-		hub_dev = hub->intfdev;
-		intf = to_usb_interface(hub_dev);
+	do {
 		dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
 				hdev->state, hdev->maxchild,
 				/* NOTE: expects max 15 ports... */
@@ -4662,8 +4615,6 @@ static void hub_events(void)
 		/* 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) {
@@ -4861,31 +4812,8 @@ static void hub_events(void)
 		 * 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) */
-}
-
-static int hub_thread(void *__unused)
-{
-	/* khubd needs to be freezable to avoid intefering 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));
-
-	pr_debug("%s: khubd exiting\n", usbcore_name);
-	return 0;
+	} while (0);
 }
 
 static const struct usb_device_id hub_id_table[] = {
@@ -4925,8 +4853,9 @@ int usb_hub_init(void)
 		return -1;
 	}
 
-	khubd_task = kthread_run(hub_thread, NULL, "khubd");
-	if (!IS_ERR(khubd_task))
+	khubd_wq = create_freezable_workqueue("khubd");
+
+	if (khubd_wq)
 		return 0;
 
 	/* Fall through if kernel_thread failed */
@@ -4938,8 +4867,6 @@ int usb_hub_init(void)
 
 void usb_hub_cleanup(void)
 {
-	kthread_stop(khubd_task);
-
 	/*
 	 * Hub resources are freed for us by usb_deregister. It calls
 	 * usb_driver_purge on every device which in turn calls that
@@ -4948,7 +4875,8 @@ void usb_hub_cleanup(void)
 	 * individual hub resources. -greg
 	 */
 	usb_deregister(&hub_driver);
-} /* usb_hub_cleanup() */
+	destroy_workqueue(khubd_wq);
+}
 
 static int descriptors_changed(struct usb_device *udev,
 		struct usb_device_descriptor *old_device_descriptor,
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index c54501967179..733d4ef53683 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -27,7 +27,6 @@
 struct usb_hub {
 	struct device		*intfdev;	/* the "interface" device */
 	struct usb_device	*hdev;
-	struct kref		kref;
 	struct urb		*urb;		/* for interrupt polling pipe */
 
 	/* buffer for urb ... with extra space in case of babble */
@@ -41,7 +40,7 @@ struct usb_hub {
 	int			error;		/* last reported error */
 	int			nerrors;	/* track consecutive errors */
 
-	struct list_head	event_list;	/* hubs w/data or errs ready */
+	struct work_struct	event_work;	/* hubs w/data or errs ready */
 	unsigned long		event_bits[1];	/* status change bitmask */
 	unsigned long		change_bits[1];	/* ports with logical connect
 							status change */
@@ -66,7 +65,6 @@ struct usb_hub {
 
 	unsigned		limited_power:1;
 	unsigned		quiescing:1;
-	unsigned		disconnected:1;
 
 	unsigned		quirk_check_port_auto_suspend:1;
 

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux