From 62b6dee1b44aa23b3935543aff7df80399ec726b Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Wed, 1 Feb 2023 12:30:17 +0800 Subject: [PATCH 1/4] PCI/portdrv: Prevent LS7A Bus Master clearing on shutdown After cc27b735ad3a ("PCI/portdrv: Turn off PCIe services during shutdown") we observe hangs during poweroff/reboot on systems with LS7A chipset. This happens because the portdrv .shutdown() method (pcie_portdrv_remove()) clears PCI_COMMAND_MASTER via pci_disable_device(), which prevents bridges from forwarding memory or I/O Requests in the upstream direction (PCIe r6.0, sec 7.5.1.1.3). LS7A Root Ports have a hardware defect: clearing PCI_COMMAND_MASTER *also* prevents the bridge from forwarding CPU MMIO requests in the downstream direction, and these MMIO accesses to devices below the bridge happen even after .shutdown(), e.g., to print console messages. LS7A neither forwards the requests nor sends an unsuccessful completion to the CPU, so the CPU waits forever, resulting in the hang. The purpose of .shutdown() is to disable interrupts and DMA from the device. PCIe ports may generate interrupts (either MSI/MSI-X or INTx) for AER, DPC, PME, hotplug, etc., but they never perform DMA except MSI/MSI-X. Clearing PCI_COMMAND_MASTER effectively disables MSI/MSI-X, but not INTx. The port service driver .remove() methods clear the interrupt enables in PCI_ERR_ROOT_COMMAND, PCI_EXP_DPC_CTL, PCI_EXP_SLTCTL, and PCI_EXP_RTCTL, etc., which disables interrupts regardless of whether they are MSI/MSI-X or INTx. Add a pcie_portdrv_shutdown() method that calls all the port service driver .remove() methods to clear the interrupt enables for each service but does not clear Bus Mastering on the port itself. [bhelgaas: commit log] Link: https://lore.kernel.org/r/20230201043018.778499-2-chenhuacai@loongson.cn Signed-off-by: Huacai Chen Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/portdrv.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c index 2cc2e60bcb39..46fad0d813b2 100644 --- a/drivers/pci/pcie/portdrv.c +++ b/drivers/pci/pcie/portdrv.c @@ -501,7 +501,6 @@ static void pcie_port_device_remove(struct pci_dev *dev) { device_for_each_child(&dev->dev, NULL, remove_iter); pci_free_irq_vectors(dev); - pci_disable_device(dev); } /** @@ -727,6 +726,19 @@ static void pcie_portdrv_remove(struct pci_dev *dev) } pcie_port_device_remove(dev); + + pci_disable_device(dev); +} + +static void pcie_portdrv_shutdown(struct pci_dev *dev) +{ + if (pci_bridge_d3_possible(dev)) { + pm_runtime_forbid(&dev->dev); + pm_runtime_get_noresume(&dev->dev); + pm_runtime_dont_use_autosuspend(&dev->dev); + } + + pcie_port_device_remove(dev); } static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev, @@ -777,7 +789,7 @@ static struct pci_driver pcie_portdriver = { .probe = pcie_portdrv_probe, .remove = pcie_portdrv_remove, - .shutdown = pcie_portdrv_remove, + .shutdown = pcie_portdrv_shutdown, .err_handler = &pcie_portdrv_err_handler, From 8b3517f88ff2983f52698893519227c10aac90b2 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Wed, 1 Feb 2023 12:30:18 +0800 Subject: [PATCH 2/4] PCI: loongson: Prevent LS7A MRRS increases Except for isochronous-configured devices, software may set Max_Read_Request_Size (MRRS) to any value up to 4096. If a device issues a read request with size greater than the completer's Max_Payload_Size (MPS), the completer is required to break the response into multiple completions. Instead of correctly responding with multiple completions to a large read request, some LS7A Root Ports respond with a Completer Abort. To prevent this, the MRRS must be limited to an implementation-specific value. The OS cannot detect that value, so rely on BIOS to configure MRRS before booting, and quirk the Root Ports so we never set an MRRS larger than that BIOS value for any downstream device. N.B. Hot-added devices are not configured by BIOS, and they power up with MRRS = 512 bytes, so these devices will be limited to 512 bytes. If the LS7A limit is smaller, those hot-added devices may not work correctly, but per [1], hotplug is not supported with this chipset revision. [1] https://lore.kernel.org/r/073638a7-ae68-2847-ac3d-29e5e760d6af@loongson.cn [bhelgaas: commit log] Link: https://bugzilla.kernel.org/show_bug.cgi?id=216884 Link: https://lore.kernel.org/r/20230201043018.778499-3-chenhuacai@loongson.cn Signed-off-by: Huacai Chen Signed-off-by: Bjorn Helgaas --- drivers/pci/controller/pci-loongson.c | 42 +++++++++------------------ drivers/pci/pci.c | 10 +++++++ include/linux/pci.h | 1 + 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index 05c50408f13b..759ec211c17b 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -75,37 +75,23 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, DEV_LS7A_LPC, system_bus_quirk); -static void loongson_mrrs_quirk(struct pci_dev *dev) +static void loongson_mrrs_quirk(struct pci_dev *pdev) { - struct pci_bus *bus = dev->bus; - struct pci_dev *bridge; - static const struct pci_device_id bridge_devids[] = { - { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_0) }, - { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_1) }, - { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_2) }, - { 0, }, - }; + /* + * Some Loongson PCIe ports have h/w limitations of maximum read + * request size. They can't handle anything larger than this. So + * force this limit on any devices attached under these ports. + */ + struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus); - /* look for the matching bridge */ - while (!pci_is_root_bus(bus)) { - bridge = bus->self; - bus = bus->parent; - /* - * Some Loongson PCIe ports have a h/w limitation of - * 256 bytes maximum read request size. They can't handle - * anything larger than this. So force this limit on - * any devices attached under these ports. - */ - if (pci_match_id(bridge_devids, bridge)) { - if (pcie_get_readrq(dev) > 256) { - pci_info(dev, "limiting MRRS to 256\n"); - pcie_set_readrq(dev, 256); - } - break; - } - } + bridge->no_inc_mrrs = 1; } -DECLARE_PCI_FIXUP_ENABLE(PCI_ANY_ID, PCI_ANY_ID, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_PCIE_PORT_0, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_PCIE_PORT_1, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_PCIE_PORT_2, loongson_mrrs_quirk); static void loongson_pci_pin_quirk(struct pci_dev *pdev) { diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index fba95486caaf..9d98bba72af9 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -6033,6 +6033,7 @@ int pcie_set_readrq(struct pci_dev *dev, int rq) { u16 v; int ret; + struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus); if (rq < 128 || rq > 4096 || !is_power_of_2(rq)) return -EINVAL; @@ -6051,6 +6052,15 @@ int pcie_set_readrq(struct pci_dev *dev, int rq) v = (ffs(rq) - 8) << 12; + if (bridge->no_inc_mrrs) { + int max_mrrs = pcie_get_readrq(dev); + + if (rq > max_mrrs) { + pci_info(dev, "can't set Max_Read_Request_Size to %d; max is %d\n", rq, max_mrrs); + return -EINVAL; + } + } + ret = pcie_capability_clear_and_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_READRQ, v); diff --git a/include/linux/pci.h b/include/linux/pci.h index adffd65e84b4..3df2049ec4a8 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -572,6 +572,7 @@ struct pci_host_bridge { void *release_data; unsigned int ignore_reset_delay:1; /* For entire hierarchy */ unsigned int no_ext_tags:1; /* No Extended Tags */ + unsigned int no_inc_mrrs:1; /* No Increase MRRS */ unsigned int native_aer:1; /* OS may use PCIe AER */ unsigned int native_pcie_hotplug:1; /* OS may use PCIe hotplug */ unsigned int native_shpc_hotplug:1; /* OS may use SHPC hotplug */ From c768f8c5f40fcdc6f058cc2f02592163d6c6716c Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 11 Feb 2023 10:33:21 +0800 Subject: [PATCH 3/4] PCI: loongson: Add more devices that need MRRS quirk Loongson-2K SOC and LS7A2000 chipset add new PCI IDs that need MRRS quirk. Add them. Link: https://lore.kernel.org/r/20230211023321.3530080-1-chenhuacai@loongson.cn Signed-off-by: Huacai Chen Signed-off-by: Bjorn Helgaas --- drivers/pci/controller/pci-loongson.c | 33 +++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index 759ec211c17b..fe0f732f6e43 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -15,9 +15,14 @@ #include "../pci.h" /* Device IDs */ -#define DEV_PCIE_PORT_0 0x7a09 -#define DEV_PCIE_PORT_1 0x7a19 -#define DEV_PCIE_PORT_2 0x7a29 +#define DEV_LS2K_PCIE_PORT0 0x1a05 +#define DEV_LS7A_PCIE_PORT0 0x7a09 +#define DEV_LS7A_PCIE_PORT1 0x7a19 +#define DEV_LS7A_PCIE_PORT2 0x7a29 +#define DEV_LS7A_PCIE_PORT3 0x7a39 +#define DEV_LS7A_PCIE_PORT4 0x7a49 +#define DEV_LS7A_PCIE_PORT5 0x7a59 +#define DEV_LS7A_PCIE_PORT6 0x7a69 #define DEV_LS2K_APB 0x7a02 #define DEV_LS7A_GMAC 0x7a03 @@ -53,11 +58,11 @@ static void bridge_class_quirk(struct pci_dev *dev) dev->class = PCI_CLASS_BRIDGE_PCI_NORMAL; } DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_0, bridge_class_quirk); + DEV_LS7A_PCIE_PORT0, bridge_class_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_1, bridge_class_quirk); + DEV_LS7A_PCIE_PORT1, bridge_class_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_2, bridge_class_quirk); + DEV_LS7A_PCIE_PORT2, bridge_class_quirk); static void system_bus_quirk(struct pci_dev *pdev) { @@ -87,11 +92,21 @@ static void loongson_mrrs_quirk(struct pci_dev *pdev) bridge->no_inc_mrrs = 1; } DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_0, loongson_mrrs_quirk); + DEV_LS2K_PCIE_PORT0, loongson_mrrs_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_1, loongson_mrrs_quirk); + DEV_LS7A_PCIE_PORT0, loongson_mrrs_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_2, loongson_mrrs_quirk); + DEV_LS7A_PCIE_PORT1, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT2, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT3, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT4, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT5, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT6, loongson_mrrs_quirk); static void loongson_pci_pin_quirk(struct pci_dev *pdev) { From 6fffbc7ae1373e10b989afe23a9eeb9c49fe15c3 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Fri, 10 Feb 2023 10:43:51 -0600 Subject: [PATCH 4/4] PCI: Honor firmware's device disabled status If a device has a firmware node (DT/ACPI), and the device is marked disabled, that is currently ignored. Add a check for this condition and bail out creating the pci_dev. This assumes the config space for the device can still be accessed because they already have by this point in order to identify the device. Link: https://lore.kernel.org/r/20230210164351.2687475-1-robh@kernel.org Tested-by: Binbin Zhou Signed-off-by: Rob Herring Signed-off-by: Bjorn Helgaas Cc: Liu Peibao Cc: Huacai Chen --- drivers/pci/probe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 1779582fb500..b1d80c1d7a69 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1841,6 +1841,8 @@ int pci_setup_device(struct pci_dev *dev) pci_set_of_node(dev); pci_set_acpi_fwnode(dev); + if (dev->dev.fwnode && !fwnode_device_is_available(dev->dev.fwnode)) + return -ENODEV; pci_dev_assign_slot(dev);