[PATCH] EHCI interrupt transactions failing during CPU frequency changes
| 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...
*/