Commit 26f953fd authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

USB: EHCI update VIA workaround

This revamps handling of the hardware "async advance" IRQ, and its watchdog
timer.  Basically it dis-entangles that important timeout from the others,
simplifying the associated state and code to make it more robust.

This reportedly improves behavior of EHCI on some systems with VIA chips,
and AFAIK won't affect non-VIA hardware.  VIA systems need this code to
recover from silcon bugs whereby the "async advance" IRQ isn't issued.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 353a4098
...@@ -754,9 +754,7 @@ show_registers (struct class_device *class_dev, char *buf) ...@@ -754,9 +754,7 @@ show_registers (struct class_device *class_dev, char *buf)
} }
if (ehci->reclaim) { if (ehci->reclaim) {
temp = scnprintf (next, size, "reclaim qh %p%s\n", temp = scnprintf (next, size, "reclaim qh %p\n", ehci->reclaim);
ehci->reclaim,
ehci->reclaim_ready ? " ready" : "");
size -= temp; size -= temp;
next += temp; next += temp;
} }
......
...@@ -111,7 +111,7 @@ static const char hcd_name [] = "ehci_hcd"; ...@@ -111,7 +111,7 @@ 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_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ #define EHCI_IAA_MSECS 10 /* arbitrary */
#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ #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 */ #define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */
...@@ -254,6 +254,7 @@ static void ehci_quiesce (struct ehci_hcd *ehci) ...@@ -254,6 +254,7 @@ static void ehci_quiesce (struct ehci_hcd *ehci)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs);
static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs); static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs);
#include "ehci-hub.c" #include "ehci-hub.c"
...@@ -263,28 +264,39 @@ static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs); ...@@ -263,28 +264,39 @@ static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs);
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static void ehci_watchdog (unsigned long param) static void ehci_iaa_watchdog (unsigned long param)
{ {
struct ehci_hcd *ehci = (struct ehci_hcd *) param; struct ehci_hcd *ehci = (struct ehci_hcd *) param;
unsigned long flags; unsigned long flags;
u32 status;
spin_lock_irqsave (&ehci->lock, flags); spin_lock_irqsave (&ehci->lock, flags);
WARN_ON(!ehci->reclaim);
/* lost IAA irqs wedge things badly; seen with a vt8235 */ /* lost IAA irqs wedge things badly; seen first with a vt8235 */
if (ehci->reclaim) { if (ehci->reclaim) {
u32 status = readl (&ehci->regs->status); status = readl (&ehci->regs->status);
if (status & STS_IAA) { if (status & STS_IAA) {
ehci_vdbg (ehci, "lost IAA\n"); ehci_vdbg (ehci, "lost IAA\n");
COUNT (ehci->stats.lost_iaa); COUNT (ehci->stats.lost_iaa);
writel (STS_IAA, &ehci->regs->status); writel (STS_IAA, &ehci->regs->status);
ehci->reclaim_ready = 1; end_unlink_async (ehci, NULL);
} }
} }
/* stop async processing after it's idled a bit */ spin_unlock_irqrestore (&ehci->lock, flags);
}
static void ehci_watchdog (unsigned long param)
{
struct ehci_hcd *ehci = (struct ehci_hcd *) param;
unsigned long flags;
spin_lock_irqsave (&ehci->lock, flags);
/* stop async processing after it's idled a bit */
if (test_bit (TIMER_ASYNC_OFF, &ehci->actions)) if (test_bit (TIMER_ASYNC_OFF, &ehci->actions))
start_unlink_async (ehci, ehci->async); start_unlink_async (ehci, ehci->async);
/* ehci could run by timer, without IRQs ... */ /* ehci could run by timer, without IRQs ... */
ehci_work (ehci, NULL); ehci_work (ehci, NULL);
...@@ -333,8 +345,6 @@ static void ehci_port_power (struct ehci_hcd *ehci, int is_on) ...@@ -333,8 +345,6 @@ static void ehci_port_power (struct ehci_hcd *ehci, int is_on)
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); timer_action_done (ehci, TIMER_IO_WATCHDOG);
if (ehci->reclaim_ready)
end_unlink_async (ehci, regs);
/* another CPU may drop ehci->lock during a schedule scan while /* another CPU may drop ehci->lock during a schedule scan while
* it reports urb completions. this flag guards against bogus * it reports urb completions. this flag guards against bogus
...@@ -369,6 +379,7 @@ static void ehci_stop (struct usb_hcd *hcd) ...@@ -369,6 +379,7 @@ static void ehci_stop (struct usb_hcd *hcd)
/* no more interrupts ... */ /* no more interrupts ... */
del_timer_sync (&ehci->watchdog); del_timer_sync (&ehci->watchdog);
del_timer_sync (&ehci->iaa_watchdog);
spin_lock_irq(&ehci->lock); spin_lock_irq(&ehci->lock);
if (HC_IS_RUNNING (hcd->state)) if (HC_IS_RUNNING (hcd->state))
...@@ -415,6 +426,10 @@ static int ehci_init(struct usb_hcd *hcd) ...@@ -415,6 +426,10 @@ static int ehci_init(struct usb_hcd *hcd)
ehci->watchdog.function = ehci_watchdog; ehci->watchdog.function = ehci_watchdog;
ehci->watchdog.data = (unsigned long) ehci; ehci->watchdog.data = (unsigned long) ehci;
init_timer(&ehci->iaa_watchdog);
ehci->iaa_watchdog.function = ehci_iaa_watchdog;
ehci->iaa_watchdog.data = (unsigned long) ehci;
/* /*
* hw default: 1K periodic list heads, one per frame. * hw default: 1K periodic list heads, one per frame.
* periodic_size can shrink by USBCMD update if hcc_params allows. * periodic_size can shrink by USBCMD update if hcc_params allows.
...@@ -431,7 +446,6 @@ static int ehci_init(struct usb_hcd *hcd) ...@@ -431,7 +446,6 @@ static int ehci_init(struct usb_hcd *hcd)
ehci->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); ehci->i_thresh = 2 + HCC_ISOC_THRES(hcc_params);
ehci->reclaim = NULL; ehci->reclaim = NULL;
ehci->reclaim_ready = 0;
ehci->next_uframe = -1; ehci->next_uframe = -1;
/* /*
...@@ -605,7 +619,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs) ...@@ -605,7 +619,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)
/* complete the unlinking of some qh [4.15.2.3] */ /* complete the unlinking of some qh [4.15.2.3] */
if (status & STS_IAA) { if (status & STS_IAA) {
COUNT (ehci->stats.reclaim); COUNT (ehci->stats.reclaim);
ehci->reclaim_ready = 1; end_unlink_async (ehci, regs);
bh = 1; bh = 1;
} }
...@@ -709,10 +723,14 @@ static int ehci_urb_enqueue ( ...@@ -709,10 +723,14 @@ static int ehci_urb_enqueue (
static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
{ {
/* if we need to use IAA and it's busy, defer */ // BUG_ON(qh->qh_state != QH_STATE_LINKED);
if (qh->qh_state == QH_STATE_LINKED
&& ehci->reclaim /* failfast */
&& HC_IS_RUNNING (ehci_to_hcd(ehci)->state)) { if (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state))
end_unlink_async (ehci, NULL);
/* defer till later if busy */
else if (ehci->reclaim) {
struct ehci_qh *last; struct ehci_qh *last;
for (last = ehci->reclaim; for (last = ehci->reclaim;
...@@ -722,12 +740,8 @@ static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -722,12 +740,8 @@ static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
qh->qh_state = QH_STATE_UNLINK_WAIT; qh->qh_state = QH_STATE_UNLINK_WAIT;
last->reclaim = qh; last->reclaim = qh;
/* bypass IAA if the hc can't care */ /* start IAA cycle */
} else if (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state) && ehci->reclaim) } else
end_unlink_async (ehci, NULL);
/* something else might have unlinked the qh by now */
if (qh->qh_state == QH_STATE_LINKED)
start_unlink_async (ehci, qh); start_unlink_async (ehci, qh);
} }
...@@ -749,7 +763,19 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) ...@@ -749,7 +763,19 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
qh = (struct ehci_qh *) urb->hcpriv; qh = (struct ehci_qh *) urb->hcpriv;
if (!qh) if (!qh)
break; break;
unlink_async (ehci, qh); switch (qh->qh_state) {
case QH_STATE_LINKED:
case QH_STATE_COMPLETING:
unlink_async (ehci, qh);
break;
case QH_STATE_UNLINK:
case QH_STATE_UNLINK_WAIT:
/* already started */
break;
case QH_STATE_IDLE:
WARN_ON(1);
break;
}
break; break;
case PIPE_INTERRUPT: case PIPE_INTERRUPT:
...@@ -841,6 +867,7 @@ ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) ...@@ -841,6 +867,7 @@ ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
unlink_async (ehci, qh); unlink_async (ehci, qh);
/* FALL THROUGH */ /* FALL THROUGH */
case QH_STATE_UNLINK: /* wait for hw to finish? */ case QH_STATE_UNLINK: /* wait for hw to finish? */
case QH_STATE_UNLINK_WAIT:
idle_timeout: idle_timeout:
spin_unlock_irqrestore (&ehci->lock, flags); spin_unlock_irqrestore (&ehci->lock, flags);
schedule_timeout_uninterruptible(1); schedule_timeout_uninterruptible(1);
......
...@@ -48,7 +48,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ...@@ -48,7 +48,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
} }
ehci->command = readl (&ehci->regs->command); ehci->command = readl (&ehci->regs->command);
if (ehci->reclaim) if (ehci->reclaim)
ehci->reclaim_ready = 1; end_unlink_async (ehci, NULL);
ehci_work(ehci, NULL); ehci_work(ehci, NULL);
/* suspend any active/unsuspended ports, maybe allow wakeup */ /* suspend any active/unsuspended ports, maybe allow wakeup */
......
...@@ -303,7 +303,7 @@ static int ehci_pci_resume(struct usb_hcd *hcd) ...@@ -303,7 +303,7 @@ static int ehci_pci_resume(struct usb_hcd *hcd)
/* emptying the schedule aborts any urbs */ /* emptying the schedule aborts any urbs */
spin_lock_irq(&ehci->lock); spin_lock_irq(&ehci->lock);
if (ehci->reclaim) if (ehci->reclaim)
ehci->reclaim_ready = 1; end_unlink_async (ehci, NULL);
ehci_work(ehci, NULL); ehci_work(ehci, NULL);
spin_unlock_irq(&ehci->lock); spin_unlock_irq(&ehci->lock);
......
...@@ -967,7 +967,7 @@ static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs) ...@@ -967,7 +967,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;
timer_action_done (ehci, TIMER_IAA_WATCHDOG); iaa_watchdog_done (ehci);
// 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;
...@@ -977,7 +977,6 @@ static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs) ...@@ -977,7 +977,6 @@ static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs)
/* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */
next = qh->reclaim; next = qh->reclaim;
ehci->reclaim = next; ehci->reclaim = next;
ehci->reclaim_ready = 0;
qh->reclaim = NULL; qh->reclaim = NULL;
qh_completions (ehci, qh, regs); qh_completions (ehci, qh, regs);
...@@ -1052,11 +1051,10 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -1052,11 +1051,10 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
return; return;
} }
ehci->reclaim_ready = 0;
cmd |= CMD_IAAD; cmd |= CMD_IAAD;
writel (cmd, &ehci->regs->command); writel (cmd, &ehci->regs->command);
(void) readl (&ehci->regs->command); (void) readl (&ehci->regs->command);
timer_action (ehci, TIMER_IAA_WATCHDOG); iaa_watchdog_start (ehci);
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -58,7 +58,6 @@ struct ehci_hcd { /* one per controller */ ...@@ -58,7 +58,6 @@ 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;
unsigned reclaim_ready : 1;
unsigned scanning : 1; unsigned scanning : 1;
/* periodic schedule support */ /* periodic schedule support */
...@@ -81,6 +80,7 @@ struct ehci_hcd { /* one per controller */ ...@@ -81,6 +80,7 @@ struct ehci_hcd { /* one per controller */
struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *itd_pool; /* itd per iso urb */
struct dma_pool *sitd_pool; /* sitd per split iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */
struct timer_list iaa_watchdog;
struct timer_list watchdog; struct timer_list watchdog;
unsigned long actions; unsigned long actions;
unsigned stamp; unsigned stamp;
...@@ -114,9 +114,21 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) ...@@ -114,9 +114,21 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci)
} }
static inline void
iaa_watchdog_start (struct ehci_hcd *ehci)
{
WARN_ON(timer_pending(&ehci->iaa_watchdog));
mod_timer (&ehci->iaa_watchdog,
jiffies + msecs_to_jiffies(EHCI_IAA_MSECS));
}
static inline void iaa_watchdog_done (struct ehci_hcd *ehci)
{
del_timer (&ehci->iaa_watchdog);
}
enum ehci_timer_action { enum ehci_timer_action {
TIMER_IO_WATCHDOG, TIMER_IO_WATCHDOG,
TIMER_IAA_WATCHDOG,
TIMER_ASYNC_SHRINK, TIMER_ASYNC_SHRINK,
TIMER_ASYNC_OFF, TIMER_ASYNC_OFF,
}; };
...@@ -134,9 +146,6 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) ...@@ -134,9 +146,6 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
unsigned long t; unsigned long t;
switch (action) { switch (action) {
case TIMER_IAA_WATCHDOG:
t = EHCI_IAA_JIFFIES;
break;
case TIMER_IO_WATCHDOG: case TIMER_IO_WATCHDOG:
t = EHCI_IO_JIFFIES; t = EHCI_IO_JIFFIES;
break; break;
...@@ -153,8 +162,7 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) ...@@ -153,8 +162,7 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
// async queue SHRINK often precedes IAA. while it's ready // async queue SHRINK often precedes IAA. while it's ready
// to go OFF neither can matter, and afterwards the IO // to go OFF neither can matter, and afterwards the IO
// watchdog stops unless there's still periodic traffic. // watchdog stops unless there's still periodic traffic.
if (action != TIMER_IAA_WATCHDOG if (time_before_eq(t, ehci->watchdog.expires)
&& t > ehci->watchdog.expires
&& timer_pending (&ehci->watchdog)) && timer_pending (&ehci->watchdog))
return; return;
mod_timer (&ehci->watchdog, t); mod_timer (&ehci->watchdog, t);
......
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