USB: ehci-hcd unlink speedups
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>
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 369a8a5..d9d53f2 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -84,7 +84,7 @@
#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
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index b85b541..2622b65 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -1116,8 +1116,7 @@
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 @@
}
}
- /* 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;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 90245fd..5799298 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -198,7 +198,10 @@
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);