[RFC 2/3] EHCI: convert the IRQ handler to a tasklet

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

 



This patch divides ehci-hcd's interrupt handler into a top half and a
bottom half, using a tasklet to execute the latter.

The conversion is very straightforward.  The only subtle point is that
we have to ignore interrupts that arrive while the tasklet is running
(i.e., from another device on a shared IRQ line).

Not-yet-signed-off-by: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>

---


 drivers/usb/host/ehci-hcd.c |   56 ++++++++++++++++++++++++++++++++++++--------
 drivers/usb/host/ehci.h     |    4 +++
 2 files changed, 51 insertions(+), 9 deletions(-)

Index: usb-3.11/drivers/usb/host/ehci.h
===================================================================
--- usb-3.11.orig/drivers/usb/host/ehci.h
+++ usb-3.11/drivers/usb/host/ehci.h
@@ -98,6 +98,10 @@ enum ehci_hrtimer_event {
 #define EHCI_HRTIMER_NO_EVENT	99
 
 struct ehci_hcd {			/* one per controller */
+	/* tasklet and IRQ support */
+	struct tasklet_struct	tasklet;
+	bool			irqs_are_masked;
+
 	/* timing support */
 	enum ehci_hrtimer_event	next_hrtimer_event;
 	unsigned		enabled_hrtimer_events;
Index: usb-3.11/drivers/usb/host/ehci-hcd.c
===================================================================
--- usb-3.11.orig/drivers/usb/host/ehci-hcd.c
+++ usb-3.11/drivers/usb/host/ehci-hcd.c
@@ -140,6 +140,8 @@ static inline void ehci_set_intr_enable(
 	ehci_writel(ehci, mask, &ehci->regs->intr_enable);
 }
 
+static void ehci_tasklet_routine(unsigned long _ehci);
+
 #include "ehci-dbg.c"
 
 /*-------------------------------------------------------------------------*/
@@ -218,6 +220,7 @@ static int ehci_halt (struct ehci_hcd *e
 
 	spin_unlock_irq(&ehci->lock);
 	synchronize_irq(ehci_to_hcd(ehci)->irq);
+	tasklet_kill(&ehci->tasklet);
 
 	return ehci_handshake(ehci, &ehci->regs->status,
 			  STS_HALT, STS_HALT, 16 * 125);
@@ -468,6 +471,8 @@ static int ehci_init(struct usb_hcd *hcd
 	struct ehci_qh_hw	*hw;
 
 	spin_lock_init(&ehci->lock);
+	tasklet_init(&ehci->tasklet, ehci_tasklet_routine,
+			(unsigned long) ehci);
 
 	/*
 	 * keep io watchdog by default, those good HCDs could turn off it later
@@ -691,13 +696,40 @@ EXPORT_SYMBOL_GPL(ehci_setup);
 
 /*-------------------------------------------------------------------------*/
 
-static irqreturn_t ehci_irq (struct usb_hcd *hcd)
+static irqreturn_t ehci_irq(struct usb_hcd *hcd)
 {
 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
+	u32			status, masked_status;
+
+	if (ehci->irqs_are_masked)
+		return IRQ_NONE;
+
+	/*
+	 * We don't use STS_FLR, but some controllers don't like it to
+	 * remain on, so mask it out along with the other status bits.
+	 */
+	status = ehci_readl(ehci, &ehci->regs->status);
+	masked_status = status & (INTR_MASK | STS_FLR);
+
+	/* Shared IRQ? */
+	if (!masked_status || unlikely(ehci->rh_state == EHCI_RH_HALTED))
+		return IRQ_NONE;
+
+	/* Mask IRQs and let the tasklet do the work */
+	ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+	ehci->irqs_are_masked = true;
+	tasklet_hi_schedule(&ehci->tasklet);
+	return IRQ_HANDLED;
+}
+
+static void ehci_tasklet_routine(unsigned long _ehci)
+{
+	struct ehci_hcd		*ehci = (struct ehci_hcd *) _ehci;
+	struct usb_hcd		*hcd = ehci_to_hcd(ehci);
 	u32			status, masked_status, pcd_status = 0, cmd;
 	int			bh;
 
-	spin_lock (&ehci->lock);
+	spin_lock_irq(&ehci->lock);
 
 	status = ehci_readl(ehci, &ehci->regs->status);
 
@@ -713,11 +745,9 @@ static irqreturn_t ehci_irq (struct usb_
 	 */
 	masked_status = status & (INTR_MASK | STS_FLR);
 
-	/* Shared IRQ? */
-	if (!masked_status || unlikely(ehci->rh_state == EHCI_RH_HALTED)) {
-		spin_unlock(&ehci->lock);
-		return IRQ_NONE;
-	}
+	/* IRQ already handled? */
+	if (!masked_status || unlikely(ehci->rh_state == EHCI_RH_HALTED))
+		goto done;
 
 	/* clear (just) interrupts */
 	ehci_writel(ehci, masked_status, &ehci->regs->status);
@@ -833,10 +863,16 @@ dead:
 
 	if (bh)
 		ehci_work (ehci);
-	spin_unlock (&ehci->lock);
+
+ done:
+	/* Unmask IRQs again */
+	ehci->irqs_are_masked = false;
+	smp_wmb();		/* Make sure ehci_irq() sees that assignment */
+	ehci_writel(ehci, ehci->intr_mask, &ehci->regs->intr_enable);
+
+	spin_unlock_irq(&ehci->lock);
 	if (pcd_status)
 		usb_hcd_poll_rh_status(hcd);
-	return IRQ_HANDLED;
 }
 
 /*-------------------------------------------------------------------------*/
@@ -1093,6 +1129,8 @@ int ehci_suspend(struct usb_hcd *hcd, bo
 	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 	spin_unlock_irq(&ehci->lock);
 
+	synchronize_irq(ehci_to_hcd(ehci)->irq);
+	tasklet_kill(&ehci->tasklet);
 	return 0;
 }
 EXPORT_SYMBOL_GPL(ehci_suspend);

--
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