123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- From 5aa5769af625c79589fd84b8afc06149c2362218 Mon Sep 17 00:00:00 2001
- From: Deepak Pandey <Deepak.Pandey@arm.com>
- Date: Fri, 31 May 2019 16:42:43 +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>
- Change-Id: I1d3a4b9bf6b3b883d262e3c4ff1f88a0eb81c1fe
- 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 | 2 +-
- drivers/pci/controller/pcie-n1sdp.c | 198 ++++++++++++++++++++++++++++
- include/linux/pci-ecam.h | 2 +
- 6 files changed, 220 insertions(+), 1 deletion(-)
- create mode 100644 drivers/pci/controller/pcie-n1sdp.c
- diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
- index bbbc31391a65..973aa3b4d407 100644
- --- a/arch/arm64/configs/defconfig
- +++ b/arch/arm64/configs/defconfig
- @@ -214,6 +214,7 @@ CONFIG_NFC_S3FWRN5_I2C=m
- CONFIG_PCI=y
- CONFIG_PCIEPORTBUS=y
- CONFIG_PCIEAER=y
- +CONFIG_PCI_QUIRKS=y
- CONFIG_PCI_IOV=y
- CONFIG_PCI_PASID=y
- CONFIG_HOTPLUG_PCI=y
- diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c
- index 860014b89b8e..2d4c1c699ffe 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 */
-
- #ifdef CONFIG_LOONGARCH
- diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
- index bfd9bac37e24..7a65799dded7 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..08e5afcf6e86 100644
- --- a/drivers/pci/controller/Makefile
- +++ b/drivers/pci/controller/Makefile
- @@ -39,7 +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/
- obj-y += mobiveil/
- 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 6b1301e2498e..b3cf3adeab28 100644
- --- a/include/linux/pci-ecam.h
- +++ b/include/linux/pci-ecam.h
- @@ -88,6 +88,8 @@ 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 loongson_pci_ecam_ops; /* Loongson 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)
|