This patch:

- Fixes a longstanding urb unlink race, by switching to a single queue
   for EDs being unlinked.  The previous two-queue scheme was sensitive to
   IRQ latencies:  one extra millisecond would make it use the wrong queue.
   This updated scheme should handle latencies of up to 32K microseconds
   (Cthulu forfend :) and slightly shrinks object code size.

- Related (mostly) cleanup.  Some functions and one ED field renamed, ED
   layout is a smidgeon more compact (even given more data), driver version
   string doesn't reflect CVS, debug message only comes out in verbose mode.

Please merge to Linus' tree.  Some of this should probably get backported
to the 2.4 tree, I suspect this as the root cause of a bug someone recently
ran into ... as well as various seemingly random flakes folk have reported.
(Fix to a related ED unlink problem should build on this.)

- Dave


--- ./drivers/usb-dist/host/ohci.h      Sun Apr 14 12:18:56 2002
+++ ./drivers/usb/host/ohci.h   Sat Jun  1 11:24:36 2002
@@ -27,22 +27,29 @@
        __u32                   hwNextED;       /* next ED in list */
 
        /* rest are purely for the driver's use */
-       struct ed               *ed_prev;  
-       __u8                    int_period;
-       __u8                    int_branch;
-       __u8                    int_load; 
-       __u8                    int_interval;
-       __u8                    state;                  // ED_{NEW,UNLINK,OPER}
+       dma_addr_t              dma;            /* addr of ED */
+       struct ed               *ed_prev;       /* for non-interrupt EDs */
+
+       u8                      type;           /* PIPE_{BULK,...} */
+       u8                      interval;       /* interrupt, isochronous */
+       union {
+               struct intr_info {              /* interrupt */
+                       u8      int_period;
+                       u8      int_branch;
+                       u8      int_load; 
+               };
+               u16             last_iso;       /* isochronous */
+       };
+
+       u8                      state;          /* ED_{NEW,UNLINK,OPER} */
 #define ED_NEW                 0x00            /* unused, no dummy td */
 #define ED_UNLINK      0x01            /* dummy td, maybe linked to hc */
 #define ED_OPER                0x02            /* dummy td, _is_ linked to hc */
 #define ED_URB_DEL     0x08            /* for unlinking; masked in */
 
-       __u8                    type; 
-       __u16                   last_iso;
+       /* HC may see EDs on rm_list until next frame (frame_no == tick) */
+       u16                     tick;
        struct ed               *ed_rm_list;
-
-       dma_addr_t              dma;                    /* addr of ED */
 } __attribute__ ((aligned(16)));
 
 #define ED_MASK        ((u32)~0x0f)            /* strip hw status in low addr bits */
@@ -335,7 +342,7 @@
        struct ohci_hcca        *hcca;
        dma_addr_t              hcca_dma;
 
-       struct ed               *ed_rm_list [2];        /* to be removed */
+       struct ed               *ed_rm_list;            /* to be removed */
 
        struct ed               *ed_bulktail;           /* last in bulk list */
        struct ed               *ed_controltail;        /* last in ctrl list */
--- ./drivers/usb-dist/host/ohci-hcd.c  Sun May 26 15:55:03 2002
+++ ./drivers/usb/host/ohci-hcd.c       Sat Jun  1 12:50:45 2002
@@ -12,6 +12,9 @@
  * 
  * History:
  * 
+ * 2002/06/01 remember frame when HC won't see EDs any more; use that info
+ *     to fix urb unlink races caused by interrupt latency assumptions;
+ *     minor ED field and function naming updates
  * 2002/01/18 package as a patch for 2.5.3; this should match the
  *     2.4.17 kernel modulo some bugs being fixed.
  *
@@ -106,7 +109,7 @@
  *     - lots more testing!!
  */
 
-#define DRIVER_VERSION "$Revision: 1.9 $"
+#define DRIVER_VERSION "2002-Jun-01"
 #define DRIVER_AUTHOR "Roman Weissgaerber <[EMAIL PROTECTED]>, David Brownell"
 #define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver"
 
@@ -287,7 +290,7 @@
                }
                        
                urb_priv->state = URB_DEL; 
-               ed_unlink (urb->dev, urb_priv->ed);
+               start_urb_unlink (ohci, urb_priv->ed);
                spin_unlock_irqrestore (&ohci->lock, flags);
        } else {
                /*
@@ -508,16 +511,15 @@
   
        /* could track INTR_SO to reduce available PCI/... bandwidth */
 
-       // FIXME:  this assumes SOF (1/ms) interrupts don't get lost...
-       if (ints & OHCI_INTR_SF) { 
-               unsigned int frame = le16_to_cpu (ohci->hcca->frame_no) & 1;
+       /* handle any pending URB/ED unlinks, leaving INTR_SF enabled
+        * when there's still unlinking to be done (next frame).
+        */
+       spin_lock (&ohci->lock);
+       if (ohci->ed_rm_list)
+               finish_unlinks (ohci, le16_to_cpu (ohci->hcca->frame_no));
+       if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list)
                writel (OHCI_INTR_SF, &regs->intrdisable);      
-               if (ohci->ed_rm_list [!frame] != NULL) {
-                       dl_del_list (ohci, !frame);
-               }
-               if (ohci->ed_rm_list [frame] != NULL)
-                       writel (OHCI_INTR_SF, &regs->intrenable);       
-       }
+       spin_unlock (&ohci->lock);
 
        writel (ints, &regs->intrstatus);
        writel (OHCI_INTR_MIE, &regs->intrenable);      
@@ -719,8 +721,7 @@
        for (i = 0; i < NUM_INTS; i++) ohci->hcca->int_table [i] = 0;
        
        /* no EDs to remove */
-       ohci->ed_rm_list [0] = NULL;
-       ohci->ed_rm_list [1] = NULL;
+       ohci->ed_rm_list = NULL;
 
        /* empty control and bulk lists */       
        ohci->ed_isotail     = NULL;
@@ -802,7 +803,7 @@
                ohci->disabled = 0;
                ohci->sleeping = 0;
                ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER;
-               if (!ohci->ed_rm_list [0] && !ohci->ed_rm_list [1]) {
+               if (!ohci->ed_rm_list) {
                        if (ohci->ed_controltail)
                                ohci->hc_control |= OHCI_CTRL_CLE;
                        if (ohci->ed_bulktail)
--- ./drivers/usb-dist/host/ohci-q.c    Fri May  3 05:59:03 2002
+++ ./drivers/usb/host/ohci-q.c Sat Jun  1 13:27:21 2002
@@ -208,8 +208,7 @@
                }
                ed->ed_prev = ohci->ed_controltail;
                if (!ohci->ed_controltail
-                               && !ohci->ed_rm_list [0]
-                               && !ohci->ed_rm_list [1]
+                               && !ohci->ed_rm_list
                                && !ohci->sleeping
                                ) {
                        ohci->hc_control |= OHCI_CTRL_CLE;
@@ -227,8 +226,7 @@
                }
                ed->ed_prev = ohci->ed_bulktail;
                if (!ohci->ed_bulktail
-                               && !ohci->ed_rm_list [0]
-                               && !ohci->ed_rm_list [1]
+                               && !ohci->ed_rm_list
                                && !ohci->sleeping
                                ) {
                        ohci->hc_control |= OHCI_CTRL_BLE;
@@ -240,16 +238,16 @@
        case PIPE_INTERRUPT:
                load = ed->int_load;
                interval = ep_2_n_interval (ed->int_period);
-               ed->int_interval = interval;
+               ed->interval = interval;
                int_branch = ep_int_balance (ohci, interval, load);
                ed->int_branch = int_branch;
 
                for (i = 0; i < ep_rev (6, interval); i += inter) {
                        inter = 1;
                        for (ed_p = & (ohci->hcca->int_table [ep_rev (5, i) + 
int_branch]); 
-                                (*ed_p != 0) && ((dma_to_ed (ohci, le32_to_cpup 
(ed_p)))->int_interval >= interval); 
+                                (*ed_p != 0) && ((dma_to_ed (ohci, le32_to_cpup 
+(ed_p)))->interval >= interval); 
                                ed_p = & ((dma_to_ed (ohci, le32_to_cpup 
(ed_p)))->hwNextED)) 
-                                       inter = ep_rev (6, (dma_to_ed (ohci, 
le32_to_cpup (ed_p)))->int_interval);
+                                       inter = ep_rev (6, (dma_to_ed (ohci, 
+le32_to_cpup (ed_p)))->interval);
                        ed->hwNextED = *ed_p; 
                        *ed_p = cpu_to_le32 (ed->dma);
                }
@@ -260,7 +258,7 @@
 
        case PIPE_ISOCHRONOUS:
                ed->hwNextED = 0;
-               ed->int_interval = 1;
+               ed->interval = 1;
                if (ohci->ed_isotail != NULL) {
                        ohci->ed_isotail->hwNextED = cpu_to_le32 (ed->dma);
                        ed->ed_prev = ohci->ed_isotail;
@@ -270,7 +268,7 @@
                                for (ed_p = & (ohci->hcca->int_table [ep_rev (5, i)]); 
                                        *ed_p != 0; 
                                        ed_p = & ((dma_to_ed (ohci, le32_to_cpup 
(ed_p)))->hwNextED)) 
-                                               inter = ep_rev (6, (dma_to_ed (ohci, 
le32_to_cpup (ed_p)))->int_interval);
+                                               inter = ep_rev (6, (dma_to_ed (ohci, 
+le32_to_cpup (ed_p)))->interval);
                                *ed_p = cpu_to_le32 (ed->dma);  
                        }       
                        ed->ed_prev = NULL;
@@ -311,7 +309,7 @@
  * the link from the ed still points to another operational ed or 0
  * so the HC can eventually finish the processing of the unlinked ed
  */
-static int ep_unlink (struct ohci_hcd *ohci, struct ed *ed) 
+static int start_ed_unlink (struct ohci_hcd *ohci, struct ed *ed) 
 {
        int     i;
 
@@ -357,8 +355,8 @@
                break;
 
        case PIPE_INTERRUPT:
-               periodic_unlink (ohci, ed, ed->int_branch, ed->int_interval);
-               for (i = ed->int_branch; i < NUM_INTS; i += ed->int_interval)
+               periodic_unlink (ohci, ed, ed->int_branch, ed->interval);
+               for (i = ed->int_branch; i < NUM_INTS; i += ed->interval)
                        ohci->ohci_int_load [i] -= ed->int_load;
 #ifdef OHCI_VERBOSE_DEBUG
                ohci_dump_periodic (ohci, "UNLINK_INT");
@@ -384,6 +382,10 @@
 
        /* FIXME ED's "unlink" state is indeterminate;
         * the HC might still be caching it (till SOF).
+        * - use ed_rm_list and finish_unlinks(), adding some state that
+        *   prevents clobbering hw linkage before the appropriate SOF
+        * - a speedup:  when only one urb is queued on the ed, save 1msec
+        *   by making start_urb_unlink() use this routine to deschedule.
         */
        ed->state = ED_UNLINK;
        return 0;
@@ -478,11 +480,8 @@
  * put the ep on the rm_list and stop the bulk or ctrl list 
  * real work is done at the next start frame (SF) hardware interrupt
  */
-static void ed_unlink (struct usb_device *usb_dev, struct ed *ed)
+static void start_urb_unlink (struct ohci_hcd *ohci, struct ed *ed)
 {    
-       unsigned int            frame;
-       struct ohci_hcd         *ohci = hcd_to_ohci (usb_dev->bus->hcpriv);
-
        /* already pending? */
        if (ed->state & ED_URB_DEL)
                return;
@@ -503,9 +502,15 @@
                        break;
        }
 
-       frame = le16_to_cpu (ohci->hcca->frame_no) & 0x1;
-       ed->ed_rm_list = ohci->ed_rm_list [frame];
-       ohci->ed_rm_list [frame] = ed;
+       /* SF interrupt might get delayed; record the frame counter value that
+        * indicates when the HC isn't looking at it, so concurrent unlinks
+        * behave.  frame_no wraps every 2^16 msec, and changes right before
+        * SF is triggered.
+        */
+       ed->tick = le16_to_cpu (ohci->hcca->frame_no) + 1;
+
+       ed->ed_rm_list = ohci->ed_rm_list;
+       ohci->ed_rm_list = ed;
 
        /* enable SOF interrupt */
        if (!ohci->sleeping) {
@@ -816,10 +821,12 @@
                        if (td_list->ed->hwHeadP & ED_H) {
                                if (urb_priv && ((td_list->index + 1)
                                                < urb_priv->length)) {
+#ifdef OHCI_VERBOSE_DEBUG
                                        dbg ("urb %p TD %d of %d, patch ED",
                                                td_list->urb,
                                                1 + td_list->index,
                                                urb_priv->length);
+#endif
                                        td_list->ed->hwHeadP = 
                            (urb_priv->td [urb_priv->length - 1]->hwNextTD
                                    & __constant_cpu_to_le32 (TD_MASK))
@@ -841,27 +848,37 @@
 
 /*-------------------------------------------------------------------------*/
 
-/* there are some pending requests to unlink 
- * - some URBs/TDs if urb_priv->state == URB_DEL
- */
-static void dl_del_list (struct ohci_hcd *ohci, unsigned int frame)
+/* wrap-aware logic stolen from <linux/jiffies.h> */
+#define tick_before(t1,t2) ((((s16)(t1))-((s16)(t2))) < 0)
+
+/* there are some urbs/eds to unlink; called in_irq(), with HCD locked */
+static void finish_unlinks (struct ohci_hcd *ohci, u16 tick)
 {
-       unsigned long   flags;
-       struct ed       *ed;
-       __u32           edINFO;
-       __u32           tdINFO;
-       struct td       *td = NULL, *td_next = NULL,
-                       *tdHeadP = NULL, *tdTailP;
-       __u32           *td_p;
+       struct ed       *ed, **last;
        int             ctrl = 0, bulk = 0;
 
-       spin_lock_irqsave (&ohci->lock, flags);
-
-       for (ed = ohci->ed_rm_list [frame]; ed != NULL; ed = ed->ed_rm_list) {
+       for (last = &ohci->ed_rm_list, ed = *last; ed != NULL; ed = *last) {
+               struct td       *td, *td_next, *tdHeadP, *tdTailP;
+               u32             *td_p;
+               int             unlinked;
+
+               /* only take off EDs that the HC isn't using, accounting for
+                * frame counter wraps.  completion callbacks might prepend
+                * EDs to the list, they'll be checked next irq.
+                */
+               if (tick_before (tick, ed->tick)) {
+                       last = &ed->ed_rm_list;
+                       continue;
+               }
+               *last = ed->ed_rm_list;
+               ed->ed_rm_list = 0;
+               unlinked = 0;
 
+               /* unlink urbs from first one requested to queue end;
+                * leave earlier urbs alone
+                */
                tdTailP = dma_to_td (ohci, le32_to_cpup (&ed->hwTailP));
                tdHeadP = dma_to_td (ohci, le32_to_cpup (&ed->hwHeadP));
-               edINFO = le32_to_cpup (&ed->hwINFO);
                td_p = &ed->hwHeadP;
 
                for (td = tdHeadP; td != tdTailP; td = td_next) {
@@ -870,8 +887,11 @@
 
                        td_next = dma_to_td (ohci,
                                le32_to_cpup (&td->hwNextTD));
-                       if ((urb_priv->state == URB_DEL)) {
-                               tdINFO = le32_to_cpup (&td->hwINFO);
+                       if (unlinked || (urb_priv->state == URB_DEL)) {
+                               u32 tdINFO = le32_to_cpup (&td->hwINFO);
+
+                               unlinked = 1;
+
                                /* HC may have partly processed this TD */
                                if (TD_CC_GET (tdINFO) < 0xE)
                                        td_done (urb, td);
@@ -880,22 +900,32 @@
 
                                /* URB is done; clean up */
                                if (++ (urb_priv->td_cnt) == urb_priv->length) {
-                                       spin_unlock_irqrestore (&ohci->lock,
-                                               flags);
+                                       if (urb->status == -EINPROGRESS)
+                                               urb->status = -ECONNRESET;
+                                       spin_unlock (&ohci->lock);
                                        finish_urb (ohci, urb);
-                                       spin_lock_irqsave (&ohci->lock, flags);
+                                       spin_lock (&ohci->lock);
                                }
                        } else {
                                td_p = &td->hwNextTD;
                        }
                }
 
+               /* FIXME actually want four cases here:
+                * (a) finishing URB unlink
+                *     [a1] no URBs queued, so start ED unlink
+                *     [a2] some (earlier) URBs still linked, re-enable
+                * (b) finishing ED unlink
+                *     [b1] no URBs queued, ED is truly idle now
+                *     [b2] URBs now queued, link ED back into schedule
+                * right now we only have (a)
+                */
                ed->state &= ~ED_URB_DEL;
                tdHeadP = dma_to_td (ohci, le32_to_cpup (&ed->hwHeadP));
 
                if (tdHeadP == tdTailP) {
                        if (ed->state == ED_OPER)
-                               ep_unlink (ohci, ed);
+                               start_ed_unlink (ohci, ed);
                        td_free (ohci, tdTailP);
                        ed->hwINFO = ED_SKIP;
                        ed->state = ED_NEW;
@@ -918,7 +948,7 @@
                        writel (0, &ohci->regs->ed_controlcurrent);
                if (bulk)       /* reset bulk list */
                        writel (0, &ohci->regs->ed_bulkcurrent);
-               if (!ohci->ed_rm_list [!frame]) {
+               if (!ohci->ed_rm_list) {
                        if (ohci->ed_controltail)
                                ohci->hc_control |= OHCI_CTRL_CLE;
                        if (ohci->ed_bulktail)
@@ -926,9 +956,6 @@
                        writel (ohci->hc_control, &ohci->regs->control);   
                }
        }
-
-       ohci->ed_rm_list [frame] = NULL;
-       spin_unlock_irqrestore (&ohci->lock, flags);
 }
 
 
@@ -939,7 +966,7 @@
  * Process normal completions (error or success) and clean the schedules.
  *
  * This is the main path for handing urbs back to drivers.  The only other
- * path is dl_del_list(), which unlinks URBs by scanning EDs, instead of
+ * path is finish_unlinks(), which unlinks URBs using ed_rm_list, instead of
  * scanning the (re-reversed) donelist as this does.
  */
 static void dl_done_list (struct ohci_hcd *ohci, struct td *td)
@@ -960,7 +987,7 @@
                /* If all this urb's TDs are done, call complete().
                 * Interrupt transfers are the only special case:
                 * they're reissued, until "deleted" by usb_unlink_urb
-                * (real work done in a SOF intr, by dl_del_list).
+                * (real work done in a SOF intr, by finish_unlinks).
                 */
                if (urb_priv->td_cnt == urb_priv->length) {
                        int     resubmit;
@@ -980,7 +1007,7 @@
                if ((ed->hwHeadP & __constant_cpu_to_le32 (TD_MASK))
                                        == ed->hwTailP
                                && (ed->state == ED_OPER)) 
-                       ep_unlink (ohci, ed);
+                       start_ed_unlink (ohci, ed);
                td = td_next;
        }  
        spin_unlock_irqrestore (&ohci->lock, flags);

Reply via email to