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 747a2c8f62f3ab5c8134e480bda21ecf87e624a9..516225a864f942ffcc10e1672657812bda74f3b1 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 b5f96fb3cd8383dc6e9f998b9139d516837f5767..51a69061b1202a28af0cdb7d95abd829885299bf 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 aaeb9d21cba5eab65b02aa1696a177e29def6d63..813a5c3427b69261d0d54d5c531ffc9f9973ce64 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 b174188ac1212e4f128190c9a8ee78c3b1ef9962..0d34ff4153998ca823ebf7b928b428e165691f3a 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 13c8972886e6e788cf47916507376e5138c1c195..127e8f169d9c2409baa283ca48582e56cbde31fc 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);