[PATCH] EHCI interrupt transactions failing during CPU frequency changes

<[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <DFEF91B22ED07447AB6AA4B237F913F92588B1@ausx3mpc125.aus.amer.dell.com>
Many interrupt transactions to low/full speed devices are getting
QTD_STS_MMF from the EHCI controller because the split transaction isn't
completed in time.  This happens when the EHCI controller tries to read
main memory during a CPU frequency change, and it can't get the data
back in time, presumably due to the processor not being able to complete
a cache snoop in a timely manner when changing frequency.

This results in ehci_hcd returning -EPROTO to drivers like hid-core, and
results in lost keystrokes, as well as messages like "usb 3-3.1: reset
low speed USB device using ehci_hcd and address 7".  This could
presumably also affect non-HID devices.

This patch turns off periodic list processing in the EHCI controller for
the duration of processor frequency changes.

Signed-off-by: Stuart Hayes <[email protected]>

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV

_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
ehci_cpufreq_2.patch (application/octet-stream, 4.2 KB)
diff -purN --exclude=tags linux-2.6.20orig/drivers/usb/host/ehci.h linux-2.6.20new/drivers/usb/host/ehci.h
--- linux-2.6.20orig/drivers/usb/host/ehci.h	2007-02-04 12:44:54.000000000 -0600
+++ linux-2.6.20new/drivers/usb/host/ehci.h	2007-02-20 11:24:31.000000000 -0600
@@ -55,6 +55,11 @@ struct ehci_hcd {			/* one per controlle
 	__u32			hcs_params;	/* cached register copy */
 	spinlock_t		lock;
 
+#ifdef CONFIG_CPU_FREQ
+	struct notifier_block	cpufreq_transition;
+	int			cpufreq_changing;
+#endif
+
 	/* async schedule support */
 	struct ehci_qh		*async;
 	struct ehci_qh		*reclaim;
diff -purN --exclude=tags linux-2.6.20orig/drivers/usb/host/ehci-hcd.c linux-2.6.20new/drivers/usb/host/ehci-hcd.c
--- linux-2.6.20orig/drivers/usb/host/ehci-hcd.c	2007-02-04 12:44:54.000000000 -0600
+++ linux-2.6.20new/drivers/usb/host/ehci-hcd.c	2007-02-20 11:24:31.000000000 -0600
@@ -268,6 +268,62 @@ static void ehci_work(struct ehci_hcd *e
 
 /*-------------------------------------------------------------------------*/
 
+#ifdef CONFIG_CPU_FREQ
+
+#include <linux/cpufreq.h>
+
+static void ehci_cpufreq_pause (struct ehci_hcd *ehci)
+{
+	unsigned long	flags;
+
+	spin_lock_irqsave(&ehci->lock, flags);
+	if (!ehci->cpufreq_changing++ &&
+	    (ehci->periodic_sched) &&
+	    (ehci_to_hcd(ehci)->state == HC_STATE_RUNNING)) {
+		int status;
+		disable_periodic(ehci);
+		/* make sure it's off before proceeding with freq change */
+		status = handshake (&ehci->regs->status, STS_PSS, 0, 9 * 125);
+	}
+	spin_unlock_irqrestore(&ehci->lock, flags);
+}
+
+static void ehci_cpufreq_unpause (struct ehci_hcd *ehci)
+{
+	unsigned long	flags;
+
+	spin_lock_irqsave(&ehci->lock, flags);
+	if (!--ehci->cpufreq_changing &&
+	    (ehci->periodic_sched) &&
+	    (ehci_to_hcd(ehci)->state == HC_STATE_RUNNING)) {
+		enable_periodic(ehci);
+	}
+	spin_unlock_irqrestore(&ehci->lock, flags);
+}
+
+#define __type_entry(ptr,type,member) ((type *)((char *)(ptr)-offsetof(type,member)))
+
+static int ehci_cpufreq_notifier(struct notifier_block *nb, unsigned long val,
+				 void *data)
+{
+	struct ehci_hcd *ehci = __type_entry(nb, struct ehci_hcd, 
+					     cpufreq_transition);
+
+	switch (val) {
+	case CPUFREQ_PRECHANGE:
+		ehci_cpufreq_pause(ehci);
+		break;
+	case CPUFREQ_POSTCHANGE:
+		ehci_cpufreq_unpause(ehci);
+		break;
+	}
+	return 0;
+}
+
+#endif
+
+/*-------------------------------------------------------------------------*/
+
 static void ehci_watchdog (unsigned long param)
 {
 	struct ehci_hcd		*ehci = (struct ehci_hcd *) param;
@@ -307,7 +363,10 @@ ehci_shutdown (struct usb_hcd *hcd)
 
 	ehci = hcd_to_ehci (hcd);
 	(void) ehci_halt (ehci);
-
+#ifdef CONFIG_CPU_FREQ
+	cpufreq_unregister_notifier(&ehci->cpufreq_transition,
+				    CPUFREQ_TRANSITION_NOTIFIER);
+#endif
 	/* make BIOS/etc use companion controller during reboot */
 	writel (0, &ehci->regs->configured_flag);
 }
@@ -485,6 +544,12 @@ static int ehci_init(struct usb_hcd *hcd
 	}
 	ehci->command = temp;
 
+#ifdef CONFIG_CPU_FREQ
+	ehci->cpufreq_transition.notifier_call = ehci_cpufreq_notifier;
+	cpufreq_register_notifier(&ehci->cpufreq_transition,
+				  CPUFREQ_TRANSITION_NOTIFIER);
+#endif
+
 	return 0;
 }
 
diff -purN --exclude=tags linux-2.6.20orig/drivers/usb/host/ehci-sched.c linux-2.6.20new/drivers/usb/host/ehci-sched.c
--- linux-2.6.20orig/drivers/usb/host/ehci-sched.c	2007-02-04 12:44:54.000000000 -0600
+++ linux-2.6.20new/drivers/usb/host/ehci-sched.c	2007-02-20 11:24:31.000000000 -0600
@@ -430,6 +430,15 @@ static int enable_periodic (struct ehci_
 	u32	cmd;
 	int	status;
 
+#ifdef CONFIG_CPU_FREQ
+	/* Only enable if CPUs aren't changing frequency, else
+	 * split transactions may generate QTD_STS_MMF because
+	 * of cache snoop delays when EHCI controller reads mem!
+	 */
+	if (ehci->cpufreq_changing)
+		return 0;
+#endif
+
 	/* did clearing PSE did take effect yet?
 	 * takes effect only at frame boundaries...
 	 */
@@ -455,6 +464,13 @@ static int disable_periodic (struct ehci
 	u32	cmd;
 	int	status;
 
+	/* don't disable if it already is--both CPU freq change and
+	 * an empty periodic schedule can turn it off
+	 */
+	cmd = readl (&ehci->regs->command);
+	if (!(cmd & CMD_PSE))
+		return 0;
+
 	/* did setting PSE not take effect yet?
 	 * takes effect only at frame boundaries...
 	 */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.