From a39bd851dccfdcb89db3d9a6b03283aaf15f310c Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 9 Mar 2018 11:06:54 -0600 Subject: PCI/PM: Clear PCIe PME Status bit in core, not PCIe port driver fe31e69740ed ("PCI/PCIe: Clear Root PME Status bits early during system resume") added a .resume_noirq() callback to the PCIe port driver to clear the PME Status bit during resume to work around a BIOS issue. The BIOS evidently enabled PME interrupts for ACPI-based runtime wakeups but did not clear the PME Status bit during resume, which meant PMEs after resume did not trigger interrupts because PME Status did not transition from cleared to set. The fix was in the PCIe port driver, so it worked when CONFIG_PCIEPORTBUS was set. But I think we *always* want the fix because the platform may use PME interrupts even if Linux is built without the PCIe port driver. Move the fix from the port driver to the PCI core so we can work around this "PME doesn't work after waking from a sleep state" issue regardless of CONFIG_PCIEPORTBUS. [bhelgaas: folded in warning fix from Arnd Bergmann : https://lkml.kernel.org/r/20180328134747.2062348-1-arnd@arndb.de] Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-driver.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers/pci/pci-driver.c') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 3bed6beda051..c49af2b679bc 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -714,6 +714,17 @@ static void pci_pm_complete(struct device *dev) #endif /* !CONFIG_PM_SLEEP */ #ifdef CONFIG_SUSPEND +static void pcie_pme_root_status_cleanup(struct pci_dev *pci_dev) +{ + /* + * Some BIOSes forget to clear Root PME Status bits after system + * wakeup, which breaks ACPI-based runtime wakeup on PCI Express. + * Clear those bits now just in case (shouldn't hurt). + */ + if (pci_is_pcie(pci_dev) && + pci_pcie_type(pci_dev) == PCI_EXP_TYPE_ROOT_PORT) + pcie_clear_root_pme_status(pci_dev); +} static int pci_pm_suspend(struct device *dev) { @@ -873,6 +884,8 @@ static int pci_pm_resume_noirq(struct device *dev) if (pci_has_legacy_pm_support(pci_dev)) return pci_legacy_resume_early(dev); + pcie_pme_root_status_cleanup(pci_dev); + if (drv && drv->pm && drv->pm->resume_noirq) error = drv->pm->resume_noirq(dev); -- cgit v1.2.3 From 3620c71484f7a19b2588e577ea732f55719f5b1f Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 9 Mar 2018 11:06:55 -0600 Subject: PCI/PM: Clear PCIe PME Status bit for Root Complex Event Collectors Per PCIe r4.0, sec 6.1.6, Root Complex Event Collectors can generate PME interrupts on behalf of Root Complex Integrated Endpoints. Linux does not currently enable PME interrupts from RC Event Collectors, but fe31e69740ed ("PCI/PCIe: Clear Root PME Status bits early during system resume") suggests PME interrupts may be enabled by the platform for ACPI- based runtime wakeup. Clear the PCIe PME Status bit for Root Complex Event Collectors during resume, just like we already do for Root Ports. If the BIOS enables PME interrupts for an event collector and neglects to clear the status bit on resume, this change should fix the same bug as fe31e69740ed (PMEs not working after waking from a sleep state), but for Root Complex Integrated Endpoints. Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/pci/pci-driver.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/pci/pci-driver.c') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index c49af2b679bc..646da0d2d7a8 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -722,7 +722,8 @@ static void pcie_pme_root_status_cleanup(struct pci_dev *pci_dev) * Clear those bits now just in case (shouldn't hurt). */ if (pci_is_pcie(pci_dev) && - pci_pcie_type(pci_dev) == PCI_EXP_TYPE_ROOT_PORT) + (pci_pcie_type(pci_dev) == PCI_EXP_TYPE_ROOT_PORT || + pci_pcie_type(pci_dev) == PCI_EXP_TYPE_RC_EC)) pcie_clear_root_pme_status(pci_dev); } -- cgit v1.2.3 From c6c889d932bb49d95273711a790d16f814cb213b Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 9 Mar 2018 11:06:56 -0600 Subject: PCI/portdrv: Remove pcie_port_bus_type link order dependency The pcie_port_bus_type must be registered before drivers that depend on it can be registered. Those drivers include: pcied_init() # PCIe native hotplug driver aer_service_init() # AER driver dpc_service_init() # DPC driver pcie_pme_service_init() # PME driver Previously we registered pcie_port_bus_type from pcie_portdrv_init(), a device_initcall. The callers of pcie_port_service_register() (above) are also device_initcalls. This is fragile because the device_initcall ordering depends on link order, which is not explicit. Register pcie_port_bus_type from pci_driver_init() along with pci_bus_type. This removes the link order dependency between portdrv and the pciehp, AER, DPC, and PCIe PME drivers. Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki Reviewed-by: Christoph Hellwig --- drivers/pci/pci-driver.c | 44 ++++++++++++++++++++++++++++++++- drivers/pci/pcie/Makefile | 2 +- drivers/pci/pcie/portdrv_bus.c | 55 ------------------------------------------ drivers/pci/pcie/portdrv_pci.c | 13 +--------- 4 files changed, 45 insertions(+), 69 deletions(-) delete mode 100644 drivers/pci/pcie/portdrv_bus.c (limited to 'drivers/pci/pci-driver.c') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 646da0d2d7a8..21eb2f7ad95d 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -19,6 +19,7 @@ #include #include #include "pci.h" +#include "pcie/portdrv.h" struct pci_dynid { struct list_head node; @@ -1552,8 +1553,49 @@ struct bus_type pci_bus_type = { }; EXPORT_SYMBOL(pci_bus_type); +#ifdef CONFIG_PCIEPORTBUS +static int pcie_port_bus_match(struct device *dev, struct device_driver *drv) +{ + struct pcie_device *pciedev; + struct pcie_port_service_driver *driver; + + if (drv->bus != &pcie_port_bus_type || dev->bus != &pcie_port_bus_type) + return 0; + + pciedev = to_pcie_device(dev); + driver = to_service_driver(drv); + + if (driver->service != pciedev->service) + return 0; + + if (driver->port_type != PCIE_ANY_PORT && + driver->port_type != pci_pcie_type(pciedev->port)) + return 0; + + return 1; +} + +struct bus_type pcie_port_bus_type = { + .name = "pci_express", + .match = pcie_port_bus_match, +}; +EXPORT_SYMBOL_GPL(pcie_port_bus_type); +#endif + static int __init pci_driver_init(void) { - return bus_register(&pci_bus_type); + int ret; + + ret = bus_register(&pci_bus_type); + if (ret) + return ret; + +#ifdef CONFIG_PCIEPORTBUS + ret = bus_register(&pcie_port_bus_type); + if (ret) + return ret; +#endif + + return 0; } postcore_initcall(pci_driver_init); diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index 223e4c34c29a..e01c10c97b95 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -6,7 +6,7 @@ # Build PCI Express ASPM if needed obj-$(CONFIG_PCIEASPM) += aspm.o -pcieportdrv-y := portdrv_core.o portdrv_pci.o portdrv_bus.o +pcieportdrv-y := portdrv_core.o portdrv_pci.o pcieportdrv-$(CONFIG_ACPI) += portdrv_acpi.o obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o diff --git a/drivers/pci/pcie/portdrv_bus.c b/drivers/pci/pcie/portdrv_bus.c deleted file mode 100644 index 4969ccf6b214..000000000000 --- a/drivers/pci/pcie/portdrv_bus.c +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * File: portdrv_bus.c - * Purpose: PCI Express Port Bus Driver's Bus Overloading Functions - * - * Copyright (C) 2004 Intel - * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) - */ - -#include -#include -#include -#include -#include - -#include "portdrv.h" - -static int pcie_port_bus_match(struct device *dev, struct device_driver *drv); - -struct bus_type pcie_port_bus_type = { - .name = "pci_express", - .match = pcie_port_bus_match, -}; -EXPORT_SYMBOL_GPL(pcie_port_bus_type); - -static int pcie_port_bus_match(struct device *dev, struct device_driver *drv) -{ - struct pcie_device *pciedev; - struct pcie_port_service_driver *driver; - - if (drv->bus != &pcie_port_bus_type || dev->bus != &pcie_port_bus_type) - return 0; - - pciedev = to_pcie_device(dev); - driver = to_service_driver(drv); - - if (driver->service != pciedev->service) - return 0; - - if ((driver->port_type != PCIE_ANY_PORT) && - (driver->port_type != pci_pcie_type(pciedev->port))) - return 0; - - return 1; -} - -int pcie_port_bus_register(void) -{ - return bus_register(&pcie_port_bus_type); -} - -void pcie_port_bus_unregister(void) -{ - bus_unregister(&pcie_port_bus_type); -} diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 5d9d5305ebef..127321e17184 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -258,22 +258,11 @@ static const struct dmi_system_id pcie_portdrv_dmi_table[] __initconst = { static int __init pcie_portdrv_init(void) { - int retval; - if (pcie_ports_disabled) return -EACCES; dmi_check_system(pcie_portdrv_dmi_table); - retval = pcie_port_bus_register(); - if (retval) { - printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval); - goto out; - } - retval = pci_register_driver(&pcie_portdriver); - if (retval) - pcie_port_bus_unregister(); - out: - return retval; + return pci_register_driver(&pcie_portdriver); } device_initcall(pcie_portdrv_init); -- cgit v1.2.3