diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2012-07-11 19:22:35 +0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-07-17 03:54:25 +0400 |
commit | 55934eb3b9fa52eb53b9d7342267fc73c38206aa (patch) | |
tree | 66666fa4f08121a53152cb956a6ef36026edc057 /drivers/usb/host/ehci-timer.c | |
parent | bf6387bcd16975ba8952b094f262a359d74e1c8a (diff) | |
download | linux-55934eb3b9fa52eb53b9d7342267fc73c38206aa.tar.xz |
USB: EHCI: use hrtimer for (s)iTD deallocation
This patch (as1579) adds an hrtimer event to handle deallocation of
iTDs and siTDs in ehci-hcd.
Because of the frame-oriented approach used by the EHCI periodic
schedule, the hardware can continue to access the Transfer Descriptor
for isochronous (or split-isochronous) transactions for up to a
millisecond after the transaction completes. The iTD (or siTD) must
not be reused before then.
The strategy currently used involves putting completed iTDs on a list
of cached entries and every so often returning them to the endpoint's
free list. The new strategy reduces overhead by putting completed
iTDs back on the free list immediately, although they are not reused
until it is safe to do so.
When the isochronous endpoint stops (its queue becomes empty), the
iTDs on its free list get moved to a global list, from which they will
be deallocated after a minimum of 2 ms. This delay is what the new
hrtimer event is for.
Overall this may not be a tremendous improvement over the current
code, but to me it seems a lot more clear and logical. In addition,
it removes the need for each iTD to keep a reference to the
ehci_iso_stream it belongs to, since the iTD never needs to be moved
back to the stream's free list from the global list.
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 | 50 |
1 files changed, 46 insertions, 4 deletions
diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 0c5326a8883c..8feb60ff4228 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -71,6 +71,7 @@ static unsigned event_delays_ns[] = { 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_PSS */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ + 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; @@ -165,7 +166,6 @@ static void ehci_poll_PSS(struct ehci_hcd *ehci) /* The status is up-to-date; restart or stop the schedule as needed */ if (want == 0) { /* Stopped */ - free_cached_lists(ehci); if (ehci->periodic_count > 0) { /* make sure ehci_work scans these */ @@ -188,9 +188,6 @@ static void ehci_poll_PSS(struct ehci_hcd *ehci) static void ehci_disable_PSE(struct ehci_hcd *ehci) { ehci_clear_command_bit(ehci, CMD_PSE); - - /* Poll to see when it actually stops */ - ehci_enable_event(ehci, EHCI_HRTIMER_POLL_PSS, true); } @@ -250,6 +247,50 @@ static void ehci_handle_intr_unlinks(struct ehci_hcd *ehci) } +/* Start another free-iTDs/siTDs cycle */ +static void start_free_itds(struct ehci_hcd *ehci) +{ + if (!(ehci->enabled_hrtimer_events & BIT(EHCI_HRTIMER_FREE_ITDS))) { + ehci->last_itd_to_free = list_entry( + ehci->cached_itd_list.prev, + struct ehci_itd, itd_list); + ehci->last_sitd_to_free = list_entry( + ehci->cached_sitd_list.prev, + struct ehci_sitd, sitd_list); + ehci_enable_event(ehci, EHCI_HRTIMER_FREE_ITDS, true); + } +} + +/* Wait for controller to stop using old iTDs and siTDs */ +static void end_free_itds(struct ehci_hcd *ehci) +{ + struct ehci_itd *itd, *n; + struct ehci_sitd *sitd, *sn; + + if (ehci->rh_state < EHCI_RH_RUNNING) { + ehci->last_itd_to_free = NULL; + ehci->last_sitd_to_free = NULL; + } + + list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) { + list_del(&itd->itd_list); + dma_pool_free(ehci->itd_pool, itd, itd->itd_dma); + if (itd == ehci->last_itd_to_free) + break; + } + list_for_each_entry_safe(sitd, sn, &ehci->cached_sitd_list, sitd_list) { + list_del(&sitd->sitd_list); + dma_pool_free(ehci->sitd_pool, sitd, sitd->sitd_dma); + if (sitd == ehci->last_sitd_to_free) + break; + } + + if (!list_empty(&ehci->cached_itd_list) || + !list_empty(&ehci->cached_sitd_list)) + start_free_itds(ehci); +} + + /* * Handler functions for the hrtimer event types. * Keep this array in the same order as the event types indexed by @@ -260,6 +301,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = { ehci_poll_PSS, /* EHCI_HRTIMER_POLL_PSS */ ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ + end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ }; |