USB: OHCI: make URB completions single-threaded
authorAlan Stern <stern@rowland.harvard.edu>
Fri, 18 Jul 2014 20:26:07 +0000 (16:26 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 18 Jul 2014 23:33:01 +0000 (16:33 -0700)
URBs for a particular endpoint should complete sequentially.  That is,
we shouldn't call the completion handler for one URB until the handler
for the previous URB has returned.

When the OHCI watchdog routine is added, there will be two paths for
completing URBs: interrupt handler and watchdog routine.  Their
activities have to be synchronized so that completions don't occur in
multiple threads concurrently.

For that purpose, this patch creates an ohci_work() routine which will
be responsible for calling process_done_list() and finish_unlinks(),
the two routines that detect when an URB is complete.  Everything will
funnel through ohci_work(), and it will be careful not to run in more
than one thread at a time.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/ohci-hcd.c
drivers/usb/host/ohci-hub.c
drivers/usb/host/ohci-q.c
drivers/usb/host/ohci.h

index 3112799bba7ffeacb79db6e7534c95983e5d9c45..ad588538e2e794c32c5329cecd2206c4a27c530f 100644 (file)
@@ -316,7 +316,7 @@ static int ohci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
 
                if (ohci->rh_state != OHCI_RH_RUNNING) {
                        /* With HC dead, we can clean up right away */
-                       finish_unlinks(ohci, 0);
+                       ohci_work(ohci);
                }
        }
        spin_unlock_irqrestore (&ohci->lock, flags);
@@ -349,7 +349,7 @@ rescan:
        if (ohci->rh_state != OHCI_RH_RUNNING) {
 sanitize:
                ed->state = ED_IDLE;
-               finish_unlinks (ohci, 0);
+               ohci_work(ohci);
        }
 
        switch (ed->state) {
@@ -789,9 +789,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
        /* handle any pending URB/ED unlinks, leaving INTR_SF enabled
         * when there's still unlinking to be done (next frame).
         */
-       process_done_list(ohci);
-       if (ohci->ed_rm_list)
-               finish_unlinks (ohci, ohci_frame_no(ohci));
+       ohci_work(ohci);
        if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list
                        && ohci->rh_state == OHCI_RH_RUNNING)
                ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable);
@@ -879,7 +877,7 @@ int ohci_restart(struct ohci_hcd *ohci)
                if (!urb->unlinked)
                        urb->unlinked = -ESHUTDOWN;
        }
-       finish_unlinks (ohci, 0);
+       ohci_work(ohci);
        spin_unlock_irq(&ohci->lock);
 
        /* paranoia, in case that didn't work: */
index dccb90edd66efaed6423ad56e2e88daac5403a99..8991692bcfb8bef6c311d366b38d144bf4298a0c 100644 (file)
@@ -40,8 +40,7 @@
        (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
 
 static void update_done_list(struct ohci_hcd *);
-static void process_done_list(struct ohci_hcd *);
-static void finish_unlinks (struct ohci_hcd *, u16);
+static void ohci_work(struct ohci_hcd *);
 
 #ifdef CONFIG_PM
 static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop)
@@ -89,8 +88,7 @@ __acquires(ohci->lock)
                spin_lock_irq (&ohci->lock);
        }
        update_done_list(ohci);
-       process_done_list(ohci);
-       finish_unlinks (ohci, ohci_frame_no(ohci));
+       ohci_work(ohci);
 
        /*
         * Some controllers don't handle "global" suspend properly if
index f36b2fa0ee2f8ebd40336f227b704833ba899aa7..1974ddc68e455e71f8285d5643e71dfc303a17db 100644 (file)
@@ -964,9 +964,9 @@ static void update_done_list(struct ohci_hcd *ohci)
 /*-------------------------------------------------------------------------*/
 
 /* there are some urbs/eds to unlink; called in_irq(), with HCD locked */
-static void
-finish_unlinks (struct ohci_hcd *ohci, u16 tick)
+static void finish_unlinks(struct ohci_hcd *ohci)
 {
+       unsigned        tick = ohci_frame_no(ohci);
        struct ed       *ed, **last;
 
 rescan_all:
@@ -1202,3 +1202,27 @@ static void process_done_list(struct ohci_hcd *ohci)
                takeback_td(ohci, td);
        }
 }
+
+/*
+ * TD takeback and URB giveback must be single-threaded.
+ * This routine takes care of it all.
+ */
+static void ohci_work(struct ohci_hcd *ohci)
+{
+       if (ohci->working) {
+               ohci->restart_work = 1;
+               return;
+       }
+       ohci->working = 1;
+
+ restart:
+       process_done_list(ohci);
+       if (ohci->ed_rm_list)
+               finish_unlinks(ohci);
+
+       if (ohci->restart_work) {
+               ohci->restart_work = 0;
+               goto restart;
+       }
+       ohci->working = 0;
+}
index a8259bc6fd8b37f64215df1ced984669ee0b89a4..ef348c2e1e4b0f5a38f48d1cc0edc579d1060e7c 100644 (file)
@@ -393,6 +393,8 @@ struct ohci_hcd {
        unsigned long           next_statechange;       /* suspend/resume */
        u32                     fminterval;             /* saved register */
        unsigned                autostop:1;     /* rh auto stopping/stopped */
+       unsigned                working:1;
+       unsigned                restart_work:1;
 
        unsigned long           flags;          /* for HC bugs */
 #define        OHCI_QUIRK_AMD756       0x01                    /* erratum #4 */