Dennis and Matteo: I promised to send both of you a patch changing the way ohci-hcd handles hardware bugs. Well, it's finally ready for testing. There's only a limited amount I can do on my own machine, so now it's up to you guys. The patch was made against an early -rc version of 3.15, but it will apply okay to 3.14 and maybe even earlier kernels. Alan Stern Index: usb-3.15/drivers/usb/host/ohci.h =================================================================== --- usb-3.15.orig/drivers/usb/host/ohci.h +++ usb-3.15/drivers/usb/host/ohci.h @@ -47,6 +47,7 @@ struct ed { struct ed *ed_next; /* on schedule or rm_list */ struct ed *ed_prev; /* for non-interrupt EDs */ struct list_head td_list; /* "shadow list" of our TDs */ + struct list_head in_use_list; /* create --> IDLE --> OPER --> ... --> IDLE --> destroy * usually: OPER --> UNLINK --> (IDLE | OPER) --> ... @@ -66,6 +67,13 @@ struct ed { /* HC may see EDs on rm_list until next frame (frame_no == tick) */ u16 tick; + + /* Detect TDs not added to the done list */ + unsigned takeback_wdh_cnt; + struct td *pending_td; +#define OKAY_TO_TAKEBACK(ohci, ed) \ + ((int) (ohci->wdh_cnt - ed->takeback_wdh_cnt) >= 0) + } __attribute__ ((aligned(16))); #define ED_MASK ((u32)~0x0f) /* strip hw status in low addr bits */ @@ -380,7 +388,9 @@ 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; + struct list_head eds_in_use; /* all EDs with at least 1 TD */ /* * driver state @@ -392,6 +402,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 */ @@ -407,13 +419,12 @@ struct ohci_hcd { #define OHCI_QUIRK_AMD_PREFETCH 0x400 /* pre-fetch for ISO transfer */ // there are also chip quirks/bugs in init logic - struct work_struct nec_work; /* Worker for NEC quirk */ + unsigned prev_frame_no; + unsigned wdh_cnt, prev_wdh_cnt; + u32 prev_donehead; + struct timer_list io_watchdog; - /* Needed for ZF Micro quirk */ - struct timer_list unlink_watchdog; - unsigned eds_scheduled; - struct ed *ed_to_check; - unsigned zf_delay; + struct work_struct nec_work; /* Worker for NEC quirk */ struct dentry *debug_dir; struct dentry *debug_async; Index: usb-3.15/drivers/usb/host/ohci-hcd.c =================================================================== --- usb-3.15.orig/drivers/usb/host/ohci-hcd.c +++ usb-3.15/drivers/usb/host/ohci-hcd.c @@ -72,12 +72,14 @@ static const char hcd_name [] = "ohci_hcd"; #define STATECHANGE_DELAY msecs_to_jiffies(300) +#define IO_WATCHDOG_DELAY msecs_to_jiffies(250) #include "ohci.h" #include "pci-quirks.h" -static void ohci_dump (struct ohci_hcd *ohci, int verbose); -static void ohci_stop (struct usb_hcd *hcd); +static void ohci_dump(struct ohci_hcd *ohci); +static void ohci_stop(struct usb_hcd *hcd); +static void io_watchdog_func(unsigned long _ohci); #include "ohci-hub.c" #include "ohci-dbg.c" @@ -202,6 +204,16 @@ static int ohci_urb_enqueue ( usb_hcd_unlink_urb_from_ep(hcd, urb); goto fail; } + + /* Start up the IO watchdog timer, if it's not running */ + if (!timer_pending(&ohci->io_watchdog) && + list_empty(&ohci->eds_in_use)) { + ohci->prev_frame_no = ohci_frame_no(ohci); + mod_timer(&ohci->io_watchdog, + jiffies + IO_WATCHDOG_DELAY); + } + list_add(&ed->in_use_list, &ohci->eds_in_use); + if (ed->type == PIPE_ISOCHRONOUS) { u16 frame = ohci_frame_no(ohci); @@ -277,30 +289,24 @@ static int ohci_urb_dequeue(struct usb_h struct ohci_hcd *ohci = hcd_to_ohci (hcd); unsigned long flags; int rc; + urb_priv_t *urb_priv; spin_lock_irqsave (&ohci->lock, flags); rc = usb_hcd_check_unlink_urb(hcd, urb, status); - if (rc) { - ; /* Do nothing */ - } else if (ohci->rh_state == OHCI_RH_RUNNING) { - urb_priv_t *urb_priv; + if (rc == 0) { /* Unless an IRQ completed the unlink while it was being * handed to us, flag it for unlink and giveback, and force * some upcoming INTR_SF to call finish_unlinks() */ urb_priv = urb->hcpriv; - if (urb_priv) { - if (urb_priv->ed->state == ED_OPER) - start_ed_unlink (ohci, urb_priv->ed); + if (urb_priv->ed->state == ED_OPER) + start_ed_unlink(ohci, urb_priv->ed); + + if (ohci->rh_state != OHCI_RH_RUNNING) { + /* With HC dead, we can clean up right away */ + ohci_work(ohci); } - } else { - /* - * with HC dead, we won't respect hc queue pointers - * any more ... just clean up every urb's memory. - */ - if (urb->hcpriv) - finish_urb(ohci, urb, status); } spin_unlock_irqrestore (&ohci->lock, flags); return rc; @@ -332,9 +338,7 @@ rescan: if (ohci->rh_state != OHCI_RH_RUNNING) { sanitize: ed->state = ED_IDLE; - if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT) - ohci->eds_scheduled--; - finish_unlinks (ohci, 0); + ohci_work(ohci); } switch (ed->state) { @@ -342,11 +346,6 @@ sanitize: /* major IRQ delivery trouble loses INTR_SF too... */ if (limit-- == 0) { ohci_warn(ohci, "ED unlink timeout\n"); - if (quirk_zfmicro(ohci)) { - ohci_warn(ohci, "Attempting ZF TD recovery\n"); - ohci->ed_to_check = ed; - ohci->zf_delay = 2; - } goto sanitize; } spin_unlock_irqrestore (&ohci->lock, flags); @@ -406,93 +405,7 @@ ohci_shutdown (struct usb_hcd *hcd) udelay(10); ohci_writel(ohci, ohci->fminterval, &ohci->regs->fminterval); -} - -static int check_ed(struct ohci_hcd *ohci, struct ed *ed) -{ - return (hc32_to_cpu(ohci, ed->hwINFO) & ED_IN) != 0 - && (hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK) - == (hc32_to_cpu(ohci, ed->hwTailP) & TD_MASK) - && !list_empty(&ed->td_list); -} - -/* ZF Micro watchdog timer callback. The ZF Micro chipset sometimes completes - * an interrupt TD but neglects to add it to the donelist. On systems with - * this chipset, we need to periodically check the state of the queues to look - * for such "lost" TDs. - */ -static void unlink_watchdog_func(unsigned long _ohci) -{ - unsigned long flags; - unsigned max; - unsigned seen_count = 0; - unsigned i; - struct ed **seen = NULL; - struct ohci_hcd *ohci = (struct ohci_hcd *) _ohci; - - spin_lock_irqsave(&ohci->lock, flags); - max = ohci->eds_scheduled; - if (!max) - goto done; - - if (ohci->ed_to_check) - goto out; - - seen = kcalloc(max, sizeof *seen, GFP_ATOMIC); - if (!seen) - goto out; - - for (i = 0; i < NUM_INTS; i++) { - struct ed *ed = ohci->periodic[i]; - - while (ed) { - unsigned temp; - - /* scan this branch of the periodic schedule tree */ - for (temp = 0; temp < seen_count; temp++) { - if (seen[temp] == ed) { - /* we've checked it and what's after */ - ed = NULL; - break; - } - } - if (!ed) - break; - seen[seen_count++] = ed; - if (!check_ed(ohci, ed)) { - ed = ed->ed_next; - continue; - } - - /* HC's TD list is empty, but HCD sees at least one - * TD that's not been sent through the donelist. - */ - ohci->ed_to_check = ed; - ohci->zf_delay = 2; - - /* The HC may wait until the next frame to report the - * TD as done through the donelist and INTR_WDH. (We - * just *assume* it's not a multi-TD interrupt URB; - * those could defer the IRQ more than one frame, using - * DI...) Check again after the next INTR_SF. - */ - ohci_writel(ohci, OHCI_INTR_SF, - &ohci->regs->intrstatus); - ohci_writel(ohci, OHCI_INTR_SF, - &ohci->regs->intrenable); - - /* flush those writes */ - (void) ohci_readl(ohci, &ohci->regs->control); - - goto out; - } - } -out: - kfree(seen); - if (ohci->eds_scheduled) - mod_timer(&ohci->unlink_watchdog, round_jiffies(jiffies + HZ)); -done: - spin_unlock_irqrestore(&ohci->lock, flags); + ohci->rh_state = OHCI_RH_HALTED; } /*-------------------------------------------------------------------------* @@ -558,6 +471,10 @@ static int ohci_init (struct ohci_hcd *o if (ohci->hcca) return 0; + setup_timer(&ohci->io_watchdog, io_watchdog_func, + (unsigned long) ohci); + set_timer_slack(&ohci->io_watchdog, msecs_to_jiffies(20)); + ohci->hcca = dma_alloc_coherent (hcd->self.controller, sizeof *ohci->hcca, &ohci->hcca_dma, 0); if (!ohci->hcca) @@ -735,16 +652,7 @@ retry: // POTPGT delay is bits 24-31, in 2 ms units. mdelay ((val >> 23) & 0x1fe); - if (quirk_zfmicro(ohci)) { - /* Create timer to watch for bad queue state on ZF Micro */ - setup_timer(&ohci->unlink_watchdog, unlink_watchdog_func, - (unsigned long) ohci); - - ohci->eds_scheduled = 0; - ohci->ed_to_check = NULL; - } - - ohci_dump (ohci, 1); + ohci_dump(ohci); return 0; } @@ -777,6 +685,142 @@ static int ohci_start(struct usb_hcd *hc /*-------------------------------------------------------------------------*/ +/* + * Some OHCI controllers are known to lose track of completed TDs. They + * don't add the TDs to the hardware done list, which means we never see + * them as being completed. + * + * This watchdog routine checks for such problems. Without some way to + * tell when those TDs have completed, we would never take their EDs off + * the unlink list. As a result, URBs could never be dequeued and + * endpoints could never be released. + */ +static void io_watchdog_func(unsigned long _ohci) +{ + struct ohci_hcd *ohci = (struct ohci_hcd *) _ohci; + bool takeback_all_pending = false; + u32 status; + u32 head; + struct ed *ed; + struct td *td, *td_start, *td_next; + unsigned frame_no; + unsigned long flags; + + spin_lock_irqsave(&ohci->lock, flags); + + /* + * One way to lose track of completed TDs is if the controller + * never writes back the done list head. If it hasn't been + * written back since the last time this function ran and if it + * was non-empty at that time, something is badly wrong with the + * hardware. + */ + status = ohci_readl(ohci, &ohci->regs->intrstatus); + if (!(status & OHCI_INTR_WDH) && ohci->wdh_cnt == ohci->prev_wdh_cnt) { + if (ohci->prev_donehead) { + ohci_err(ohci, "HcDoneHead not written back; disabled\n"); + died: + usb_hc_died(ohci_to_hcd(ohci)); + ohci_dump(ohci); + ohci_shutdown(ohci_to_hcd(ohci)); + goto done; + } else { + /* No write back because the done list was empty */ + takeback_all_pending = true; + } + } + + /* Check every ED which might have pending TDs */ + list_for_each_entry(ed, &ohci->eds_in_use, in_use_list) { + if (ed->pending_td) { + if (takeback_all_pending || + OKAY_TO_TAKEBACK(ohci, ed)) { + unsigned tmp = hc32_to_cpu(ohci, ed->hwINFO); + + ohci_dbg(ohci, "takeback pending TD for dev %d ep 0x%x\n", + 0x007f & tmp, + (0x000f & (tmp >> 7)) + + ((tmp & ED_IN) >> 5)); + add_to_done_list(ohci, ed->pending_td); + } + } + + /* Starting from the latest pending TD, */ + td = ed->pending_td; + + /* or the last TD on the done list, */ + if (!td) { + list_for_each_entry(td_next, &ed->td_list, td_list) { + if (!td_next->next_dl_td) + break; + td = td_next; + } + } + + /* find the last TD processed by the controller. */ + head = hc32_to_cpu(ohci, ACCESS_ONCE(ed->hwHeadP)) & TD_MASK; + td_start = td; + td_next = list_prepare_entry(td, &ed->td_list, td_list); + list_for_each_entry_continue(td_next, &ed->td_list, td_list) { + if (head == (u32) td_next->td_dma) + break; + td = td_next; /* head pointer has passed this TD */ + } + if (td != td_start) { + /* + * In case a WDH cycle is in progress, we will wait + * for the next two cycles to complete before assuming + * this TD will never get on the done list. + */ + ed->takeback_wdh_cnt = ohci->wdh_cnt + 2; + ed->pending_td = td; + } + } + + ohci_work(ohci); + + if (ohci->rh_state == OHCI_RH_RUNNING) { + + /* + * Sometimes a controller just stops working. We can tell + * by checking that the frame counter has advanced since + * the last time we ran. + * + * But be careful: Some controllers violate the spec by + * stopping their frame counter when no ports are active. + */ + frame_no = ohci_frame_no(ohci); + if (frame_no == ohci->prev_frame_no) { + int active_cnt = 0; + int i; + unsigned tmp; + + for (i = 0; i < ohci->num_ports; ++i) { + tmp = roothub_portstatus(ohci, i); + /* Enabled and not suspended? */ + if ((tmp & RH_PS_PES) && !(tmp & RH_PS_PSS)) + ++active_cnt; + } + + if (active_cnt > 0) { + ohci_err(ohci, "frame counter not updating; disabled\n"); + goto died; + } + } + if (!list_empty(&ohci->eds_in_use)) { + ohci->prev_frame_no = frame_no; + ohci->prev_wdh_cnt = ohci->wdh_cnt; + ohci->prev_donehead = ohci_readl(ohci, + &ohci->regs->donehead); + mod_timer(&ohci->io_watchdog, + jiffies + IO_WATCHDOG_DELAY); + } + } + + done: + spin_unlock_irqrestore(&ohci->lock, flags); +} + /* an interrupt happens */ static irqreturn_t ohci_irq (struct usb_hcd *hcd) @@ -825,7 +869,7 @@ static irqreturn_t ohci_irq (struct usb_ usb_hc_died(hcd); } - ohci_dump (ohci, 1); + ohci_dump(ohci); ohci_usb_reset (ohci); } @@ -863,58 +907,30 @@ static irqreturn_t ohci_irq (struct usb_ usb_hcd_resume_root_hub(hcd); } - if (ints & OHCI_INTR_WDH) { - spin_lock (&ohci->lock); - dl_done_list (ohci); - spin_unlock (&ohci->lock); - } - - if (quirk_zfmicro(ohci) && (ints & OHCI_INTR_SF)) { - spin_lock(&ohci->lock); - if (ohci->ed_to_check) { - struct ed *ed = ohci->ed_to_check; - - if (check_ed(ohci, ed)) { - /* HC thinks the TD list is empty; HCD knows - * at least one TD is outstanding - */ - if (--ohci->zf_delay == 0) { - struct td *td = list_entry( - ed->td_list.next, - struct td, td_list); - ohci_warn(ohci, - "Reclaiming orphan TD %p\n", - td); - takeback_td(ohci, td); - ohci->ed_to_check = NULL; - } - } else - ohci->ed_to_check = NULL; - } - 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); - if (ohci->ed_rm_list) - finish_unlinks (ohci, ohci_frame_no(ohci)); - if ((ints & OHCI_INTR_SF) != 0 - && !ohci->ed_rm_list - && !ohci->ed_to_check + 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, ®s->intrdisable); - spin_unlock (&ohci->lock); if (ohci->rh_state == OHCI_RH_RUNNING) { ohci_writel (ohci, ints, ®s->intrstatus); + if (ints & OHCI_INTR_WDH) + ++ohci->wdh_cnt; + ohci_writel (ohci, OHCI_INTR_MIE, ®s->intrenable); // flush those writes (void) ohci_readl (ohci, &ohci->regs->control); } + spin_unlock(&ohci->lock); return IRQ_HANDLED; } @@ -925,18 +941,17 @@ static void ohci_stop (struct usb_hcd *h { struct ohci_hcd *ohci = hcd_to_ohci (hcd); - ohci_dump (ohci, 1); + ohci_dump(ohci); if (quirk_nec(ohci)) flush_work(&ohci->nec_work); + del_timer_sync(&ohci->io_watchdog); ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); ohci_usb_reset(ohci); free_irq(hcd->irq, hcd); hcd->irq = 0; - if (quirk_zfmicro(ohci)) - del_timer(&ohci->unlink_watchdog); if (quirk_amdiso(ohci)) usb_amd_dev_put(); @@ -993,7 +1008,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: */ Index: usb-3.15/drivers/usb/host/ohci-q.c =================================================================== --- usb-3.15.orig/drivers/usb/host/ohci-q.c +++ usb-3.15/drivers/usb/host/ohci-q.c @@ -187,10 +187,6 @@ static int ed_schedule (struct ohci_hcd ed->ed_prev = NULL; ed->ed_next = NULL; ed->hwNextED = 0; - if (quirk_zfmicro(ohci) - && (ed->type == PIPE_INTERRUPT) - && !(ohci->eds_scheduled++)) - mod_timer(&ohci->unlink_watchdog, round_jiffies(jiffies + HZ)); wmb (); /* we care about rm_list when setting CLE/BLE in case the HC was at @@ -869,13 +865,46 @@ static void ed_halted(struct ohci_hcd *o } } -/* 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; + + /* Did we just add the latest pending TD? */ + td2 = ed->pending_td; + if (td2 && td2->next_dl_td) + ed->pending_td = NULL; +} + +/* Get the entries on the hardware done list 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); @@ -883,7 +912,7 @@ static struct td *dl_reverse_done_list ( 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; @@ -905,19 +934,17 @@ static struct td *dl_reverse_done_list ( && (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; } /*-------------------------------------------------------------------------*/ /* 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: @@ -929,30 +956,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_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 */ - if (td->td_dma != head) { - if (ed == ohci->ed_to_check) - ohci->ed_to_check = NULL; - else - goto skip_ed; - } - } + td = list_first_entry(&ed->td_list, struct td, td_list); + + /* 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; + + /* Don't mess up anything already on the done list */ + if (td->next_dl_td) + goto skip_ed; } /* reentrancy: if we drop the schedule lock, someone might @@ -1020,18 +1044,16 @@ rescan_this: /* ED's now officially unlinked, hc doesn't see */ ed->state = ED_IDLE; - if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT) - ohci->eds_scheduled--; ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H); ed->hwNextED = 0; wmb (); ed->hwINFO &= ~cpu_to_hc32 (ohci, ED_SKIP | ED_DEQUEUE); /* but if there's work queued, reschedule */ - if (!list_empty (&ed->td_list)) { - if (ohci->rh_state == OHCI_RH_RUNNING) - ed_schedule (ohci, ed); - } + if (list_empty(&ed->td_list)) + list_del(&ed->in_use_list); + else if (ohci->rh_state == OHCI_RH_RUNNING) + ed_schedule(ohci, ed); if (modified) goto rescan_all; @@ -1082,12 +1104,7 @@ rescan_this: /*-------------------------------------------------------------------------*/ -/* - * Used to take back a TD from the host controller. This would normally be - * called from within dl_done_list, however it may be called directly if the - * HC no longer sees the TD and it has not appeared on the donelist (after - * two frames). This bug has been observed on ZF Micro systems. - */ +/* Take back a TD from the host controller */ static void takeback_td(struct ohci_hcd *ohci, struct td *td) { struct urb *urb = td->urb; @@ -1134,37 +1151,43 @@ static void takeback_td(struct ohci_hcd * * This is the main path for handing urbs back to drivers. The only other * normal path is finish_unlinks(), which unlinks URBs using ed_rm_list, - * instead of scanning the (re-reversed) donelist as this does. There's - * an abnormal path too, handling a quirk in some Compaq silicon: URBs - * with TDs that appear to be orphaned are directly reclaimed. + * 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); + struct td *td; - while (td) { - struct td *td_next = td->next_dl_td; - struct ed *ed = td->ed; - - /* - * 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; } } + +/* + * 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; +} Index: usb-3.15/drivers/usb/host/ohci-hub.c =================================================================== --- usb-3.15.orig/drivers/usb/host/ohci-hub.c +++ usb-3.15/drivers/usb/host/ohci-hub.c @@ -39,8 +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 finish_unlinks (struct ohci_hcd *, u16); +static void update_done_list(struct ohci_hcd *); +static void ohci_work(struct ohci_hcd *); #ifdef CONFIG_PM static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop) @@ -87,8 +87,8 @@ __acquires(ohci->lock) msleep (8); spin_lock_irq (&ohci->lock); } - dl_done_list (ohci); - finish_unlinks (ohci, ohci_frame_no(ohci)); + update_done_list(ohci); + ohci_work(ohci); /* maybe resume can wake root hub */ if (ohci_to_hcd(ohci)->self.root_hub->do_remote_wakeup || autostop) { @@ -291,6 +291,9 @@ static int ohci_bus_suspend (struct usb_ else rc = ohci_rh_suspend (ohci, 0); spin_unlock_irq (&ohci->lock); + + if (rc == 0) + del_timer_sync(&ohci->io_watchdog); return rc; } Index: usb-3.15/drivers/usb/host/ohci-dbg.c =================================================================== --- usb-3.15.orig/drivers/usb/host/ohci-dbg.c +++ usb-3.15/drivers/usb/host/ohci-dbg.c @@ -236,7 +236,7 @@ ohci_dump_roothub ( } } -static void ohci_dump (struct ohci_hcd *controller, int verbose) +static void ohci_dump(struct ohci_hcd *controller) { ohci_dbg (controller, "OHCI controller state\n"); @@ -468,6 +468,7 @@ static ssize_t fill_async_buffer(struct unsigned long flags; ohci = buf->ohci; + buf->count = PAGE_SIZE; /* display control and bulk lists together, for simplicity */ spin_lock_irqsave (&ohci->lock, flags); Index: usb-3.15/drivers/usb/host/ohci-mem.c =================================================================== --- usb-3.15.orig/drivers/usb/host/ohci-mem.c +++ usb-3.15/drivers/usb/host/ohci-mem.c @@ -28,6 +28,7 @@ static void ohci_hcd_init (struct ohci_h ohci->next_statechange = jiffies; spin_lock_init (&ohci->lock); INIT_LIST_HEAD (&ohci->pending); + INIT_LIST_HEAD(&ohci->eds_in_use); } /*-------------------------------------------------------------------------*/ -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html