| From e88326f652ac6d126d5df8b3c0b2ac40e8b73797 Mon Sep 17 00:00:00 2001 |
| From: Vishnu Banavath <vishnu.banavath@arm.com> |
| Date: Tue, 23 Aug 2022 15:17:28 +0100 |
| Subject: [PATCH] pcie: Add quirk for the Arm Neoverse N1SDP platform |
| |
| The Arm N1SDP SoC suffers from some PCIe integration issues, most |
| prominently config space accesses to not existing BDFs being answered |
| with a bus abort, resulting in an SError. |
| To mitigate this, the firmware scans the bus before boot (catching the |
| SErrors) and creates a table with valid BDFs, which acts as a filter for |
| Linux' config space accesses. |
| |
| Add code consulting the table as an ACPI PCIe quirk, also register the |
| corresponding device tree based description of the host controller. |
| Also fix the other two minor issues on the way, namely not being fully |
| ECAM compliant and config space accesses being restricted to 32-bit |
| accesses only. |
| |
| This allows the Arm Neoverse N1SDP board to boot Linux without crashing |
| and to access *any* devices (there are no platform devices except UART). |
| |
| Signed-off-by: Deepak Pandey <Deepak.Pandey@arm.com> |
| [Sudipto: extend to cover the CCIX root port as well] |
| Signed-off-by: Sudipto Paul <sudipto.paul@arm.com> |
| [Andre: fix coding style issues, rewrite some parts, add DT support] |
| Signed-off-by: Andre Przywara <andre.przywara@arm.com> |
| |
| Upstream-Status: Inappropriate [will not be submitted as its a workaround to address hardware issue] |
| Signed-off-by: Deepak Pandey <Deepak.Pandey@arm.com> |
| Signed-off-by: Vishnu Banavath <vishnu.banavath@arm.com> |
| Signed-off-by: Adam Johnston <adam.johnston@arm.com> |
| --- |
| arch/arm64/configs/defconfig | 1 + |
| drivers/acpi/pci_mcfg.c | 7 + |
| drivers/pci/controller/Kconfig | 11 ++ |
| drivers/pci/controller/Makefile | 1 + |
| drivers/pci/controller/pcie-n1sdp.c | 198 ++++++++++++++++++++++++++++ |
| include/linux/pci-ecam.h | 2 + |
| 6 files changed, 220 insertions(+) |
| create mode 100644 drivers/pci/controller/pcie-n1sdp.c |
| |
| diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig |
| index 3ccbe378f875..1ce32678ccc7 100644 |
| --- a/arch/arm64/configs/defconfig |
| +++ b/arch/arm64/configs/defconfig |
| @@ -236,6 +236,7 @@ CONFIG_PCIE_LAYERSCAPE_GEN4=y |
| CONFIG_PCI_ENDPOINT=y |
| CONFIG_PCI_ENDPOINT_CONFIGFS=y |
| CONFIG_PCI_EPF_TEST=m |
| +CONFIG_PCI_QUIRKS=y |
| CONFIG_DEVTMPFS=y |
| CONFIG_DEVTMPFS_MOUNT=y |
| CONFIG_FW_LOADER_USER_HELPER=y |
| diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c |
| index 63b98eae5e75..7700d36393ec 100644 |
| --- a/drivers/acpi/pci_mcfg.c |
| +++ b/drivers/acpi/pci_mcfg.c |
| @@ -171,6 +171,13 @@ static struct mcfg_fixup mcfg_quirks[] = { |
| ALTRA_ECAM_QUIRK(1, 13), |
| ALTRA_ECAM_QUIRK(1, 14), |
| ALTRA_ECAM_QUIRK(1, 15), |
| + |
| +#define N1SDP_ECAM_MCFG(rev, seg, ops) \ |
| + {"ARMLTD", "ARMN1SDP", rev, seg, MCFG_BUS_ANY, ops } |
| + |
| + /* N1SDP SoC with v1 PCIe controller */ |
| + N1SDP_ECAM_MCFG(0x20181101, 0, &pci_n1sdp_pcie_ecam_ops), |
| + N1SDP_ECAM_MCFG(0x20181101, 1, &pci_n1sdp_ccix_ecam_ops), |
| #endif /* ARM64 */ |
| }; |
| |
| diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig |
| index b8d96d38064d..3c5a8a218442 100644 |
| --- a/drivers/pci/controller/Kconfig |
| +++ b/drivers/pci/controller/Kconfig |
| @@ -50,6 +50,17 @@ config PCI_IXP4XX |
| Say Y here if you want support for the PCI host controller found |
| in the Intel IXP4xx XScale-based network processor SoC. |
| |
| +config PCIE_HOST_N1SDP_ECAM |
| + bool "ARM N1SDP PCIe Controller" |
| + depends on ARM64 |
| + depends on OF || (ACPI && PCI_QUIRKS) |
| + select PCI_HOST_COMMON |
| + default y if ARCH_VEXPRESS |
| + help |
| + Say Y here if you want PCIe support for the Arm N1SDP platform. |
| + The controller is ECAM compliant, but needs a quirk to workaround |
| + an integration issue. |
| + |
| config PCI_TEGRA |
| bool "NVIDIA Tegra PCIe controller" |
| depends on ARCH_TEGRA || COMPILE_TEST |
| diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile |
| index 37c8663de7fe..5b8a9535a2f7 100644 |
| --- a/drivers/pci/controller/Makefile |
| +++ b/drivers/pci/controller/Makefile |
| @@ -39,6 +39,7 @@ obj-$(CONFIG_PCI_LOONGSON) += pci-loongson.o |
| obj-$(CONFIG_PCIE_HISI_ERR) += pcie-hisi-error.o |
| obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o |
| obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o |
| +obj-$(CONFIG_PCIE_HOST_N1SDP_ECAM) += pcie-n1sdp.o |
| |
| # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW |
| obj-y += dwc/ |
| diff --git a/drivers/pci/controller/pcie-n1sdp.c b/drivers/pci/controller/pcie-n1sdp.c |
| new file mode 100644 |
| index 000000000000..408699b9dcb1 |
| --- /dev/null |
| +++ b/drivers/pci/controller/pcie-n1sdp.c |
| @@ -0,0 +1,198 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright (C) 2018/2019 ARM Ltd. |
| + * |
| + * This quirk is to mask the following issues: |
| + * - PCIE SLVERR: config space accesses to invalid PCIe BDFs cause a bus |
| + * error (signalled as an asynchronous SError) |
| + * - MCFG BDF mapping: the root complex is mapped separately from the device |
| + * config space |
| + * - Non 32-bit accesses to config space are not supported. |
| + * |
| + * At boot time the SCP board firmware creates a discovery table with |
| + * the root complex' base address and the valid BDF values, discovered while |
| + * scanning the config space and catching the SErrors. |
| + * Linux responds only to the EPs listed in this table, returning NULL |
| + * for the rest. |
| + */ |
| + |
| +#include <linux/kernel.h> |
| +#include <linux/init.h> |
| +#include <linux/ioport.h> |
| +#include <linux/sizes.h> |
| +#include <linux/of_pci.h> |
| +#include <linux/of.h> |
| +#include <linux/pci-ecam.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/module.h> |
| + |
| +#include "../pci.h" |
| + |
| +/* Platform specific values as hardcoded in the firmware. */ |
| +#define AP_NS_SHARED_MEM_BASE 0x06000000 |
| +#define MAX_SEGMENTS 2 /* Two PCIe root complexes. */ |
| +#define BDF_TABLE_SIZE SZ_16K |
| + |
| +/* |
| + * Shared memory layout as written by the SCP upon boot time: |
| + * ---- |
| + * Discover data header --> RC base address |
| + * \-> BDF Count |
| + * Discover data --> BDF 0...n |
| + * ---- |
| + */ |
| +struct pcie_discovery_data { |
| + u32 rc_base_addr; |
| + u32 nr_bdfs; |
| + u32 valid_bdfs[0]; |
| +} *pcie_discovery_data[MAX_SEGMENTS]; |
| + |
| +void __iomem *rc_remapped_addr[MAX_SEGMENTS]; |
| + |
| +/* |
| + * map_bus() is called before we do a config space access for a certain |
| + * device. We use this to check whether this device is valid, avoiding |
| + * config space accesses which would result in an SError otherwise. |
| + */ |
| +static void __iomem *pci_n1sdp_map_bus(struct pci_bus *bus, unsigned int devfn, |
| + int where) |
| +{ |
| + struct pci_config_window *cfg = bus->sysdata; |
| + unsigned int devfn_shift = cfg->ops->bus_shift - 8; |
| + unsigned int busn = bus->number; |
| + unsigned int segment = bus->domain_nr; |
| + unsigned int bdf_addr; |
| + unsigned int table_count, i; |
| + struct pci_dev *dev; |
| + |
| + if (segment >= MAX_SEGMENTS || |
| + busn < cfg->busr.start || busn > cfg->busr.end) |
| + return NULL; |
| + |
| + /* The PCIe root complex has a separate config space mapping. */ |
| + if (busn == 0 && devfn == 0) |
| + return rc_remapped_addr[segment] + where; |
| + |
| + dev = pci_get_domain_bus_and_slot(segment, busn, devfn); |
| + if (dev && dev->is_virtfn) |
| + return pci_ecam_map_bus(bus, devfn, where); |
| + |
| + /* Accesses beyond the vendor ID always go to existing devices. */ |
| + if (where > 0) |
| + return pci_ecam_map_bus(bus, devfn, where); |
| + |
| + busn -= cfg->busr.start; |
| + bdf_addr = (busn << cfg->ops->bus_shift) + (devfn << devfn_shift); |
| + table_count = pcie_discovery_data[segment]->nr_bdfs; |
| + for (i = 0; i < table_count; i++) { |
| + if (bdf_addr == pcie_discovery_data[segment]->valid_bdfs[i]) |
| + return pci_ecam_map_bus(bus, devfn, where); |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +static int pci_n1sdp_init(struct pci_config_window *cfg, unsigned int segment) |
| +{ |
| + phys_addr_t table_base; |
| + struct device *dev = cfg->parent; |
| + struct pcie_discovery_data *shared_data; |
| + size_t bdfs_size; |
| + |
| + if (segment >= MAX_SEGMENTS) |
| + return -ENODEV; |
| + |
| + table_base = AP_NS_SHARED_MEM_BASE + segment * BDF_TABLE_SIZE; |
| + |
| + if (!request_mem_region(table_base, BDF_TABLE_SIZE, |
| + "PCIe valid BDFs")) { |
| + dev_err(dev, "PCIe BDF shared region request failed\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + shared_data = devm_ioremap(dev, |
| + table_base, BDF_TABLE_SIZE); |
| + if (!shared_data) |
| + return -ENOMEM; |
| + |
| + /* Copy the valid BDFs structure to allocated normal memory. */ |
| + bdfs_size = sizeof(struct pcie_discovery_data) + |
| + sizeof(u32) * shared_data->nr_bdfs; |
| + pcie_discovery_data[segment] = devm_kmalloc(dev, bdfs_size, GFP_KERNEL); |
| + if (!pcie_discovery_data[segment]) |
| + return -ENOMEM; |
| + |
| + memcpy_fromio(pcie_discovery_data[segment], shared_data, bdfs_size); |
| + |
| + rc_remapped_addr[segment] = devm_ioremap(dev, |
| + shared_data->rc_base_addr, |
| + PCI_CFG_SPACE_EXP_SIZE); |
| + if (!rc_remapped_addr[segment]) { |
| + dev_err(dev, "Cannot remap root port base\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + devm_iounmap(dev, shared_data); |
| + |
| + return 0; |
| +} |
| + |
| +/* Called for ACPI segment 0, and for all segments when using DT. */ |
| +static int pci_n1sdp_pcie_init(struct pci_config_window *cfg) |
| +{ |
| + struct platform_device *pdev = to_platform_device(cfg->parent); |
| + int segment = 0; |
| + |
| + if (pdev->dev.of_node) |
| + segment = of_get_pci_domain_nr(pdev->dev.of_node); |
| + if (segment < 0 || segment > MAX_SEGMENTS) { |
| + dev_err(&pdev->dev, "N1SDP PCI controllers require linux,pci-domain property\n"); |
| + dev_err(&pdev->dev, "Or invalid segment number, must be smaller than %d\n", |
| + MAX_SEGMENTS); |
| + return -EINVAL; |
| + } |
| + |
| + return pci_n1sdp_init(cfg, segment); |
| +} |
| + |
| +/* Called for ACPI segment 1. */ |
| +static int pci_n1sdp_ccix_init(struct pci_config_window *cfg) |
| +{ |
| + return pci_n1sdp_init(cfg, 1); |
| +} |
| + |
| +const struct pci_ecam_ops pci_n1sdp_pcie_ecam_ops = { |
| + .bus_shift = 20, |
| + .init = pci_n1sdp_pcie_init, |
| + .pci_ops = { |
| + .map_bus = pci_n1sdp_map_bus, |
| + .read = pci_generic_config_read32, |
| + .write = pci_generic_config_write32, |
| + } |
| +}; |
| + |
| +const struct pci_ecam_ops pci_n1sdp_ccix_ecam_ops = { |
| + .bus_shift = 20, |
| + .init = pci_n1sdp_ccix_init, |
| + .pci_ops = { |
| + .map_bus = pci_n1sdp_map_bus, |
| + .read = pci_generic_config_read32, |
| + .write = pci_generic_config_write32, |
| + } |
| +}; |
| + |
| +static const struct of_device_id n1sdp_pcie_of_match[] = { |
| + { .compatible = "arm,n1sdp-pcie", .data = &pci_n1sdp_pcie_ecam_ops }, |
| + { }, |
| +}; |
| +MODULE_DEVICE_TABLE(of, n1sdp_pcie_of_match); |
| + |
| +static struct platform_driver n1sdp_pcie_driver = { |
| + .driver = { |
| + .name = KBUILD_MODNAME, |
| + .of_match_table = n1sdp_pcie_of_match, |
| + .suppress_bind_attrs = true, |
| + }, |
| + .probe = pci_host_common_probe, |
| +}; |
| +builtin_platform_driver(n1sdp_pcie_driver); |
| diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h |
| index adea5a4771cf..e6bbc037cef8 100644 |
| --- a/include/linux/pci-ecam.h |
| +++ b/include/linux/pci-ecam.h |
| @@ -87,6 +87,8 @@ extern const struct pci_ecam_ops xgene_v1_pcie_ecam_ops; /* APM X-Gene PCIe v1 * |
| extern const struct pci_ecam_ops xgene_v2_pcie_ecam_ops; /* APM X-Gene PCIe v2.x */ |
| extern const struct pci_ecam_ops al_pcie_ops; /* Amazon Annapurna Labs PCIe */ |
| extern const struct pci_ecam_ops tegra194_pcie_ops; /* Tegra194 PCIe */ |
| +extern const struct pci_ecam_ops pci_n1sdp_pcie_ecam_ops; /* Arm N1SDP PCIe */ |
| +extern const struct pci_ecam_ops pci_n1sdp_ccix_ecam_ops; /* Arm N1SDP PCIe */ |
| #endif |
| |
| #if IS_ENABLED(CONFIG_PCI_HOST_COMMON) |