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, &regs->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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to