On Tue, 1 Apr 2014, Dennis New wrote: > On Tue, 1 Apr 2014 09:30:01 -0400 (EDT), Alan Stern wrote: > > I don't know that much can be done to prevent the controller from > > crashing, or to recover from such a crash. Maybe resetting the > > controller would work, maybe not. > > > > But at least it should be possible to insure that a controller > > malfunction doesn't bring down the entire USB stack with it. I will > > work on a patch for this, but I'm going to be quite busy with other > > matters for the next few days. It may take some time to get the patch > > ready for you to test. Here's a patch for you to test. Let me know if it doesn't apply properly to the kernel you're using. I can't tell if it will prevent all your problems, because it's not clear exactly what's going wrong. But at least this is a start. Alan Stern Index: usb-3.14/drivers/usb/host/ohci.h =================================================================== --- usb-3.14.orig/drivers/usb/host/ohci.h +++ usb-3.14/drivers/usb/host/ohci.h @@ -408,6 +408,8 @@ struct ohci_hcd { // there are also chip quirks/bugs in init logic struct work_struct nec_work; /* Worker for NEC quirk */ + struct timer_list sf_watchdog; + unsigned sf_tick; /* Needed for ZF Micro quirk */ struct timer_list unlink_watchdog; Index: usb-3.14/drivers/usb/host/ohci-hcd.c =================================================================== --- usb-3.14.orig/drivers/usb/host/ohci-hcd.c +++ usb-3.14/drivers/usb/host/ohci-hcd.c @@ -76,6 +76,7 @@ static const char hcd_name [] = "ohci_hc #include "ohci.h" #include "pci-quirks.h" +static void enable_sf_interrupt(struct ohci_hcd *ohci); static void ohci_dump (struct ohci_hcd *ohci, int verbose); static void ohci_stop (struct usb_hcd *hcd); @@ -416,6 +417,49 @@ static int check_ed(struct ohci_hcd *ohc && !list_empty(&ed->td_list); } +/* + * Sometimes OHCI controllers fail to issue Start-of-Frame interrupts. + * There are two main reasons for this to happen: the controller crashes + * without a UE interrupt, or the controller turns off its frame counter + * (some versions do this when no ports are connected). + * + * Without SF interrupts, the ed_rm_list will never be emptied, which means + * unlinked URBs will never complete. Hence the need for this watchdog + * routine. + */ +static void sf_watchdog_func(unsigned long _ohci) +{ + unsigned long flags; + struct ohci_hcd *ohci = (struct ohci_hcd *) _ohci; + + ohci_err(ohci, "OHCI SF watchdog triggered\n"); + if (ohci->sf_tick == ohci_frame_no(ohci)) + ohci_err(ohci, "Frame counter has stopped at %u\n", + ohci->sf_tick); + spin_lock_irqsave(&ohci->lock, flags); + finish_unlinks(ohci, ohci->sf_tick + 20); + + if ((ohci->ed_rm_list || ohci->ed_to_check) && + ohci->rh_state == OHCI_RH_RUNNING) + enable_sf_interrupt(ohci); + else + ohci_writel(ohci, OHCI_INTR_SF, &ohci->regs->intrdisable); + spin_unlock_irqrestore(&ohci->lock, flags); +} + +static void enable_sf_interrupt(struct ohci_hcd *ohci) +{ + + 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); + + ohci->sf_tick = ohci_frame_no(ohci); + mod_timer(&ohci->sf_watchdog, jiffies + 1 + msecs_to_jiffies(20)); +} + /* 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 @@ -476,14 +520,7 @@ static void unlink_watchdog_func(unsigne * 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); - + enable_sf_interrupt(ohci); goto out; } } @@ -506,6 +543,9 @@ static int ohci_init (struct ohci_hcd *o int ret; struct usb_hcd *hcd = ohci_to_hcd(ohci); + setup_timer(&ohci->sf_watchdog, sf_watchdog_func, + (unsigned long) ohci); + if (distrust_firmware) ohci->flags |= OHCI_QUIRK_HUB_POWER; @@ -825,6 +865,7 @@ static irqreturn_t ohci_irq (struct usb_ usb_hc_died(hcd); } + del_timer(&ohci->sf_watchdog); ohci_dump (ohci, 1); ohci_usb_reset (ohci); } @@ -902,11 +943,13 @@ static irqreturn_t ohci_irq (struct usb_ 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->rh_state == OHCI_RH_RUNNING) + if ((ohci->ed_rm_list || ohci->ed_to_check) && + ohci->rh_state == OHCI_RH_RUNNING) + enable_sf_interrupt(ohci); + else if ((ints & OHCI_INTR_SF) != 0) { ohci_writel (ohci, OHCI_INTR_SF, ®s->intrdisable); + del_timer(&ohci->sf_watchdog); + } spin_unlock (&ohci->lock); if (ohci->rh_state == OHCI_RH_RUNNING) { @@ -935,6 +978,7 @@ static void ohci_stop (struct usb_hcd *h free_irq(hcd->irq, hcd); hcd->irq = 0; + del_timer_sync(&ohci->sf_watchdog); if (quirk_zfmicro(ohci)) del_timer(&ohci->unlink_watchdog); if (quirk_amdiso(ohci)) Index: usb-3.14/drivers/usb/host/ohci-hub.c =================================================================== --- usb-3.14.orig/drivers/usb/host/ohci-hub.c +++ usb-3.14/drivers/usb/host/ohci-hub.c @@ -87,6 +87,7 @@ __acquires(ohci->lock) msleep (8); spin_lock_irq (&ohci->lock); } + del_timer(&ohci->sf_watchdog); dl_done_list (ohci); finish_unlinks (ohci, ohci_frame_no(ohci)); @@ -221,7 +222,7 @@ skip_resume: /* interrupts might have been disabled */ ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable); if (ohci->ed_rm_list) - ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); + enable_sf_interrupt(ohci); /* Then re-enable operations */ ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control); Index: usb-3.14/drivers/usb/host/ohci-q.c =================================================================== --- usb-3.14.orig/drivers/usb/host/ohci-q.c +++ usb-3.14/drivers/usb/host/ohci-q.c @@ -493,11 +493,7 @@ static void start_ed_unlink (struct ohci ed->ed_prev = NULL; ohci->ed_rm_list = ed; - /* enable SOF interrupt */ - ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrstatus); - ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); - // flush those writes, and get latest HCCA contents - (void) ohci_readl (ohci, &ohci->regs->control); + enable_sf_interrupt(ohci); /* SF interrupt might get delayed; record the frame counter value that * indicates when the HC isn't looking at it, so concurrent unlinks @@ -505,7 +501,6 @@ static void start_ed_unlink (struct ohci * SF is triggered. */ ed->tick = ohci_frame_no(ohci) + 1; - } /*-------------------------------------------------------------------------* -- 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