PCI: loongson: Prevent LS7A MRRS increases

[ Upstream commit 8b3517f88f ]

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>
This commit is contained in:
Huacai Chen 2023-02-01 12:30:18 +08:00 committed by Greg Kroah-Hartman
parent 104c82d862
commit f45374c1b2
3 changed files with 25 additions and 28 deletions

View file

@ -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)
{

View file

@ -6017,6 +6017,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;
@ -6035,6 +6036,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);

View file

@ -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 */