PCI PM: Make it possible to force using INTx for PCIe PME signaling
authorRafael J. Wysocki <rjw@sisk.pl>
Wed, 17 Feb 2010 22:40:07 +0000 (23:40 +0100)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Tue, 23 Feb 2010 00:20:39 +0000 (16:20 -0800)
Apparently, some machines may have problems with PCI run-time power
management if MSIs are used for the native PCIe PME signaling.  In
particular, on the MSI Wind U-100 PCIe PME interrupts are not
generated by a PCIe root port after a resume from suspend to RAM, if
the system wake-up was triggered by a PME from the device attached to
this port.  [It doesn't help to free the interrupt on suspend and
request it back on resume, even if that is done along with disabling
the MSI and re-enabling it, respectively.]  However, if INTx
interrupts are used for this purpose on the same machine, everything
works just fine.

For this reason, add a kernel command line switch allowing one to
request that MSIs be not used for the native PCIe PME signaling,
introduce a DMI table allowing us to blacklist machines that need
this switch to be set by default and put the MSI Wind U-100 into this
table.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Documentation/kernel-parameters.txt
drivers/pci/pcie/pme/pcie_pme.c
drivers/pci/pcie/portdrv.h
drivers/pci/pcie/portdrv_core.c
drivers/pci/pcie/portdrv_pci.c

index 747a2c8..516225a 100644 (file)
@@ -2003,6 +2003,8 @@ and is between 256 and 4096 characters. It is defined in the file
                force   Use native PCIe PME signaling even if the BIOS refuses
                        to allow the kernel to control the relevant PCIe config
                        registers.
+               nomsi   Do not use MSI for native PCIe PME signaling (this makes
+                       all PCIe root ports use INTx for everything).
 
        pcmv=           [HW,PCMCIA] BadgePAD 4
 
index b5f96fb..51a6906 100644 (file)
@@ -53,12 +53,22 @@ static bool pcie_pme_disabled;
  */
 static bool pcie_pme_force_enable;
 
+/*
+ * If this switch is set, MSI will not be used for PCIe PME signaling.  This
+ * causes the PCIe port driver to use INTx interrupts only, but it turns out
+ * that using MSI for PCIe PME signaling doesn't play well with PCIe PME-based
+ * wake-up from system sleep states.
+ */
+bool pcie_pme_msi_disabled;
+
 static int __init pcie_pme_setup(char *str)
 {
        if (!strcmp(str, "off"))
                pcie_pme_disabled = true;
        else if (!strcmp(str, "force"))
                pcie_pme_force_enable = true;
+       else if (!strcmp(str, "nomsi"))
+               pcie_pme_msi_disabled = true;
        return 1;
 }
 __setup("pcie_pme=", pcie_pme_setup);
@@ -73,7 +83,9 @@ __setup("pcie_pme=", pcie_pme_setup);
  */
 static bool pcie_pme_platform_setup(struct pcie_device *srv)
 {
-       return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable;
+       if (!pcie_pme_platform_notify(srv))
+               return true;
+       return pcie_pme_force_enable;
 }
 
 struct pcie_pme_service_data {
index aaeb9d2..813a5c3 100644 (file)
@@ -30,4 +30,21 @@ extern void pcie_port_device_remove(struct pci_dev *dev);
 extern int __must_check pcie_port_bus_register(void);
 extern void pcie_port_bus_unregister(void);
 
+#ifdef CONFIG_PCIE_PME
+extern bool pcie_pme_msi_disabled;
+
+static inline void pcie_pme_disable_msi(void)
+{
+       pcie_pme_msi_disabled = true;
+}
+
+static inline bool pcie_pme_no_msi(void)
+{
+       return pcie_pme_msi_disabled;
+}
+#else /* !CONFIG_PCIE_PME */
+static inline void pcie_pme_disable_msi(void) {}
+static inline bool pcie_pme_no_msi(void) { return false; }
+#endif /* !CONFIG_PCIE_PME */
+
 #endif /* _PORTDRV_H_ */
index b174188..0d34ff4 100644 (file)
@@ -186,16 +186,24 @@ static int pcie_port_enable_msix(struct pci_dev *dev, int *vectors, int mask)
  */
 static int init_service_irqs(struct pci_dev *dev, int *irqs, int mask)
 {
-       int i, irq;
+       int i, irq = -1;
+
+       /* We have to use INTx if MSI cannot be used for PCIe PME. */
+       if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi()) {
+               if (dev->pin)
+                       irq = dev->irq;
+               goto no_msi;
+       }
 
        /* Try to use MSI-X if supported */
        if (!pcie_port_enable_msix(dev, irqs, mask))
                return 0;
+
        /* We're not going to use MSI-X, so try MSI and fall back to INTx */
-       irq = -1;
        if (!pci_enable_msi(dev) || dev->pin)
                irq = dev->irq;
 
+ no_msi:
        for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
                irqs[i] = irq;
        irqs[PCIE_PORT_SERVICE_VC_SHIFT] = -1;
index 13c8972..127e8f1 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/slab.h>
 #include <linux/pcieport_if.h>
 #include <linux/aer.h>
+#include <linux/dmi.h>
 
 #include "portdrv.h"
 #include "aer/aerdrv.h"
@@ -273,10 +274,36 @@ static struct pci_driver pcie_portdriver = {
        .driver.pm      = PCIE_PORTDRV_PM_OPS,
 };
 
+static int __init dmi_pcie_pme_disable_msi(const struct dmi_system_id *d)
+{
+       pr_notice("%s detected: will not use MSI for PCIe PME signaling\n",
+                       d->ident);
+       pcie_pme_disable_msi();
+       return 0;
+}
+
+static struct dmi_system_id __initdata pcie_portdrv_dmi_table[] = {
+       /*
+        * Boxes that should not use MSI for PCIe PME signaling.
+        */
+       {
+        .callback = dmi_pcie_pme_disable_msi,
+        .ident = "MSI Wind U-100",
+        .matches = {
+                    DMI_MATCH(DMI_SYS_VENDOR,
+                               "MICRO-STAR INTERNATIONAL CO., LTD"),
+                    DMI_MATCH(DMI_PRODUCT_NAME, "U-100"),
+                    },
+        },
+        {}
+};
+
 static int __init pcie_portdrv_init(void)
 {
        int retval;
 
+       dmi_check_system(pcie_portdrv_dmi_table);
+
        retval = pcie_port_bus_register();
        if (retval) {
                printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval);