Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 1 | From e88326f652ac6d126d5df8b3c0b2ac40e8b73797 Mon Sep 17 00:00:00 2001 |
| 2 | From: Vishnu Banavath <vishnu.banavath@arm.com> |
| 3 | Date: Tue, 23 Aug 2022 15:17:28 +0100 |
Patrick Williams | 8dd6848 | 2022-10-04 07:57:18 -0500 | [diff] [blame] | 4 | Subject: [PATCH] pcie: Add quirk for the Arm Neoverse N1SDP platform |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 5 | |
| 6 | The Arm N1SDP SoC suffers from some PCIe integration issues, most |
| 7 | prominently config space accesses to not existing BDFs being answered |
| 8 | with a bus abort, resulting in an SError. |
| 9 | To mitigate this, the firmware scans the bus before boot (catching the |
| 10 | SErrors) and creates a table with valid BDFs, which acts as a filter for |
| 11 | Linux' config space accesses. |
| 12 | |
| 13 | Add code consulting the table as an ACPI PCIe quirk, also register the |
| 14 | corresponding device tree based description of the host controller. |
| 15 | Also fix the other two minor issues on the way, namely not being fully |
| 16 | ECAM compliant and config space accesses being restricted to 32-bit |
| 17 | accesses only. |
| 18 | |
| 19 | This allows the Arm Neoverse N1SDP board to boot Linux without crashing |
| 20 | and to access *any* devices (there are no platform devices except UART). |
| 21 | |
| 22 | Signed-off-by: Deepak Pandey <Deepak.Pandey@arm.com> |
| 23 | [Sudipto: extend to cover the CCIX root port as well] |
| 24 | Signed-off-by: Sudipto Paul <sudipto.paul@arm.com> |
| 25 | [Andre: fix coding style issues, rewrite some parts, add DT support] |
| 26 | Signed-off-by: Andre Przywara <andre.przywara@arm.com> |
Patrick Williams | 8dd6848 | 2022-10-04 07:57:18 -0500 | [diff] [blame] | 27 | |
| 28 | Upstream-Status: Inappropriate [will not be submitted as its a workaround to address hardware issue] |
| 29 | Signed-off-by: Deepak Pandey <Deepak.Pandey@arm.com> |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 30 | Signed-off-by: Vishnu Banavath <vishnu.banavath@arm.com> |
| 31 | Signed-off-by: Adam Johnston <adam.johnston@arm.com> |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 32 | --- |
| 33 | arch/arm64/configs/defconfig | 1 + |
| 34 | drivers/acpi/pci_mcfg.c | 7 + |
| 35 | drivers/pci/controller/Kconfig | 11 ++ |
| 36 | drivers/pci/controller/Makefile | 1 + |
| 37 | drivers/pci/controller/pcie-n1sdp.c | 198 ++++++++++++++++++++++++++++ |
| 38 | include/linux/pci-ecam.h | 2 + |
| 39 | 6 files changed, 220 insertions(+) |
| 40 | create mode 100644 drivers/pci/controller/pcie-n1sdp.c |
| 41 | |
| 42 | diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 43 | index 3ccbe378f875..1ce32678ccc7 100644 |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 44 | --- a/arch/arm64/configs/defconfig |
| 45 | +++ b/arch/arm64/configs/defconfig |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 46 | @@ -236,6 +236,7 @@ CONFIG_PCIE_LAYERSCAPE_GEN4=y |
| 47 | CONFIG_PCI_ENDPOINT=y |
| 48 | CONFIG_PCI_ENDPOINT_CONFIGFS=y |
| 49 | CONFIG_PCI_EPF_TEST=m |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 50 | +CONFIG_PCI_QUIRKS=y |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 51 | CONFIG_DEVTMPFS=y |
| 52 | CONFIG_DEVTMPFS_MOUNT=y |
| 53 | CONFIG_FW_LOADER_USER_HELPER=y |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 54 | diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 55 | index 63b98eae5e75..7700d36393ec 100644 |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 56 | --- a/drivers/acpi/pci_mcfg.c |
| 57 | +++ b/drivers/acpi/pci_mcfg.c |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 58 | @@ -171,6 +171,13 @@ static struct mcfg_fixup mcfg_quirks[] = { |
| 59 | ALTRA_ECAM_QUIRK(1, 13), |
| 60 | ALTRA_ECAM_QUIRK(1, 14), |
| 61 | ALTRA_ECAM_QUIRK(1, 15), |
| 62 | + |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 63 | +#define N1SDP_ECAM_MCFG(rev, seg, ops) \ |
| 64 | + {"ARMLTD", "ARMN1SDP", rev, seg, MCFG_BUS_ANY, ops } |
| 65 | + |
| 66 | + /* N1SDP SoC with v1 PCIe controller */ |
| 67 | + N1SDP_ECAM_MCFG(0x20181101, 0, &pci_n1sdp_pcie_ecam_ops), |
| 68 | + N1SDP_ECAM_MCFG(0x20181101, 1, &pci_n1sdp_ccix_ecam_ops), |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 69 | #endif /* ARM64 */ |
| 70 | }; |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 71 | |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 72 | diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 73 | index b8d96d38064d..3c5a8a218442 100644 |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 74 | --- a/drivers/pci/controller/Kconfig |
| 75 | +++ b/drivers/pci/controller/Kconfig |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 76 | @@ -50,6 +50,17 @@ config PCI_IXP4XX |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 77 | Say Y here if you want support for the PCI host controller found |
| 78 | in the Intel IXP4xx XScale-based network processor SoC. |
| 79 | |
| 80 | +config PCIE_HOST_N1SDP_ECAM |
| 81 | + bool "ARM N1SDP PCIe Controller" |
| 82 | + depends on ARM64 |
| 83 | + depends on OF || (ACPI && PCI_QUIRKS) |
| 84 | + select PCI_HOST_COMMON |
| 85 | + default y if ARCH_VEXPRESS |
| 86 | + help |
| 87 | + Say Y here if you want PCIe support for the Arm N1SDP platform. |
| 88 | + The controller is ECAM compliant, but needs a quirk to workaround |
| 89 | + an integration issue. |
| 90 | + |
| 91 | config PCI_TEGRA |
| 92 | bool "NVIDIA Tegra PCIe controller" |
| 93 | depends on ARCH_TEGRA || COMPILE_TEST |
| 94 | diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 95 | index 37c8663de7fe..5b8a9535a2f7 100644 |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 96 | --- a/drivers/pci/controller/Makefile |
| 97 | +++ b/drivers/pci/controller/Makefile |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 98 | @@ -39,6 +39,7 @@ obj-$(CONFIG_PCI_LOONGSON) += pci-loongson.o |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 99 | obj-$(CONFIG_PCIE_HISI_ERR) += pcie-hisi-error.o |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 100 | obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o |
| 101 | obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 102 | +obj-$(CONFIG_PCIE_HOST_N1SDP_ECAM) += pcie-n1sdp.o |
Patrick Williams | 2194f50 | 2022-10-16 14:26:09 -0500 | [diff] [blame^] | 103 | |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 104 | # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW |
| 105 | obj-y += dwc/ |
Brad Bishop | bec4ebc | 2022-08-03 09:55:16 -0400 | [diff] [blame] | 106 | diff --git a/drivers/pci/controller/pcie-n1sdp.c b/drivers/pci/controller/pcie-n1sdp.c |
| 107 | new file mode 100644 |
| 108 | index 000000000000..408699b9dcb1 |
| 109 | --- /dev/null |
| 110 | +++ b/drivers/pci/controller/pcie-n1sdp.c |
| 111 | @@ -0,0 +1,198 @@ |
| 112 | +// SPDX-License-Identifier: GPL-2.0 |
| 113 | +/* |
| 114 | + * Copyright (C) 2018/2019 ARM Ltd. |
| 115 | + * |
| 116 | + * This quirk is to mask the following issues: |
| 117 | + * - PCIE SLVERR: config space accesses to invalid PCIe BDFs cause a bus |
| 118 | + * error (signalled as an asynchronous SError) |
| 119 | + * - MCFG BDF mapping: the root complex is mapped separately from the device |
| 120 | + * config space |
| 121 | + * - Non 32-bit accesses to config space are not supported. |
| 122 | + * |
| 123 | + * At boot time the SCP board firmware creates a discovery table with |
| 124 | + * the root complex' base address and the valid BDF values, discovered while |
| 125 | + * scanning the config space and catching the SErrors. |
| 126 | + * Linux responds only to the EPs listed in this table, returning NULL |
| 127 | + * for the rest. |
| 128 | + */ |
| 129 | + |
| 130 | +#include <linux/kernel.h> |
| 131 | +#include <linux/init.h> |
| 132 | +#include <linux/ioport.h> |
| 133 | +#include <linux/sizes.h> |
| 134 | +#include <linux/of_pci.h> |
| 135 | +#include <linux/of.h> |
| 136 | +#include <linux/pci-ecam.h> |
| 137 | +#include <linux/platform_device.h> |
| 138 | +#include <linux/module.h> |
| 139 | + |
| 140 | +#include "../pci.h" |
| 141 | + |
| 142 | +/* Platform specific values as hardcoded in the firmware. */ |
| 143 | +#define AP_NS_SHARED_MEM_BASE 0x06000000 |
| 144 | +#define MAX_SEGMENTS 2 /* Two PCIe root complexes. */ |
| 145 | +#define BDF_TABLE_SIZE SZ_16K |
| 146 | + |
| 147 | +/* |
| 148 | + * Shared memory layout as written by the SCP upon boot time: |
| 149 | + * ---- |
| 150 | + * Discover data header --> RC base address |
| 151 | + * \-> BDF Count |
| 152 | + * Discover data --> BDF 0...n |
| 153 | + * ---- |
| 154 | + */ |
| 155 | +struct pcie_discovery_data { |
| 156 | + u32 rc_base_addr; |
| 157 | + u32 nr_bdfs; |
| 158 | + u32 valid_bdfs[0]; |
| 159 | +} *pcie_discovery_data[MAX_SEGMENTS]; |
| 160 | + |
| 161 | +void __iomem *rc_remapped_addr[MAX_SEGMENTS]; |
| 162 | + |
| 163 | +/* |
| 164 | + * map_bus() is called before we do a config space access for a certain |
| 165 | + * device. We use this to check whether this device is valid, avoiding |
| 166 | + * config space accesses which would result in an SError otherwise. |
| 167 | + */ |
| 168 | +static void __iomem *pci_n1sdp_map_bus(struct pci_bus *bus, unsigned int devfn, |
| 169 | + int where) |
| 170 | +{ |
| 171 | + struct pci_config_window *cfg = bus->sysdata; |
| 172 | + unsigned int devfn_shift = cfg->ops->bus_shift - 8; |
| 173 | + unsigned int busn = bus->number; |
| 174 | + unsigned int segment = bus->domain_nr; |
| 175 | + unsigned int bdf_addr; |
| 176 | + unsigned int table_count, i; |
| 177 | + struct pci_dev *dev; |
| 178 | + |
| 179 | + if (segment >= MAX_SEGMENTS || |
| 180 | + busn < cfg->busr.start || busn > cfg->busr.end) |
| 181 | + return NULL; |
| 182 | + |
| 183 | + /* The PCIe root complex has a separate config space mapping. */ |
| 184 | + if (busn == 0 && devfn == 0) |
| 185 | + return rc_remapped_addr[segment] + where; |
| 186 | + |
| 187 | + dev = pci_get_domain_bus_and_slot(segment, busn, devfn); |
| 188 | + if (dev && dev->is_virtfn) |
| 189 | + return pci_ecam_map_bus(bus, devfn, where); |
| 190 | + |
| 191 | + /* Accesses beyond the vendor ID always go to existing devices. */ |
| 192 | + if (where > 0) |
| 193 | + return pci_ecam_map_bus(bus, devfn, where); |
| 194 | + |
| 195 | + busn -= cfg->busr.start; |
| 196 | + bdf_addr = (busn << cfg->ops->bus_shift) + (devfn << devfn_shift); |
| 197 | + table_count = pcie_discovery_data[segment]->nr_bdfs; |
| 198 | + for (i = 0; i < table_count; i++) { |
| 199 | + if (bdf_addr == pcie_discovery_data[segment]->valid_bdfs[i]) |
| 200 | + return pci_ecam_map_bus(bus, devfn, where); |
| 201 | + } |
| 202 | + |
| 203 | + return NULL; |
| 204 | +} |
| 205 | + |
| 206 | +static int pci_n1sdp_init(struct pci_config_window *cfg, unsigned int segment) |
| 207 | +{ |
| 208 | + phys_addr_t table_base; |
| 209 | + struct device *dev = cfg->parent; |
| 210 | + struct pcie_discovery_data *shared_data; |
| 211 | + size_t bdfs_size; |
| 212 | + |
| 213 | + if (segment >= MAX_SEGMENTS) |
| 214 | + return -ENODEV; |
| 215 | + |
| 216 | + table_base = AP_NS_SHARED_MEM_BASE + segment * BDF_TABLE_SIZE; |
| 217 | + |
| 218 | + if (!request_mem_region(table_base, BDF_TABLE_SIZE, |
| 219 | + "PCIe valid BDFs")) { |
| 220 | + dev_err(dev, "PCIe BDF shared region request failed\n"); |
| 221 | + return -ENOMEM; |
| 222 | + } |
| 223 | + |
| 224 | + shared_data = devm_ioremap(dev, |
| 225 | + table_base, BDF_TABLE_SIZE); |
| 226 | + if (!shared_data) |
| 227 | + return -ENOMEM; |
| 228 | + |
| 229 | + /* Copy the valid BDFs structure to allocated normal memory. */ |
| 230 | + bdfs_size = sizeof(struct pcie_discovery_data) + |
| 231 | + sizeof(u32) * shared_data->nr_bdfs; |
| 232 | + pcie_discovery_data[segment] = devm_kmalloc(dev, bdfs_size, GFP_KERNEL); |
| 233 | + if (!pcie_discovery_data[segment]) |
| 234 | + return -ENOMEM; |
| 235 | + |
| 236 | + memcpy_fromio(pcie_discovery_data[segment], shared_data, bdfs_size); |
| 237 | + |
| 238 | + rc_remapped_addr[segment] = devm_ioremap(dev, |
| 239 | + shared_data->rc_base_addr, |
| 240 | + PCI_CFG_SPACE_EXP_SIZE); |
| 241 | + if (!rc_remapped_addr[segment]) { |
| 242 | + dev_err(dev, "Cannot remap root port base\n"); |
| 243 | + return -ENOMEM; |
| 244 | + } |
| 245 | + |
| 246 | + devm_iounmap(dev, shared_data); |
| 247 | + |
| 248 | + return 0; |
| 249 | +} |
| 250 | + |
| 251 | +/* Called for ACPI segment 0, and for all segments when using DT. */ |
| 252 | +static int pci_n1sdp_pcie_init(struct pci_config_window *cfg) |
| 253 | +{ |
| 254 | + struct platform_device *pdev = to_platform_device(cfg->parent); |
| 255 | + int segment = 0; |
| 256 | + |
| 257 | + if (pdev->dev.of_node) |
| 258 | + segment = of_get_pci_domain_nr(pdev->dev.of_node); |
| 259 | + if (segment < 0 || segment > MAX_SEGMENTS) { |
| 260 | + dev_err(&pdev->dev, "N1SDP PCI controllers require linux,pci-domain property\n"); |
| 261 | + dev_err(&pdev->dev, "Or invalid segment number, must be smaller than %d\n", |
| 262 | + MAX_SEGMENTS); |
| 263 | + return -EINVAL; |
| 264 | + } |
| 265 | + |
| 266 | + return pci_n1sdp_init(cfg, segment); |
| 267 | +} |
| 268 | + |
| 269 | +/* Called for ACPI segment 1. */ |
| 270 | +static int pci_n1sdp_ccix_init(struct pci_config_window *cfg) |
| 271 | +{ |
| 272 | + return pci_n1sdp_init(cfg, 1); |
| 273 | +} |
| 274 | + |
| 275 | +const struct pci_ecam_ops pci_n1sdp_pcie_ecam_ops = { |
| 276 | + .bus_shift = 20, |
| 277 | + .init = pci_n1sdp_pcie_init, |
| 278 | + .pci_ops = { |
| 279 | + .map_bus = pci_n1sdp_map_bus, |
| 280 | + .read = pci_generic_config_read32, |
| 281 | + .write = pci_generic_config_write32, |
| 282 | + } |
| 283 | +}; |
| 284 | + |
| 285 | +const struct pci_ecam_ops pci_n1sdp_ccix_ecam_ops = { |
| 286 | + .bus_shift = 20, |
| 287 | + .init = pci_n1sdp_ccix_init, |
| 288 | + .pci_ops = { |
| 289 | + .map_bus = pci_n1sdp_map_bus, |
| 290 | + .read = pci_generic_config_read32, |
| 291 | + .write = pci_generic_config_write32, |
| 292 | + } |
| 293 | +}; |
| 294 | + |
| 295 | +static const struct of_device_id n1sdp_pcie_of_match[] = { |
| 296 | + { .compatible = "arm,n1sdp-pcie", .data = &pci_n1sdp_pcie_ecam_ops }, |
| 297 | + { }, |
| 298 | +}; |
| 299 | +MODULE_DEVICE_TABLE(of, n1sdp_pcie_of_match); |
| 300 | + |
| 301 | +static struct platform_driver n1sdp_pcie_driver = { |
| 302 | + .driver = { |
| 303 | + .name = KBUILD_MODNAME, |
| 304 | + .of_match_table = n1sdp_pcie_of_match, |
| 305 | + .suppress_bind_attrs = true, |
| 306 | + }, |
| 307 | + .probe = pci_host_common_probe, |
| 308 | +}; |
| 309 | +builtin_platform_driver(n1sdp_pcie_driver); |
| 310 | diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h |
| 311 | index adea5a4771cf..e6bbc037cef8 100644 |
| 312 | --- a/include/linux/pci-ecam.h |
| 313 | +++ b/include/linux/pci-ecam.h |
| 314 | @@ -87,6 +87,8 @@ extern const struct pci_ecam_ops xgene_v1_pcie_ecam_ops; /* APM X-Gene PCIe v1 * |
| 315 | extern const struct pci_ecam_ops xgene_v2_pcie_ecam_ops; /* APM X-Gene PCIe v2.x */ |
| 316 | extern const struct pci_ecam_ops al_pcie_ops; /* Amazon Annapurna Labs PCIe */ |
| 317 | extern const struct pci_ecam_ops tegra194_pcie_ops; /* Tegra194 PCIe */ |
| 318 | +extern const struct pci_ecam_ops pci_n1sdp_pcie_ecam_ops; /* Arm N1SDP PCIe */ |
| 319 | +extern const struct pci_ecam_ops pci_n1sdp_ccix_ecam_ops; /* Arm N1SDP PCIe */ |
| 320 | #endif |
| 321 | |
| 322 | #if IS_ENABLED(CONFIG_PCI_HOST_COMMON) |