diff options
Diffstat (limited to 'drivers/usb/host/ohci-hcd.c')
-rw-r--r-- | drivers/usb/host/ohci-hcd.c | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index ad588538e2e7..aba8f19eae4d 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/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); static void ohci_stop(struct usb_hcd *hcd); +static void io_watchdog_func(unsigned long _ohci); #include "ohci-hub.c" #include "ohci-dbg.c" @@ -225,6 +227,14 @@ static int ohci_urb_enqueue ( usb_hcd_unlink_urb_from_ep(hcd, urb); goto fail; } + + /* Start up the I/O watchdog timer, if it's not running */ + if (!timer_pending(&ohci->io_watchdog) && + list_empty(&ohci->eds_in_use)) + 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); @@ -416,6 +426,7 @@ ohci_shutdown (struct usb_hcd *hcd) udelay(10); ohci_writel(ohci, ohci->fminterval, &ohci->regs->fminterval); + ohci->rh_state = OHCI_RH_HALTED; } /*-------------------------------------------------------------------------* @@ -484,6 +495,10 @@ static int ohci_init (struct ohci_hcd *ohci) 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, GFP_KERNEL); if (!ohci->hcca) @@ -694,6 +709,112 @@ static int ohci_start(struct usb_hcd *hcd) /*-------------------------------------------------------------------------*/ +/* + * Some OHCI controllers are known to lose track of completed TDs. They + * don't add the TDs to the hardware done queue, 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 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 queue 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"); + 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 queue 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 queue. + */ + ed->takeback_wdh_cnt = ohci->wdh_cnt + 2; + ed->pending_td = td; + } + } + + ohci_work(ohci); + + if (ohci->rh_state == OHCI_RH_RUNNING) { + if (!list_empty(&ohci->eds_in_use)) { + 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) @@ -796,6 +917,9 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) 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); @@ -815,6 +939,7 @@ static void ohci_stop (struct usb_hcd *hcd) 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); |