Commit 32830f20 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: EHCI: use hrtimer for unlinking empty async QHs

This patch (as1583) changes ehci-hcd to use an hrtimer event for
unlinking empty (unused) async QHs instead of using a kernel timer.

The check for empty QHs is moved to a new routine, where it doesn't
require going through an entire scan of both the async and periodic
schedules.  And it can unlink multiple QHs at once, unlike the current
code.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 3c273a05
...@@ -94,8 +94,6 @@ static const char hcd_name [] = "ehci_hcd"; ...@@ -94,8 +94,6 @@ static const char hcd_name [] = "ehci_hcd";
#define EHCI_TUNE_FLS 1 /* (medium) 512-frame schedule */ #define EHCI_TUNE_FLS 1 /* (medium) 512-frame schedule */
#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ #define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */
#define EHCI_SHRINK_JIFFIES (DIV_ROUND_UP(HZ, 200) + 1)
/* 5-ms async qh unlink delay */
/* Initial IRQ latency: faster than hw default */ /* Initial IRQ latency: faster than hw default */
static int log2_irq_thresh = 0; // 0 to 6 static int log2_irq_thresh = 0; // 0 to 6
...@@ -130,15 +128,6 @@ MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); ...@@ -130,15 +128,6 @@ MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us");
static void static void
timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action)
{ {
/* Don't override timeouts which shrink or (later) disable
* the async ring; just the I/O watchdog. Note that if a
* SHRINK were pending, OFF would never be requested.
*/
if (timer_pending(&ehci->watchdog)
&& (BIT(TIMER_ASYNC_SHRINK)
& ehci->actions))
return;
if (!test_and_set_bit(action, &ehci->actions)) { if (!test_and_set_bit(action, &ehci->actions)) {
unsigned long t; unsigned long t;
...@@ -148,10 +137,6 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) ...@@ -148,10 +137,6 @@ timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action)
return; return;
t = EHCI_IO_JIFFIES; t = EHCI_IO_JIFFIES;
break; break;
/* case TIMER_ASYNC_SHRINK: */
default:
t = EHCI_SHRINK_JIFFIES;
break;
} }
mod_timer(&ehci->watchdog, t + jiffies); mod_timer(&ehci->watchdog, t + jiffies);
} }
...@@ -307,6 +292,7 @@ static void ehci_quiesce (struct ehci_hcd *ehci) ...@@ -307,6 +292,7 @@ static void ehci_quiesce (struct ehci_hcd *ehci)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static void end_unlink_async(struct ehci_hcd *ehci); static void end_unlink_async(struct ehci_hcd *ehci);
static void unlink_empty_async(struct ehci_hcd *ehci);
static void ehci_work(struct ehci_hcd *ehci); static void ehci_work(struct ehci_hcd *ehci);
static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh);
static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh); static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh);
......
...@@ -300,6 +300,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ...@@ -300,6 +300,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
ehci->rh_state = EHCI_RH_SUSPENDED; ehci->rh_state = EHCI_RH_SUSPENDED;
end_unlink_async(ehci); end_unlink_async(ehci);
unlink_empty_async(ehci);
ehci_handle_intr_unlinks(ehci); ehci_handle_intr_unlinks(ehci);
end_free_itds(ehci); end_free_itds(ehci);
......
...@@ -1205,7 +1205,7 @@ static void start_iaa_cycle(struct ehci_hcd *ehci, bool nested) ...@@ -1205,7 +1205,7 @@ static void start_iaa_cycle(struct ehci_hcd *ehci, bool nested)
end_unlink_async(ehci); end_unlink_async(ehci);
/* Otherwise start a new IAA cycle */ /* Otherwise start a new IAA cycle */
} else { } else if (likely(ehci->rh_state == EHCI_RH_RUNNING)) {
/* Make sure the unlinks are all visible to the hardware */ /* Make sure the unlinks are all visible to the hardware */
wmb(); wmb();
...@@ -1253,6 +1253,39 @@ static void end_unlink_async(struct ehci_hcd *ehci) ...@@ -1253,6 +1253,39 @@ static void end_unlink_async(struct ehci_hcd *ehci)
} }
} }
static void unlink_empty_async(struct ehci_hcd *ehci)
{
struct ehci_qh *qh, *next;
bool stopped = (ehci->rh_state < EHCI_RH_RUNNING);
bool check_unlinks_later = false;
/* Unlink all the async QHs that have been empty for a timer cycle */
next = ehci->async->qh_next.qh;
while (next) {
qh = next;
next = qh->qh_next.qh;
if (list_empty(&qh->qtd_list) &&
qh->qh_state == QH_STATE_LINKED) {
if (!stopped && qh->unlink_cycle ==
ehci->async_unlink_cycle)
check_unlinks_later = true;
else
single_unlink_async(ehci, qh);
}
}
/* Start a new IAA cycle if any QHs are waiting for it */
if (ehci->async_unlink)
start_iaa_cycle(ehci, false);
/* QHs that haven't been empty for long enough will be handled later */
if (check_unlinks_later) {
ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true);
++ehci->async_unlink_cycle;
}
}
/* makes sure the async qh will become idle */ /* makes sure the async qh will become idle */
/* caller must own ehci->lock */ /* caller must own ehci->lock */
...@@ -1277,12 +1310,8 @@ static void start_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -1277,12 +1310,8 @@ static void start_unlink_async(struct ehci_hcd *ehci, struct ehci_qh *qh)
static void scan_async (struct ehci_hcd *ehci) static void scan_async (struct ehci_hcd *ehci)
{ {
bool stopped;
struct ehci_qh *qh; struct ehci_qh *qh;
enum ehci_timer_action action = TIMER_IO_WATCHDOG; bool check_unlinks_later = false;
timer_action_done (ehci, TIMER_ASYNC_SHRINK);
stopped = (ehci->rh_state < EHCI_RH_RUNNING);
ehci->qh_scan_next = ehci->async->qh_next.qh; ehci->qh_scan_next = ehci->async->qh_next.qh;
while (ehci->qh_scan_next) { while (ehci->qh_scan_next) {
...@@ -1301,28 +1330,27 @@ static void scan_async (struct ehci_hcd *ehci) ...@@ -1301,28 +1330,27 @@ static void scan_async (struct ehci_hcd *ehci)
* in single_unlink_async(). * in single_unlink_async().
*/ */
temp = qh_completions(ehci, qh); temp = qh_completions(ehci, qh);
if (qh->needs_rescan) if (qh->needs_rescan) {
start_unlink_async(ehci, qh); start_unlink_async(ehci, qh);
qh->unlink_time = jiffies + EHCI_SHRINK_JIFFIES; } else if (list_empty(&qh->qtd_list)
if (temp != 0) && qh->qh_state == QH_STATE_LINKED) {
qh->unlink_cycle = ehci->async_unlink_cycle;
check_unlinks_later = true;
} else if (temp != 0)
goto rescan; goto rescan;
} }
}
/* unlink idle entries, reducing DMA usage as well /*
* as HCD schedule-scanning costs. delay for any qh * Unlink empty entries, reducing DMA usage as well
* we just scanned, there's a not-unusual case that it * as HCD schedule-scanning costs. Delay for any qh
* doesn't stay idle for long. * we just scanned, there's a not-unusual case that it
* (plus, avoids some kind of re-activation race.) * doesn't stay idle for long.
*/ */
if (list_empty(&qh->qtd_list) if (check_unlinks_later && ehci->rh_state == EHCI_RH_RUNNING &&
&& qh->qh_state == QH_STATE_LINKED) { !(ehci->enabled_hrtimer_events &
if (!ehci->async_unlink && (stopped || BIT(EHCI_HRTIMER_ASYNC_UNLINKS))) {
time_after_eq(jiffies, qh->unlink_time))) ehci_enable_event(ehci, EHCI_HRTIMER_ASYNC_UNLINKS, true);
start_unlink_async(ehci, qh); ++ehci->async_unlink_cycle;
else
action = TIMER_ASYNC_SHRINK;
}
} }
if (action == TIMER_ASYNC_SHRINK)
timer_action (ehci, TIMER_ASYNC_SHRINK);
} }
...@@ -72,6 +72,7 @@ static unsigned event_delays_ns[] = { ...@@ -72,6 +72,7 @@ static unsigned event_delays_ns[] = {
1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */ 1 * NSEC_PER_MSEC, /* EHCI_HRTIMER_POLL_DEAD */
1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */ 1125 * NSEC_PER_USEC, /* EHCI_HRTIMER_UNLINK_INTR */
2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */ 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_FREE_ITDS */
6 * NSEC_PER_MSEC, /* EHCI_HRTIMER_ASYNC_UNLINKS */
10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_IAA_WATCHDOG */
10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */ 10 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_PERIODIC */
15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */ 15 * NSEC_PER_MSEC, /* EHCI_HRTIMER_DISABLE_ASYNC */
...@@ -347,6 +348,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = { ...@@ -347,6 +348,7 @@ static void (*event_handlers[])(struct ehci_hcd *) = {
ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */ ehci_handle_controller_death, /* EHCI_HRTIMER_POLL_DEAD */
ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */ ehci_handle_intr_unlinks, /* EHCI_HRTIMER_UNLINK_INTR */
end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */ end_free_itds, /* EHCI_HRTIMER_FREE_ITDS */
unlink_empty_async, /* EHCI_HRTIMER_ASYNC_UNLINKS */
ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */ ehci_iaa_watchdog, /* EHCI_HRTIMER_IAA_WATCHDOG */
ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */ ehci_disable_PSE, /* EHCI_HRTIMER_DISABLE_PERIODIC */
ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */ ehci_disable_ASE, /* EHCI_HRTIMER_DISABLE_ASYNC */
......
...@@ -84,6 +84,7 @@ enum ehci_hrtimer_event { ...@@ -84,6 +84,7 @@ enum ehci_hrtimer_event {
EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ EHCI_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */
EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ EHCI_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */
EHCI_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ EHCI_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */
EHCI_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */
EHCI_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ EHCI_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */
EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ EHCI_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */
EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ EHCI_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */
...@@ -123,6 +124,7 @@ struct ehci_hcd { /* one per controller */ ...@@ -123,6 +124,7 @@ struct ehci_hcd { /* one per controller */
struct ehci_qh *async_unlink_last; struct ehci_qh *async_unlink_last;
struct ehci_qh *async_iaa; struct ehci_qh *async_iaa;
struct ehci_qh *qh_scan_next; struct ehci_qh *qh_scan_next;
unsigned async_unlink_cycle;
unsigned async_count; /* async activity count */ unsigned async_count; /* async activity count */
/* periodic schedule support */ /* periodic schedule support */
...@@ -232,7 +234,6 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) ...@@ -232,7 +234,6 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci)
enum ehci_timer_action { enum ehci_timer_action {
TIMER_IO_WATCHDOG, TIMER_IO_WATCHDOG,
TIMER_ASYNC_SHRINK,
}; };
static inline void static inline void
...@@ -382,7 +383,6 @@ struct ehci_qh { ...@@ -382,7 +383,6 @@ struct ehci_qh {
struct ehci_qtd *dummy; struct ehci_qtd *dummy;
struct ehci_qh *unlink_next; /* next on unlink list */ struct ehci_qh *unlink_next; /* next on unlink list */
unsigned long unlink_time;
unsigned unlink_cycle; unsigned unlink_cycle;
unsigned stamp; unsigned stamp;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment