summaryrefslogtreecommitdiff
path: root/drivers/usb/host
diff options
context:
space:
mode:
authorDavid Brownell <dbrownell@users.sourceforge.net>2008-06-03 22:21:55 -0700
committerGreg Kroah-Hartman <gregkh@suse.de>2008-07-21 15:16:27 -0700
commitb963801164618e25fbdc0cd452ce49c3628b46c8 (patch)
tree149c888f19921f356607c8d7a1c1001e148450ba /drivers/usb/host
parent38f3ad5e7463d4dd490a8081a5f3f9f2dec7ecd6 (diff)
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>
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/ehci-hcd.c2
-rw-r--r--drivers/usb/host/ehci-q.c17
-rw-r--r--drivers/usb/host/ehci.h5
3 files changed, 14 insertions, 10 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 369a8a5ea7bb..d9d53f289caf 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -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
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index b85b54160cda..2622b6596d7c 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -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;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 90245fd8bac4..5799298364fb 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -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);