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

[PATCH] ehci, remove potential hangs

These don't affect the hang I'm hunting for, but paranoia
argues the patch is better integrated than not:

- prevent resubmit-from-completion looping in_irq if the
   transfers complete really fast.  (likely never seen, but...)

- grab ehci lock before reading irq status; should be harmless
   except in one host error cleanup-after-death
parent 8e4f2cd3
...@@ -637,9 +637,13 @@ static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs) ...@@ -637,9 +637,13 @@ static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs)
static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs) static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)
{ {
struct ehci_hcd *ehci = hcd_to_ehci (hcd); struct ehci_hcd *ehci = hcd_to_ehci (hcd);
u32 status = readl (&ehci->regs->status); u32 status;
int bh; int bh;
spin_lock (&ehci->lock);
status = readl (&ehci->regs->status);
/* e.g. cardbus physical eject */ /* e.g. cardbus physical eject */
if (status == ~(u32) 0) { if (status == ~(u32) 0) {
ehci_dbg (ehci, "device removed\n"); ehci_dbg (ehci, "device removed\n");
...@@ -648,9 +652,7 @@ static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs) ...@@ -648,9 +652,7 @@ static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)
status &= INTR_MASK; status &= INTR_MASK;
if (!status) /* irq sharing? */ if (!status) /* irq sharing? */
return; goto done;
spin_lock (&ehci->lock);
/* clear (just) interrupts */ /* clear (just) interrupts */
writel (status, &ehci->regs->status); writel (status, &ehci->regs->status);
...@@ -693,6 +695,7 @@ static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs) ...@@ -693,6 +695,7 @@ static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs)
if (bh) if (bh)
ehci_work (ehci, regs); ehci_work (ehci, regs);
done:
spin_unlock (&ehci->lock); spin_unlock (&ehci->lock);
} }
......
...@@ -222,7 +222,7 @@ ehci_urb_done (struct ehci_hcd *ehci, struct urb *urb, struct pt_regs *regs) ...@@ -222,7 +222,7 @@ ehci_urb_done (struct ehci_hcd *ehci, struct urb *urb, struct pt_regs *regs)
static unsigned static unsigned
qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs) qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs)
{ {
struct ehci_qtd *last = 0; struct ehci_qtd *last = 0, *end = qh->dummy;
struct list_head *entry, *tmp; struct list_head *entry, *tmp;
int stopped = 0; int stopped = 0;
unsigned count = 0; unsigned count = 0;
...@@ -253,6 +253,10 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs) ...@@ -253,6 +253,10 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs)
last = 0; last = 0;
} }
/* ignore urbs submitted during completions we reported */
if (qtd == end)
break;
/* hardware copies qtd out of qh overlay */ /* hardware copies qtd out of qh overlay */
rmb (); rmb ();
token = le32_to_cpu (qtd->hw_token); token = le32_to_cpu (qtd->hw_token);
...@@ -967,25 +971,28 @@ static void ...@@ -967,25 +971,28 @@ 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;
unsigned count;
if (!++(ehci->stamp))
ehci->stamp++;
rescan: rescan:
qh = ehci->async->qh_next.qh; qh = ehci->async->qh_next.qh;
count = 0;
if (likely (qh != 0)) { if (likely (qh != 0)) {
do { do {
/* clean any finished work for this qh */ /* clean any finished work for this qh */
if (!list_empty (&qh->qtd_list)) { if (!list_empty (&qh->qtd_list)
&& qh->stamp != ehci->stamp) {
int temp; int temp;
/* unlinks could happen here; completion /* unlinks could happen here; completion
* reporting drops the lock. * reporting drops the lock. rescan using
* the latest schedule, but don't rescan
* qhs we already finished (no looping).
*/ */
qh = qh_get (qh); qh = qh_get (qh);
qh->stamp = ehci->stamp;
temp = qh_completions (ehci, qh, regs); temp = qh_completions (ehci, qh, regs);
qh_put (ehci, qh); qh_put (ehci, qh);
if (temp != 0) { if (temp != 0) {
count += temp;
goto rescan; goto rescan;
} }
} }
......
...@@ -81,6 +81,7 @@ struct ehci_hcd { /* one per controller */ ...@@ -81,6 +81,7 @@ struct ehci_hcd { /* one per controller */
struct pci_pool *sitd_pool; /* sitd per split iso urb */ struct pci_pool *sitd_pool; /* sitd per split iso urb */
struct timer_list watchdog; struct timer_list watchdog;
unsigned stamp;
#ifdef EHCI_STATS #ifdef EHCI_STATS
struct ehci_stats stats; struct ehci_stats stats;
...@@ -306,6 +307,7 @@ struct ehci_qh { ...@@ -306,6 +307,7 @@ struct ehci_qh {
struct ehci_qtd *dummy; struct ehci_qtd *dummy;
atomic_t refcount; atomic_t refcount;
unsigned stamp;
u8 qh_state; u8 qh_state;
#define QH_STATE_LINKED 1 /* HC sees this */ #define QH_STATE_LINKED 1 /* HC sees this */
......
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