Commit 6971329b authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[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.
parent bf543ace
...@@ -116,8 +116,10 @@ static const char hcd_name [] = "ehci-hcd"; ...@@ -116,8 +116,10 @@ static const char hcd_name [] = "ehci-hcd";
#define EHCI_TUNE_MULT_TT 1 #define EHCI_TUNE_MULT_TT 1
#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ #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_ASYNC_JIFFIES (HZ/20) /* async idle timeout */
#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */
/* Initial IRQ latency: lower than default */ /* Initial IRQ latency: lower than default */
static int log2_irq_thresh = 0; // 0 to 6 static int log2_irq_thresh = 0; // 0 to 6
...@@ -266,16 +268,13 @@ static void ehci_watchdog (unsigned long param) ...@@ -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); 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); spin_unlock_irqrestore (&ehci->lock, flags);
} }
...@@ -658,11 +657,18 @@ static int ehci_resume (struct usb_hcd *hcd) ...@@ -658,11 +657,18 @@ static int ehci_resume (struct usb_hcd *hcd)
*/ */
static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs) static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs)
{ {
timer_action_done (ehci, TIMER_IO_WATCHDOG);
if (ehci->reclaim_ready) if (ehci->reclaim_ready)
end_unlink_async (ehci, regs); end_unlink_async (ehci, regs);
scan_async (ehci, regs); scan_async (ehci, regs);
if (ehci->next_uframe != -1) if (ehci->next_uframe != -1)
scan_periodic (ehci, regs); 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);
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -706,8 +706,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -706,8 +706,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
/* (re)start the async schedule? */ /* (re)start the async schedule? */
head = ehci->async; head = ehci->async;
if (ehci->async_idle) timer_action_done (ehci, TIMER_ASYNC_OFF);
del_timer (&ehci->watchdog);
if (!head->qh_next.qh) { if (!head->qh_next.qh) {
u32 cmd = readl (&ehci->regs->command); u32 cmd = readl (&ehci->regs->command);
...@@ -733,8 +732,6 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -733,8 +732,6 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
qh->qh_state = QH_STATE_LINKED; qh->qh_state = QH_STATE_LINKED;
/* qtd completions reported later by interrupt */ /* 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) ...@@ -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 *qh = ehci->reclaim;
struct ehci_qh *next; 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->hw_next = cpu_to_le32 (qh->qh_dma);
qh->qh_state = QH_STATE_IDLE; qh->qh_state = QH_STATE_IDLE;
...@@ -940,12 +937,8 @@ static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs) ...@@ -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. * active but idle for a while once it empties.
*/ */
if (HCD_IS_RUNNING (ehci->hcd.state) if (HCD_IS_RUNNING (ehci->hcd.state)
&& ehci->async->qh_next.qh == 0 && ehci->async->qh_next.qh == 0)
&& !timer_pending (&ehci->watchdog)) { timer_action (ehci, TIMER_ASYNC_OFF);
ehci->async_idle = 1;
mod_timer (&ehci->watchdog,
jiffies + EHCI_ASYNC_JIFFIES);
}
} }
if (next) if (next)
...@@ -980,6 +973,7 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -980,6 +973,7 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
wmb (); wmb ();
// handshake later, if we need to // handshake later, if we need to
} }
timer_action_done (ehci, TIMER_ASYNC_OFF);
return; return;
} }
...@@ -1005,9 +999,8 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -1005,9 +999,8 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
ehci->reclaim_ready = 0; ehci->reclaim_ready = 0;
cmd |= CMD_IAAD; cmd |= CMD_IAAD;
writel (cmd, &ehci->regs->command); writel (cmd, &ehci->regs->command);
/* posted write need not be known to HC yet ... */ (void) readl (&ehci->regs->command);
timer_action (ehci, TIMER_IAA_WATCHDOG);
mod_timer (&ehci->watchdog, jiffies + EHCI_WATCHDOG_JIFFIES);
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -1016,10 +1009,11 @@ static void ...@@ -1016,10 +1009,11 @@ static void
scan_async (struct ehci_hcd *ehci, struct pt_regs *regs) scan_async (struct ehci_hcd *ehci, struct pt_regs *regs)
{ {
struct ehci_qh *qh; struct ehci_qh *qh;
int unlink_delay = 0; enum ehci_timer_action action = TIMER_IO_WATCHDOG;
if (!++(ehci->stamp)) if (!++(ehci->stamp))
ehci->stamp++; ehci->stamp++;
timer_action_done (ehci, TIMER_ASYNC_SHRINK);
rescan: rescan:
qh = ehci->async->qh_next.qh; qh = ehci->async->qh_next.qh;
if (likely (qh != 0)) { if (likely (qh != 0)) {
...@@ -1051,17 +1045,14 @@ scan_async (struct ehci_hcd *ehci, struct pt_regs *regs) ...@@ -1051,17 +1045,14 @@ scan_async (struct ehci_hcd *ehci, struct pt_regs *regs)
*/ */
if (list_empty (&qh->qtd_list)) { if (list_empty (&qh->qtd_list)) {
if (qh->stamp == ehci->stamp) if (qh->stamp == ehci->stamp)
unlink_delay = 1; action = TIMER_ASYNC_SHRINK;
else if (!ehci->reclaim) { else if (!ehci->reclaim)
start_unlink_async (ehci, qh); start_unlink_async (ehci, qh);
unlink_delay = 0;
}
} }
qh = qh->qh_next.qh; qh = qh->qh_next.qh;
} while (qh); } while (qh);
} }
if (action == TIMER_ASYNC_SHRINK)
if (unlink_delay && !timer_pending (&ehci->watchdog)) timer_action (ehci, TIMER_ASYNC_SHRINK);
mod_timer (&ehci->watchdog, jiffies + EHCI_WATCHDOG_JIFFIES/2);
} }
...@@ -52,8 +52,7 @@ struct ehci_hcd { /* one per controller */ ...@@ -52,8 +52,7 @@ struct ehci_hcd { /* one per controller */
/* async schedule support */ /* async schedule support */
struct ehci_qh *async; struct ehci_qh *async;
struct ehci_qh *reclaim; struct ehci_qh *reclaim;
int reclaim_ready : 1, int reclaim_ready : 1;
async_idle : 1;
/* periodic schedule support */ /* periodic schedule support */
#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */
...@@ -83,6 +82,7 @@ struct ehci_hcd { /* one per controller */ ...@@ -83,6 +82,7 @@ struct ehci_hcd { /* one per controller */
struct timer_list watchdog; struct timer_list watchdog;
struct notifier_block reboot_notifier; struct notifier_block reboot_notifier;
unsigned long actions;
unsigned stamp; unsigned stamp;
/* irq statistics */ /* irq statistics */
...@@ -100,6 +100,53 @@ struct ehci_hcd { /* one per controller */ ...@@ -100,6 +100,53 @@ struct ehci_hcd { /* one per controller */
/* NOTE: urb->transfer_flags expected to not use this bit !!! */ /* NOTE: urb->transfer_flags expected to not use this bit !!! */
#define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */ #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 */ /* EHCI register interface, corresponds to EHCI Revision 0.95 specification */
......
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