PCI: loongson: Prevent LS7A MRRS increases
authorHuacai Chen <chenhuacai@loongson.cn>
Wed, 1 Feb 2023 04:30:18 +0000 (12:30 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 11 Mar 2023 12:57:35 +0000 (13:57 +0100)
[ Upstream commit 8b3517f88ff2983f52698893519227c10aac90b2 ]

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 <chenhuacai@loongson.cn>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/pci/controller/pci-loongson.c
drivers/pci/pci.c
include/linux/pci.h

index 48169b1e38171d307897db0d355a097c9a0980a2..dc7b4e4293cedb638bb52fdf443fe1b42cb5a7a8 100644 (file)
@@ -60,37 +60,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, },
-       };
-
-       /* 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;
-               }
-       }
+       /*
+        * 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);
+
+       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 __iomem *cfg1_map(struct loongson_pci *priv, int bus,
                                unsigned int devfn, int where)
index 778ae3c861f458d141b06296ab9a77b64f4b8e6f..ce0988513fdafaff4be76ef8324b43e570810590 100644 (file)
@@ -5970,6 +5970,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;
@@ -5988,6 +5989,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);
 
index 9d6e75222868f3526f1e0f4fae464e3d7113d0b5..34dd24c99180491b01ede5a2a7fe5cfb69c620a7 100644 (file)
@@ -557,6 +557,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 */