diff options
| author | David Brownell <david-b@pacbell.net> | 2003-05-01 10:01:35 -0700 |
|---|---|---|
| committer | Greg Kroah-Hartman <greg@kroah.com> | 2003-05-01 10:01:35 -0700 |
| commit | 6971329b2f73dffddf26538e0099457fe3a87b32 (patch) | |
| tree | 3e768b272d9c319de95589856530ba61771dfb0f | |
| parent | bf543ace2fd96f149980a2b596168d56cd047fae (diff) | |
[PATCH] USB: ehci i/o watchdog
This patch adds a new "I/O watchdog" role to the existing
timer code, and cleans it up a bit. If you want to run
EHCI without IRQs, it's now simple: disable them, and
tweak the timer appropriately.
The patch should help with these reported problems.
(a) Bulk I/O sometimes seems to stop progressing. Not
trouble in itself, but usb-storage and scsi could
wedge deeply because of bugs in their fault recovery;
and then the problems could break khubd and rmmod...
(b) Some periodic transfers need to be "jumpstarted".
Usually seen with a high speed hub.
| -rw-r--r-- | drivers/usb/host/ehci-hcd.c | 24 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-q.c | 35 | ||||
| -rw-r--r-- | drivers/usb/host/ehci.h | 51 |
3 files changed, 77 insertions, 33 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index f2fc3aa6e663..9ce8a6876ee6 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -116,8 +116,10 @@ static const char hcd_name [] = "ehci-hcd"; #define EHCI_TUNE_MULT_TT 1 #define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ -#define EHCI_WATCHDOG_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#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 */ /* Initial IRQ latency: lower than default */ static int log2_irq_thresh = 0; // 0 to 6 @@ -266,16 +268,13 @@ static void ehci_watchdog (unsigned long param) } } + /* stop async processing after it's idled a bit */ + if (test_bit (TIMER_ASYNC_OFF, &ehci->actions)) + start_unlink_async (ehci, ehci->async); + + /* ehci could run by timer, without IRQs ... */ ehci_work (ehci, NULL); - if (ehci->reclaim && !timer_pending (&ehci->watchdog)) - mod_timer (&ehci->watchdog, - jiffies + EHCI_WATCHDOG_JIFFIES); - /* stop async processing after it's idled a while */ - else if (ehci->async_idle) { - start_unlink_async (ehci, ehci->async); - ehci->async_idle = 0; - } spin_unlock_irqrestore (&ehci->lock, flags); } @@ -658,11 +657,18 @@ static int ehci_resume (struct usb_hcd *hcd) */ static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs) { + timer_action_done (ehci, TIMER_IO_WATCHDOG); if (ehci->reclaim_ready) end_unlink_async (ehci, regs); scan_async (ehci, regs); if (ehci->next_uframe != -1) scan_periodic (ehci, regs); + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + */ + if ((ehci->async->qh_next.ptr != 0) || (ehci->periodic_sched != 0)) + timer_action (ehci, TIMER_IO_WATCHDOG); } /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index d9e4c58fd0db..b2243ca6bef8 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -706,8 +706,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) /* (re)start the async schedule? */ head = ehci->async; - if (ehci->async_idle) - del_timer (&ehci->watchdog); + timer_action_done (ehci, TIMER_ASYNC_OFF); if (!head->qh_next.qh) { u32 cmd = readl (&ehci->regs->command); @@ -733,8 +732,6 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) qh->qh_state = QH_STATE_LINKED; /* qtd completions reported later by interrupt */ - - ehci->async_idle = 0; } /*-------------------------------------------------------------------------*/ @@ -915,7 +912,7 @@ static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs) struct ehci_qh *qh = ehci->reclaim; struct ehci_qh *next; - del_timer (&ehci->watchdog); + timer_action_done (ehci, TIMER_IAA_WATCHDOG); // qh->hw_next = cpu_to_le32 (qh->qh_dma); qh->qh_state = QH_STATE_IDLE; @@ -940,12 +937,8 @@ static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs) * active but idle for a while once it empties. */ if (HCD_IS_RUNNING (ehci->hcd.state) - && ehci->async->qh_next.qh == 0 - && !timer_pending (&ehci->watchdog)) { - ehci->async_idle = 1; - mod_timer (&ehci->watchdog, - jiffies + EHCI_ASYNC_JIFFIES); - } + && ehci->async->qh_next.qh == 0) + timer_action (ehci, TIMER_ASYNC_OFF); } if (next) @@ -980,6 +973,7 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) wmb (); // handshake later, if we need to } + timer_action_done (ehci, TIMER_ASYNC_OFF); return; } @@ -1005,9 +999,8 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ehci->reclaim_ready = 0; cmd |= CMD_IAAD; writel (cmd, &ehci->regs->command); - /* posted write need not be known to HC yet ... */ - - mod_timer (&ehci->watchdog, jiffies + EHCI_WATCHDOG_JIFFIES); + (void) readl (&ehci->regs->command); + timer_action (ehci, TIMER_IAA_WATCHDOG); } /*-------------------------------------------------------------------------*/ @@ -1016,10 +1009,11 @@ static void scan_async (struct ehci_hcd *ehci, struct pt_regs *regs) { struct ehci_qh *qh; - int unlink_delay = 0; + enum ehci_timer_action action = TIMER_IO_WATCHDOG; if (!++(ehci->stamp)) ehci->stamp++; + timer_action_done (ehci, TIMER_ASYNC_SHRINK); rescan: qh = ehci->async->qh_next.qh; if (likely (qh != 0)) { @@ -1051,17 +1045,14 @@ rescan: */ if (list_empty (&qh->qtd_list)) { if (qh->stamp == ehci->stamp) - unlink_delay = 1; - else if (!ehci->reclaim) { + action = TIMER_ASYNC_SHRINK; + else if (!ehci->reclaim) start_unlink_async (ehci, qh); - unlink_delay = 0; - } } qh = qh->qh_next.qh; } while (qh); } - - if (unlink_delay && !timer_pending (&ehci->watchdog)) - mod_timer (&ehci->watchdog, jiffies + EHCI_WATCHDOG_JIFFIES/2); + if (action == TIMER_ASYNC_SHRINK) + timer_action (ehci, TIMER_ASYNC_SHRINK); } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 72d51be569de..c730b7ea5a2c 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -52,8 +52,7 @@ struct ehci_hcd { /* one per controller */ /* async schedule support */ struct ehci_qh *async; struct ehci_qh *reclaim; - int reclaim_ready : 1, - async_idle : 1; + int reclaim_ready : 1; /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ @@ -83,6 +82,7 @@ struct ehci_hcd { /* one per controller */ struct timer_list watchdog; struct notifier_block reboot_notifier; + unsigned long actions; unsigned stamp; /* irq statistics */ @@ -100,6 +100,53 @@ struct ehci_hcd { /* one per controller */ /* NOTE: urb->transfer_flags expected to not use this bit !!! */ #define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */ +enum ehci_timer_action { + TIMER_IO_WATCHDOG, + TIMER_IAA_WATCHDOG, + TIMER_ASYNC_SHRINK, + TIMER_ASYNC_OFF, +}; + +static inline void +timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action) +{ + clear_bit (action, &ehci->actions); +} + +static inline void +timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) +{ + if (!test_and_set_bit (action, &ehci->actions)) { + unsigned long t; + + switch (action) { + case TIMER_IAA_WATCHDOG: + t = EHCI_IAA_JIFFIES; + break; + case TIMER_IO_WATCHDOG: + t = EHCI_IO_JIFFIES; + break; + case TIMER_ASYNC_OFF: + t = EHCI_ASYNC_JIFFIES; + break; + // case TIMER_ASYNC_SHRINK: + default: + t = EHCI_SHRINK_JIFFIES; + break; + } + t += jiffies; + // all timings except IAA watchdog can be overridden. + // async queue SHRINK often precedes IAA. while it's ready + // to go OFF neither can matter, and afterwards the IO + // watchdog stops unless there's still periodic traffic. + if (action != TIMER_IAA_WATCHDOG + && t > ehci->watchdog.expires + && timer_pending (&ehci->watchdog)) + return; + mod_timer (&ehci->watchdog, t); + } +} + /*-------------------------------------------------------------------------*/ /* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ |
