USB: ehci-hcd unlink speedups
authorDavid Brownell <dbrownell@users.sourceforge.net>
Wed, 4 Jun 2008 05:21:55 +0000 (22:21 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Mon, 21 Jul 2008 22:16:27 +0000 (15:16 -0700)
This patch fixes some performance bugs observed with some workloads
when unlinking EHCI queue header (QH) descriptors from the async ring
(control/bulk schedule).

The mechanism intended to defer unlinking an empty QH (so there is no
penalty in common cases where it's quickly reused) was not working as
intended.  Sometimes the unlink was scheduled:

 - too quickly ... which can be a *strong* negative effect, since
   that QH becomes unavailable for immediate re-use;

 - too slowly ... wasting DMA cycles, usually a minor issue except
   for increased bus contention and power usage;

Plus there was an extreme case of "too slowly":  a logical error in the
IAA watchdog-timer conversion meant that sometimes the unlink never
got scheduled.

The fix replaces a simple counter with a timestamp derived from the
controller's 8 KHz microframe counter, and adjusts the timer usage
for some issues associated with HZ being less than 8K.

(Based on a patch originally by Alan Stern, and good troubleshooting
from  Leonid.)

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Leonid <leonidv11@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-q.c
drivers/usb/host/ehci.h

index 369a8a5ea7bb374da2776dd3a02587b0f58b06fb..d9d53f289caf48b83001b42321e1bbaf9ca00886 100644 (file)
@@ -84,7 +84,7 @@ static const char     hcd_name [] = "ehci_hcd";
 #define EHCI_IAA_MSECS         10              /* arbitrary */
 #define EHCI_IO_JIFFIES                (HZ/10)         /* io watchdog > irq_thresh */
 #define EHCI_ASYNC_JIFFIES     (HZ/20)         /* async idle timeout */
-#define EHCI_SHRINK_JIFFIES    (HZ/200)        /* async qh unlink delay */
+#define EHCI_SHRINK_FRAMES     5               /* async qh unlink delay */
 
 /* Initial IRQ latency:  faster than hw default */
 static int log2_irq_thresh = 0;                // 0 to 6
index b85b54160cdaeade20792ad9878d9751606f3b50..2622b6596d7cc6283f6e0b34f2513826a25b0105 100644 (file)
@@ -1116,8 +1116,7 @@ static void scan_async (struct ehci_hcd *ehci)
        struct ehci_qh          *qh;
        enum ehci_timer_action  action = TIMER_IO_WATCHDOG;
 
-       if (!++(ehci->stamp))
-               ehci->stamp++;
+       ehci->stamp = ehci_readl(ehci, &ehci->regs->frame_index);
        timer_action_done (ehci, TIMER_ASYNC_SHRINK);
 rescan:
        qh = ehci->async->qh_next.qh;
@@ -1142,18 +1141,20 @@ rescan:
                                }
                        }
 
-                       /* unlink idle entries, reducing HC PCI usage as well
+                       /* unlink idle entries, reducing DMA usage as well
                         * as HCD schedule-scanning costs.  delay for any qh
                         * we just scanned, there's a not-unusual case that it
                         * doesn't stay idle for long.
                         * (plus, avoids some kind of re-activation race.)
                         */
-                       if (list_empty (&qh->qtd_list)) {
-                               if (qh->stamp == ehci->stamp)
+                       if (list_empty(&qh->qtd_list)
+                                       && qh->qh_state == QH_STATE_LINKED) {
+                               if (!ehci->reclaim
+                                       && ((ehci->stamp - qh->stamp) & 0x1fff)
+                                               >= (EHCI_SHRINK_FRAMES * 8))
+                                       start_unlink_async(ehci, qh);
+                               else
                                        action = TIMER_ASYNC_SHRINK;
-                               else if (!ehci->reclaim
-                                           && qh->qh_state == QH_STATE_LINKED)
-                                       start_unlink_async (ehci, qh);
                        }
 
                        qh = qh->qh_next.qh;
index 90245fd8bac4dd234390ec96e636570ef4e98c73..5799298364fb8dc1763f4530ca05ff99cec91b38 100644 (file)
@@ -198,7 +198,10 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
                        break;
                // case TIMER_ASYNC_SHRINK:
                default:
-                       t = EHCI_SHRINK_JIFFIES;
+                       /* add a jiffie since we synch against the
+                        * 8 KHz uframe counter.
+                        */
+                       t = DIV_ROUND_UP(EHCI_SHRINK_FRAMES * HZ, 1000) + 1;
                        break;
                }
                mod_timer(&ehci->watchdog, t + jiffies);