summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/host/ohci-hcd.c10
-rw-r--r--drivers/usb/host/ohci-hub.c6
-rw-r--r--drivers/usb/host/ohci-q.c28
-rw-r--r--drivers/usb/host/ohci.h2
4 files changed, 34 insertions, 12 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 3112799bba7f..ad588538e2e7 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -316,7 +316,7 @@ static int ohci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
if (ohci->rh_state != OHCI_RH_RUNNING) {
/* With HC dead, we can clean up right away */
- finish_unlinks(ohci, 0);
+ ohci_work(ohci);
}
}
spin_unlock_irqrestore (&ohci->lock, flags);
@@ -349,7 +349,7 @@ rescan:
if (ohci->rh_state != OHCI_RH_RUNNING) {
sanitize:
ed->state = ED_IDLE;
- finish_unlinks (ohci, 0);
+ ohci_work(ohci);
}
switch (ed->state) {
@@ -789,9 +789,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
/* handle any pending URB/ED unlinks, leaving INTR_SF enabled
* when there's still unlinking to be done (next frame).
*/
- process_done_list(ohci);
- if (ohci->ed_rm_list)
- finish_unlinks (ohci, ohci_frame_no(ohci));
+ ohci_work(ohci);
if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list
&& ohci->rh_state == OHCI_RH_RUNNING)
ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable);
@@ -879,7 +877,7 @@ int ohci_restart(struct ohci_hcd *ohci)
if (!urb->unlinked)
urb->unlinked = -ESHUTDOWN;
}
- finish_unlinks (ohci, 0);
+ ohci_work(ohci);
spin_unlock_irq(&ohci->lock);
/* paranoia, in case that didn't work: */
diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c
index dccb90edd66e..8991692bcfb8 100644
--- a/drivers/usb/host/ohci-hub.c
+++ b/drivers/usb/host/ohci-hub.c
@@ -40,8 +40,7 @@
(OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
static void update_done_list(struct ohci_hcd *);
-static void process_done_list(struct ohci_hcd *);
-static void finish_unlinks (struct ohci_hcd *, u16);
+static void ohci_work(struct ohci_hcd *);
#ifdef CONFIG_PM
static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop)
@@ -89,8 +88,7 @@ __acquires(ohci->lock)
spin_lock_irq (&ohci->lock);
}
update_done_list(ohci);
- process_done_list(ohci);
- finish_unlinks (ohci, ohci_frame_no(ohci));
+ ohci_work(ohci);
/*
* Some controllers don't handle "global" suspend properly if
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
index f36b2fa0ee2f..1974ddc68e45 100644
--- a/drivers/usb/host/ohci-q.c
+++ b/drivers/usb/host/ohci-q.c
@@ -964,9 +964,9 @@ static void update_done_list(struct ohci_hcd *ohci)
/*-------------------------------------------------------------------------*/
/* there are some urbs/eds to unlink; called in_irq(), with HCD locked */
-static void
-finish_unlinks (struct ohci_hcd *ohci, u16 tick)
+static void finish_unlinks(struct ohci_hcd *ohci)
{
+ unsigned tick = ohci_frame_no(ohci);
struct ed *ed, **last;
rescan_all:
@@ -1202,3 +1202,27 @@ static void process_done_list(struct ohci_hcd *ohci)
takeback_td(ohci, td);
}
}
+
+/*
+ * TD takeback and URB giveback must be single-threaded.
+ * This routine takes care of it all.
+ */
+static void ohci_work(struct ohci_hcd *ohci)
+{
+ if (ohci->working) {
+ ohci->restart_work = 1;
+ return;
+ }
+ ohci->working = 1;
+
+ restart:
+ process_done_list(ohci);
+ if (ohci->ed_rm_list)
+ finish_unlinks(ohci);
+
+ if (ohci->restart_work) {
+ ohci->restart_work = 0;
+ goto restart;
+ }
+ ohci->working = 0;
+}
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index a8259bc6fd8b..ef348c2e1e4b 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -393,6 +393,8 @@ struct ohci_hcd {
unsigned long next_statechange; /* suspend/resume */
u32 fminterval; /* saved register */
unsigned autostop:1; /* rh auto stopping/stopped */
+ unsigned working:1;
+ unsigned restart_work:1;
unsigned long flags; /* for HC bugs */
#define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */