From 1eca72ad8fb0bc877982ad6aa8bcee6ed6964d20 Mon Sep 17 00:00:00 2001
From: Ming Lei <tom.leiming@gmail.com>
Date: Wed, 12 Jun 2013 14:00:39 +0800
Subject: [PATCH 2/5] USB: support interrupt threaded handler

Convert percpu tasklet into tasklet in HCD, also implement
interrupt threaded handler, which is enabled at default.

unsetting USB_HCD_THREADED_IRQ will disable interrupt threaded
handler and enable tasklet for URB giveback.

Both interrupt threaded handler and tasklet can't be enabled
if HCD_BH isn't set in hcd->driver->flags.
---
 drivers/usb/core/hcd.c  |  103 +++++++++++++++++++++++++++++++++++++++++++++--
 include/linux/usb/hcd.h |    1 +
 2 files changed, 100 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 5577275..6fdbaaf 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -48,6 +48,7 @@
 
 #include "usb.h"
 
+#define USB_HCD_THREADED_IRQ
 
 /*-------------------------------------------------------------------------*/
 
@@ -1661,6 +1662,7 @@ static void __usb_hcd_giveback_urb(struct urb *urb)
 	usb_put_urb (urb);
 }
 
+#ifndef USB_HCD_THREADED_IRQ
 static void usb_giveback_urb_bh(unsigned long param)
 {
 	struct giveback_urb_bh *bh = (struct giveback_urb_bh *)param;
@@ -1689,6 +1691,60 @@ restart:
 	spin_unlock_irqrestore(&bh->lock, flags);
 }
 
+#else
+static void usb_giveback_urb_bh(struct giveback_urb_bh *bh)
+{
+	unsigned long flags;
+	struct list_head local_list;
+
+	preempt_disable();
+
+	spin_lock_irqsave(&bh->lock, flags);
+	bh->running = 1;
+restart:
+	list_replace_init(&bh->head, &local_list);
+	spin_unlock_irqrestore(&bh->lock, flags);
+
+	while (!list_empty(&local_list)) {
+		struct urb *urb;
+
+		urb = list_entry(local_list.next, struct urb, urb_list);
+		list_del_init(&urb->urb_list);
+
+		local_bh_disable();
+		__usb_hcd_giveback_urb(urb);
+		local_bh_enable();
+	}
+
+	/* check if there are new URBs to giveback */
+	spin_lock_irqsave(&bh->lock, flags);
+	if (!list_empty(&bh->head))
+		goto restart;
+	bh->running = 0;
+	spin_unlock_irqrestore(&bh->lock, flags);
+
+	preempt_enable();
+}
+
+/* threaded irq handler use single queue */
+static irqreturn_t usb_hcd_irq_handler(int irq, void *param)
+{
+	struct usb_hcd *hcd = (struct usb_hcd *)param;
+
+	usb_giveback_urb_bh(hcd->async_bh);
+
+	return IRQ_HANDLED;
+}
+
+static void rh_giveback_urb_work(struct work_struct *work)
+{
+	struct giveback_urb_bh *bh=
+		container_of(work, struct giveback_urb_bh, work);
+
+	usb_giveback_urb_bh(bh);
+}
+#endif
+
 /**
  * usb_hcd_giveback_urb - return URB from HCD to device driver
  * @hcd: host controller returning the URB
@@ -1710,7 +1766,7 @@ void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
 {
 	struct giveback_urb_bh *bh = hcd->async_bh;
 	bool async = 1;
-	bool sched = 1;
+	bool sched = 0;
 
 	urb->status = status;
 	if (!hcd_giveback_urb_in_bh(hcd)) {
@@ -1718,23 +1774,35 @@ void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
 		return;
 	}
 
+#ifndef USB_HCD_THREADED_IRQ
 	if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe)) {
 		bh = hcd->periodic_bh;
 		async = 0;
 	}
+#else
+	if (!urb->dev->parent || urb->unlinked) {
+		bh = hcd->periodic_bh;
+		sched = 1;
+	}
+#endif
 
 	spin_lock(&bh->lock);
 	list_add_tail(&urb->urb_list, &bh->head);
-	if (bh->running)
-		sched = 0;
+	if (!bh->running)
+		sched = 1;
 	spin_unlock(&bh->lock);
 
+#ifndef USB_HCD_THREADED_IRQ
 	if (sched) {
 		if (async)
 			tasklet_schedule(&bh->bh);
 		else
 			tasklet_hi_schedule(&bh->bh);
 	}
+#else
+	if (sched)
+		schedule_work(&hcd->periodic_bh->work);
+#endif
 }
 EXPORT_SYMBOL_GPL(usb_hcd_giveback_urb);
 
@@ -2301,8 +2369,16 @@ irqreturn_t usb_hcd_irq (int irq, void *__hcd)
 		rc = IRQ_NONE;
 	else if (hcd->driver->irq(hcd) == IRQ_NONE)
 		rc = IRQ_NONE;
-	else
+	else {
+#ifndef USB_HCD_THREADED_IRQ
 		rc = IRQ_HANDLED;
+#else
+		if(hcd_giveback_urb_in_bh(hcd))
+			rc = IRQ_WAKE_THREAD;
+		else
+			rc = IRQ_HANDLED;
+#endif
+	}
 
 	local_irq_restore(flags);
 	return rc;
@@ -2361,7 +2437,11 @@ static void __init_giveback_urb_bh(struct giveback_urb_bh *bh)
 
 	spin_lock_init(&bh->lock);
 	INIT_LIST_HEAD(&bh->head);
+#ifndef USB_HCD_THREADED_IRQ
 	tasklet_init(&bh->bh, usb_giveback_urb_bh, (unsigned long)bh);
+#else
+	INIT_WORK(&bh->work, rh_giveback_urb_work);
+#endif
 }
 
 static int init_giveback_urb_bh(struct usb_hcd *hcd)
@@ -2387,7 +2467,11 @@ static int init_giveback_urb_bh(struct usb_hcd *hcd)
 
 static void __exit_giveback_urb_bh(struct giveback_urb_bh *bh)
 {
+#ifndef USB_HCD_THREADED_IRQ
 	tasklet_kill(&bh->bh);
+#else
+	flush_work(&bh->work);
+#endif
 }
 
 static void exit_giveback_urb_bh(struct usb_hcd *hcd)
@@ -2556,8 +2640,19 @@ static int usb_hcd_request_irqs(struct usb_hcd *hcd,
 
 		snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",
 				hcd->driver->description, hcd->self.busnum);
+#ifndef USB_HCD_THREADED_IRQ
 		retval = request_irq(irqnum, &usb_hcd_irq, irqflags,
 				hcd->irq_descr, hcd);
+#else
+		if (!hcd_giveback_urb_in_bh(hcd))
+			retval = request_irq(irqnum, &usb_hcd_irq, irqflags,
+					     hcd->irq_descr, hcd);
+		else
+			retval = request_threaded_irq(irqnum, &usb_hcd_irq,
+						      &usb_hcd_irq_handler,
+						      irqflags,
+						      hcd->irq_descr, hcd);
+#endif
 		if (retval != 0) {
 			dev_err(hcd->self.controller,
 					"request interrupt %d failed\n",
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
index d895dbddc..b05f9a0 100644
--- a/include/linux/usb/hcd.h
+++ b/include/linux/usb/hcd.h
@@ -73,6 +73,7 @@ struct giveback_urb_bh {
 	spinlock_t lock;
 	struct list_head  head;
 	struct tasklet_struct bh;
+	struct work_struct    work;
 };
 
 struct usb_hcd {
-- 
1.7.9.5

