diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2012-07-11 15:22:26 (GMT) |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-07-16 23:54:25 (GMT) |
commit | df2022553dd8d34d49e16c19d851ea619438f0ef (patch) | |
tree | 9850c967e0fbee1cb99e900c2e60a0ad50b528da /drivers/usb/host/ehci-timer.c | |
parent | 314466101c6ae14f6f5db8a86eda1509ba2c02a8 (diff) | |
download | linux-fsl-qoriq-df2022553dd8d34d49e16c19d851ea619438f0ef.tar.xz |
USB: EHCI: use hrtimer for interrupt QH unlink
This patch (as1577) adds hrtimer support for unlinking interrupt QHs
in ehci-hcd. The current code relies on a fixed delay of either 2 or
55 us, which is not always adequate and in any case is totally bogus.
Thanks to internal caching, the EHCI hardware may continue to access
an interrupt QH for more than a millisecond after it has been unlinked.
In fact, the EHCI spec doesn't say how long to wait before using an
unlinked interrupt QH. The patch sets the delay to 9 microframes
minimum, which ought to be adequate.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/host/ehci-timer.c')
-rw-r--r-- | drivers/usb/host/ehci-timer.c | 34 |
1 files changed, 34 insertions, 0 deletions
diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 1e907dd..bd8b591 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -69,6 +69,7 @@ static void ehci_clear_command_bit(struct ehci_hcd *ehci, u32 bit) static unsigned event_delays_ns[] = { 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_ASS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ + 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; @@ -192,6 +193,38 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) } +/* Handle unlinked interrupt QHs once they are gone from the hardware */ +static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) +{ + bool stopped = (ehci->rh_state < EHCI_RH_RUNNING); + + /* + * Process all the QHs on the intr_unlink list that were added + * before the current unlink cycle began. The list is in + * temporal order, so stop when we reach the first entry in the + * current cycle. But if the root hub isn't running then + * process all the QHs on the list. + */ + ehci->intr_unlinking = true; + while (ehci->intr_unlink) { + struct ehci_qh *qh = ehci->intr_unlink; + + if (!stopped && qh->unlink_cycle == ehci->intr_unlink_cycle) + break; + ehci->intr_unlink = qh->unlink_next; + qh->unlink_next = NULL; + end_unlink_intr(ehci, qh); + } + + /* Handle remaining entries later */ + if (ehci->intr_unlink) { + ehci_enable_event(ehci, EHCI_HRTIMER_UNLINK_INTR, true); + ++ehci->intr_unlink_cycle; + } + ehci->intr_unlinking = false; +} + + /* * Handler functions for the hrtimer event types. * Keep this array in the same order as the event types indexed by @@ -200,6 +233,7 @@ static void ehci_disable_PSE(struct ehci_hcd *ehci) static void (*event_handlers[])(struct ehci_hcd *) = { ehci_poll_ASS, /* EHCI_HRTIMER_POLL_ASS */ ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ + ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; |