# This is a BitKeeper generated patch for the following project:
# Project Name: Linux kernel tree
# This patch format is intended for GNU patch command version 2.5 or higher.
# This patch includes the following deltas:
#                  ChangeSet    1.454   -> 1.455  
#       drivers/usb/host/ohci-hcd.c     1.11    -> 1.12   
#       drivers/usb/host/ohci-q.c       1.7     -> 1.8    
#       drivers/usb/host/ohci.h 1.4     -> 1.5    
#
# The following is the BitKeeper ChangeSet Log
# --------------------------------------------
# 02/06/05      [EMAIL PROTECTED]     1.455
# [PATCH] ohci-hcd, fix urb unlink races
# 
# 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.
# --------------------------------------------
#
diff -Nru a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
--- a/drivers/usb/host/ohci-hcd.c       Wed Jun  5 12:28:53 2002
+++ b/drivers/usb/host/ohci-hcd.c       Wed Jun  5 12:28:53 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)
diff -Nru a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
--- a/drivers/usb/host/ohci-q.c Wed Jun  5 12:28:53 2002
+++ b/drivers/usb/host/ohci-q.c Wed Jun  5 12:28:53 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);
diff -Nru a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
--- a/drivers/usb/host/ohci.h   Wed Jun  5 12:28:53 2002
+++ b/drivers/usb/host/ohci.h   Wed Jun  5 12:28:53 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 */

_______________________________________________________________

Don't miss the 2002 Sprint PCS Application Developer's Conference
August 25-28 in Las Vegas -- http://devcon.sprintpcs.com/adp/index.cfm

_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to