diff options
-rw-r--r-- | drivers/usb/host/ohci-hcd.c | 12 | ||||
-rw-r--r-- | drivers/usb/host/ohci-hub.c | 6 | ||||
-rw-r--r-- | drivers/usb/host/ohci-q.c | 109 | ||||
-rw-r--r-- | drivers/usb/host/ohci.h | 1 |
4 files changed, 70 insertions, 58 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 52829276a44e..3112799bba7f 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -780,24 +780,21 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) usb_hcd_resume_root_hub(hcd); } - if (ints & OHCI_INTR_WDH) { - spin_lock (&ohci->lock); - dl_done_list (ohci); - spin_unlock (&ohci->lock); - } + spin_lock(&ohci->lock); + if (ints & OHCI_INTR_WDH) + update_done_list(ohci); /* could track INTR_SO to reduce available PCI/... bandwidth */ /* handle any pending URB/ED unlinks, leaving INTR_SF enabled * when there's still unlinking to be done (next frame). */ - spin_lock (&ohci->lock); + process_done_list(ohci); if (ohci->ed_rm_list) finish_unlinks (ohci, ohci_frame_no(ohci)); if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list && ohci->rh_state == OHCI_RH_RUNNING) ohci_writel (ohci, OHCI_INTR_SF, ®s->intrdisable); - spin_unlock (&ohci->lock); if (ohci->rh_state == OHCI_RH_RUNNING) { ohci_writel (ohci, ints, ®s->intrstatus); @@ -805,6 +802,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) // flush those writes (void) ohci_readl (ohci, &ohci->regs->control); } + spin_unlock(&ohci->lock); return IRQ_HANDLED; } diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index b4940de1eba3..dccb90edd66e 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -39,7 +39,8 @@ #define OHCI_SCHED_ENABLES \ (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) -static void dl_done_list (struct ohci_hcd *); +static void update_done_list(struct ohci_hcd *); +static void process_done_list(struct ohci_hcd *); static void finish_unlinks (struct ohci_hcd *, u16); #ifdef CONFIG_PM @@ -87,7 +88,8 @@ __acquires(ohci->lock) msleep (8); spin_lock_irq (&ohci->lock); } - dl_done_list (ohci); + update_done_list(ohci); + process_done_list(ohci); finish_unlinks (ohci, ohci_frame_no(ohci)); /* diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c index a9f4f04c3fad..f36b2fa0ee2f 100644 --- a/drivers/usb/host/ohci-q.c +++ b/drivers/usb/host/ohci-q.c @@ -892,13 +892,41 @@ static void ed_halted(struct ohci_hcd *ohci, struct td *td, int cc) } } -/* replies to the request have to be on a FIFO basis so - * we unreverse the hc-reversed done-list - */ -static struct td *dl_reverse_done_list (struct ohci_hcd *ohci) +/* Add a TD to the done list */ +static void add_to_done_list(struct ohci_hcd *ohci, struct td *td) +{ + struct td *td2, *td_prev; + struct ed *ed; + + if (td->next_dl_td) + return; /* Already on the list */ + + /* Add all the TDs going back until we reach one that's on the list */ + ed = td->ed; + td2 = td_prev = td; + list_for_each_entry_continue_reverse(td2, &ed->td_list, td_list) { + if (td2->next_dl_td) + break; + td2->next_dl_td = td_prev; + td_prev = td2; + } + + if (ohci->dl_end) + ohci->dl_end->next_dl_td = td_prev; + else + ohci->dl_start = td_prev; + + /* + * Make td->next_dl_td point to td itself, to mark the fact + * that td is on the done list. + */ + ohci->dl_end = td->next_dl_td = td; +} + +/* Get the entries on the hardware done queue and put them on our list */ +static void update_done_list(struct ohci_hcd *ohci) { u32 td_dma; - struct td *td_rev = NULL; struct td *td = NULL; td_dma = hc32_to_cpup (ohci, &ohci->hcca->done_head); @@ -906,7 +934,7 @@ static struct td *dl_reverse_done_list (struct ohci_hcd *ohci) wmb(); /* get TD from hc's singly linked list, and - * prepend to ours. ed->td_list changes later. + * add to ours. ed->td_list changes later. */ while (td_dma) { int cc; @@ -928,11 +956,9 @@ static struct td *dl_reverse_done_list (struct ohci_hcd *ohci) && (td->ed->hwHeadP & cpu_to_hc32 (ohci, ED_H))) ed_halted(ohci, td, cc); - td->next_dl_td = td_rev; - td_rev = td; td_dma = hc32_to_cpup (ohci, &td->hwNextTD); + add_to_done_list(ohci, td); } - return td_rev; } /*-------------------------------------------------------------------------*/ @@ -956,26 +982,27 @@ rescan_all: /* only take off EDs that the HC isn't using, accounting for * frame counter wraps and EDs with partially retired TDs */ - if (likely(ohci->rh_state == OHCI_RH_RUNNING)) { - if (tick_before (tick, ed->tick)) { + if (likely(ohci->rh_state == OHCI_RH_RUNNING) && + tick_before(tick, ed->tick)) { skip_ed: - last = &ed->ed_next; - continue; - } + last = &ed->ed_next; + continue; + } + if (!list_empty(&ed->td_list)) { + struct td *td; + u32 head; - if (!list_empty (&ed->td_list)) { - struct td *td; - u32 head; + td = list_first_entry(&ed->td_list, struct td, td_list); - td = list_entry (ed->td_list.next, struct td, - td_list); - head = hc32_to_cpu (ohci, ed->hwHeadP) & - TD_MASK; + /* INTR_WDH may need to clean up first */ + head = hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK; + if (td->td_dma != head && + ohci->rh_state == OHCI_RH_RUNNING) + goto skip_ed; - /* INTR_WDH may need to clean up first */ - if (td->td_dma != head) - goto skip_ed; - } + /* Don't mess up anything already on the done list */ + if (td->next_dl_td) + goto skip_ed; } /* ED's now officially unlinked, hc doesn't see */ @@ -1161,33 +1188,17 @@ static void takeback_td(struct ohci_hcd *ohci, struct td *td) * normal path is finish_unlinks(), which unlinks URBs using ed_rm_list, * instead of scanning the (re-reversed) donelist as this does. */ -static void -dl_done_list (struct ohci_hcd *ohci) +static void process_done_list(struct ohci_hcd *ohci) { - struct td *td = dl_reverse_done_list (ohci); - - while (td) { - struct td *td_next = td->next_dl_td; - struct ed *ed = td->ed; + struct td *td; - /* - * Some OHCI controllers (NVIDIA for sure, maybe others) - * occasionally forget to add TDs to the done queue. Since - * TDs for a given endpoint are always processed in order, - * if we find a TD on the donelist then all of its - * predecessors must be finished as well. - */ - for (;;) { - struct td *td2; - - td2 = list_first_entry(&ed->td_list, struct td, - td_list); - if (td2 == td) - break; - takeback_td(ohci, td2); - } + while (ohci->dl_start) { + td = ohci->dl_start; + if (td == ohci->dl_end) + ohci->dl_start = ohci->dl_end = NULL; + else + ohci->dl_start = td->next_dl_td; takeback_td(ohci, td); - td = td_next; } } diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index 392932dd6318..a8259bc6fd8b 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -380,6 +380,7 @@ struct ohci_hcd { struct dma_pool *td_cache; struct dma_pool *ed_cache; struct td *td_hash [TD_HASH_SIZE]; + struct td *dl_start, *dl_end; /* the done list */ struct list_head pending; /* |