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 [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html