Skip to content

Commit

Permalink
PCI PM: Make it possible to force using INTx for PCIe PME signaling
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Rafael J. Wysocki authored and Jesse Barnes committed Feb 23, 2010
1 parent c7f4865 commit c39fae1
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Documentation/kernel-parameters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 13 additions & 1 deletion drivers/pci/pcie/pme/pcie_pme.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions drivers/pci/pcie/portdrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_ */
12 changes: 10 additions & 2 deletions drivers/pci/pcie/portdrv_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 27 additions & 0 deletions drivers/pci/pcie/portdrv_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit c39fae1

Please sign in to comment.