Re: usb audio breaks ohci-pci

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux