This patch set provides initial Cavium Thunderx support.
David Daney (4): pci: Add is_pcierc element to struct pci_bus gic-its: Allow pci_requester_id to be overridden. arm64, pci: Allow RC drivers to supply pcibios_add_device() implementation. irqchip: gic-v3: Add gic_get_irq_domain() to get the irqdomain of the GIC.
Ganapatrao Kulkarni (4): arm64:numa: adding numa support for arm64 platforms. Documentation: arm64/arm: dt bindings for numa. arm64:numa:dt adding numa node mapping using dt node property arm,associativity efi: Avoid dt memory node deletion from efistub.
Radha Mohan Chintakuntla (3): net: mdio-octeon: Modify driver to work on both ThunderX and Octeon net: mdio-octeon: Fix octeon_mdiobus_probe function for return values net: thunderx: Select CONFIG_MDIO_OCTEON for ThunderX NIC
Thanneeru Srinivasulu (1): net: thunderx: Force to load octeon-mdio before bgx driver. Signed-off-by: Thanneeru Srinivasulu tsrinivasulu@caviumnetworks.com
Tirumalesh Chalamarla (6): arm64: gicv3: its: Ignore Chacheability fields in GITS_BASER arm64: gicv3: its: Set correct its table size for ThunderX arm64, gicv3, thunder: Add workaround for erratum AP-23154 PCI_ Add host drivers for Cavium ThunderX processors arm64: KVM: Enable minimalistic support for Thunder its:thunder: ThunderX 23144 errata fix.
Tomasz Nowicki (1): Compiler bug workaround!!!
Vadim Lomovtsev (1): Revert "acpi, thuderx, pci: Add MCFG fixup."
Documentation/devicetree/bindings/arm/numa.txt | 212 +++++++++ arch/arm64/Kconfig | 64 +++ arch/arm64/include/asm/cputype.h | 3 + arch/arm64/include/asm/mmzone.h | 32 ++ arch/arm64/include/asm/numa.h | 43 ++ arch/arm64/include/asm/pci.h | 3 + arch/arm64/include/uapi/asm/kvm.h | 3 +- arch/arm64/kernel/Makefile | 1 + arch/arm64/kernel/dt_numa.c | 302 +++++++++++++ arch/arm64/kernel/pci.c | 10 + arch/arm64/kernel/setup.c | 8 + arch/arm64/kernel/smp.c | 2 + arch/arm64/kvm/guest.c | 6 + arch/arm64/kvm/sys_regs_generic_v8.c | 2 + arch/arm64/mm/Makefile | 1 + arch/arm64/mm/init.c | 34 +- arch/arm64/mm/numa.c | 522 ++++++++++++++++++++++ drivers/firmware/efi/libstub/fdt.c | 12 +- drivers/irqchip/irq-gic-v3-its.c | 42 +- drivers/irqchip/irq-gic-v3.c | 12 + drivers/net/ethernet/cavium/Kconfig | 2 + drivers/net/ethernet/cavium/thunder/thunder_bgx.c | 3 + drivers/net/ethernet/cavium/thunder/thunder_bgx.h | 1 + drivers/net/phy/Kconfig | 9 +- drivers/net/phy/mdio-octeon.c | 136 +++++- drivers/pci/host/Kconfig | 12 + drivers/pci/host/Makefile | 2 + drivers/pci/host/pcie-thunder-pem.c | 462 +++++++++++++++++++ drivers/pci/host/pcie-thunder.c | 335 ++++++++++++++ drivers/pci/probe.c | 2 + drivers/tty/n_tty.c | 3 +- include/linux/irqchip/arm-gic-v3.h | 3 + include/linux/pci.h | 1 + 33 files changed, 2247 insertions(+), 38 deletions(-) create mode 100644 Documentation/devicetree/bindings/arm/numa.txt create mode 100644 arch/arm64/include/asm/mmzone.h create mode 100644 arch/arm64/include/asm/numa.h create mode 100644 arch/arm64/kernel/dt_numa.c create mode 100644 arch/arm64/mm/numa.c create mode 100644 drivers/pci/host/pcie-thunder-pem.c create mode 100644 drivers/pci/host/pcie-thunder.c
From: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com
Implement erratum:
24313 - Pointer Memory Attribute Fields with Wrong Access Type
These fields have a wrong access type of RO/RAZ. Ignoring the bits.
Signed-off-by: Tirumalesh Chalamarla tchalamarla@cavium.com Signed-off-by: Robert Richter rrichter@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- arch/arm64/Kconfig | 7 +++++++ drivers/irqchip/irq-gic-v3-its.c | 4 ++++ 2 files changed, 11 insertions(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 318175f..68ef1f1 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -243,6 +243,13 @@ config ARCH_THUNDER help This enables support for Cavium's Thunder Family of SoCs.
+config THUNDERX_PASS1_ERRATA_24313 + bool "Cavium ThunderX erratum 24313" + depends on ARCH_THUNDER + def_bool ARCH_THUNDER + help + Enable workaround for erratum 24313. + config ARCH_VEXPRESS bool "ARMv8 software model (Versatile Express)" select ARCH_REQUIRE_GPIOLIB diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 1b7e155..a46040f 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -802,7 +802,11 @@ static int its_alloc_tables(struct its_node *its) int i; int psz = SZ_64K; u64 shr = GITS_BASER_InnerShareable; +#ifdef CONFIG_THUNDERX_PASS1_ERRATA_24313 + u64 cache = 0; +#else u64 cache = GITS_BASER_WaWb; +#endif
for (i = 0; i < GITS_BASER_NR_REGS; i++) { u64 val = readq_relaxed(its->base + GITS_BASER + i * 8);
From: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com
Max its table size for ThunderX is 8MB (20 bits, 128 pages).
Currently the controller is detecting 16MB (256 pages). There is a bug in ITS (erratum 22375) that prevents S/W to set "0xff" (256 of 64KB pages) as the max table size of device table. This patch avoids setting page size to the maximum and also saves 8MB unnecessarily allocated memory.
Signed-off-by: Robert Richter rrichter@cavium.com Signed-off-by: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- arch/arm64/Kconfig | 7 +++++++ drivers/irqchip/irq-gic-v3-its.c | 4 ++++ 2 files changed, 11 insertions(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 68ef1f1..af502e0 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -243,6 +243,13 @@ config ARCH_THUNDER help This enables support for Cavium's Thunder Family of SoCs.
+config THUNDERX_PASS1_ERRATA_22375 + bool "Cavium ThunderX erratum 22375" + depends on ARCH_THUNDER + def_bool ARCH_THUNDER + help + Enable workaround for erratum 22375. + config THUNDERX_PASS1_ERRATA_24313 bool "Cavium ThunderX erratum 24313" depends on ARCH_THUNDER diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index a46040f..36b664c 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -829,8 +829,12 @@ static int its_alloc_tables(struct its_node *its) * For other tables, only allocate a single page. */ if (type == GITS_BASER_TYPE_DEVICE) { +#ifdef CONFIG_THUNDERX_PASS1_ERRATA_22375 + u32 ids = 0x13; /* 20 bits, 8MB */ +#else u64 typer = readq_relaxed(its->base + GITS_TYPER); u32 ids = GITS_TYPER_DEVBITS(typer); +#endif
/* * 'order' was initialized earlier to the default page
From: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com
This patch adds a workaround for erratum AP-23154.
Signed-off-by: Chad Reese kreese@cavium.com Signed-off-by: Tirumalesh Chalamarla tchalamarla@cavium.com Signed-off-by: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com Signed-off-by: Robert Richter rrichter@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- arch/arm64/Kconfig | 12 ++++++++++++ drivers/irqchip/irq-gic-v3.c | 7 +++++++ 2 files changed, 19 insertions(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index af502e0..2c542d5 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -243,6 +243,16 @@ config ARCH_THUNDER help This enables support for Cavium's Thunder Family of SoCs.
+menu "Cavium ThunderX errata" + +config THUNDERX_PASS1_ERRATA_23154 + bool "Cavium ThunderX erratum 23154" + depends on ARCH_THUNDER + def_bool ARCH_THUNDER + help + Enable workaround for erratum 23154. + + config THUNDERX_PASS1_ERRATA_22375 bool "Cavium ThunderX erratum 22375" depends on ARCH_THUNDER @@ -257,6 +267,8 @@ config THUNDERX_PASS1_ERRATA_24313 help Enable workaround for erratum 24313.
+endmenu + config ARCH_VEXPRESS bool "ARMv8 software model (Versatile Express)" select ARCH_REQUIRE_GPIOLIB diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index c52f7ba..a0452dd 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -111,7 +111,14 @@ static u64 __maybe_unused gic_read_iar(void) { u64 irqstat;
+#ifdef CONFIG_THUNDERX_PASS1_ERRATA_23154 + asm volatile("nop;nop;nop;nop;nop;nop;nop;nop;"); asm volatile("mrs_s %0, " __stringify(ICC_IAR1_EL1) : "=r" (irqstat)); + asm volatile("nop;nop;nop;nop;"); + mb(); +#else + asm volatile("mrs_s %0, " __stringify(ICC_IAR1_EL1) : "=r" (irqstat)); +#endif return irqstat; }
From: David Daney david.daney@cavium.com
... and use is to force only_one_child() to return true.
Needed because the ThunderX PCIe RC cannot be identified by existing methods.
Signed-off-by: David Daney david.daney@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/pci/probe.c | 2 ++ include/linux/pci.h | 1 + 2 files changed, 3 insertions(+)
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index cefd636..11ec2e7 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1643,6 +1643,8 @@ static int only_one_child(struct pci_bus *bus) { struct pci_dev *parent = bus->self;
+ if (bus->is_pcierc) + return 1; if (!parent || !pci_is_pcie(parent)) return 0; if (pci_pcie_type(parent) == PCI_EXP_TYPE_ROOT_PORT) diff --git a/include/linux/pci.h b/include/linux/pci.h index 8a0321a..1f1ce73 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -473,6 +473,7 @@ struct pci_bus { struct bin_attribute *legacy_io; /* legacy I/O for this bus */ struct bin_attribute *legacy_mem; /* legacy mem */ unsigned int is_added:1; + unsigned int is_pcierc:1; };
#define to_pci_bus(n) container_of(n, struct pci_bus, dev)
From: David Daney david.daney@cavium.com
Signed-off-by: David Daney david.daney@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/irqchip/irq-gic-v3-its.c | 14 +++++++++++++- include/linux/irqchip/arm-gic-v3.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 36b664c..890aa3b 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1197,11 +1197,23 @@ static int its_pci_msi_vec_count(struct pci_dev *pdev) return max(msi, msix); }
+static u32 its_dflt_pci_requester_id(struct pci_dev *pdev, u16 alias) +{ + return alias; +} + +static its_pci_requester_id_t its_pci_requester_id = its_dflt_pci_requester_id; +void set_its_pci_requester_id(its_pci_requester_id_t fn) +{ + its_pci_requester_id = fn; +} +EXPORT_SYMBOL(set_its_pci_requester_id); + static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data) { struct its_pci_alias *dev_alias = data;
- dev_alias->dev_id = alias; + dev_alias->dev_id = its_pci_requester_id(pdev, alias); if (pdev != dev_alias->pdev) dev_alias->count += its_pci_msi_vec_count(dev_alias->pdev);
diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index ffbc034..18e3757 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -389,6 +389,8 @@ int its_cpu_init(void); int its_init(struct device_node *node, struct rdists *rdists, struct irq_domain *domain);
+typedef u32 (*its_pci_requester_id_t)(struct pci_dev *, u16); +void set_its_pci_requester_id(its_pci_requester_id_t fn); #endif
#endif
From: David Daney david.daney@cavium.com
The default is to continue doing the what we have done before, but add a hook so that this can be overridden.
Signed-off-by: David Daney david.daney@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- arch/arm64/include/asm/pci.h | 3 +++ arch/arm64/kernel/pci.c | 10 ++++++++++ 2 files changed, 13 insertions(+)
diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index b008a72..ad3fb18 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -37,6 +37,9 @@ static inline int pci_proc_domain(struct pci_bus *bus) { return 1; } + +void set_pcibios_add_device(int (*arg)(struct pci_dev *)); + #endif /* CONFIG_PCI */
#endif /* __KERNEL__ */ diff --git a/arch/arm64/kernel/pci.c b/arch/arm64/kernel/pci.c index 4095379..3356023 100644 --- a/arch/arm64/kernel/pci.c +++ b/arch/arm64/kernel/pci.c @@ -38,11 +38,21 @@ resource_size_t pcibios_align_resource(void *data, const struct resource *res, return res->start; }
+static int (*pcibios_add_device_impl)(struct pci_dev *); + +void set_pcibios_add_device(int (*arg)(struct pci_dev *)) +{ + pcibios_add_device_impl = arg; +} + /* * Try to assign the IRQ number from DT when adding a new device */ int pcibios_add_device(struct pci_dev *dev) { + if (pcibios_add_device_impl) + return pcibios_add_device_impl(dev); + dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
return 0;
From: David Daney david.daney@cavium.com
Needed to map SPI interrupt sources.
Signed-off-by: David Daney david.daney@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/irqchip/irq-gic-v3.c | 5 +++++ include/linux/irqchip/arm-gic-v3.h | 1 + 2 files changed, 6 insertions(+)
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index a0452dd..aef2da3 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -58,6 +58,11 @@ static struct gic_chip_data gic_data __read_mostly; /* Our default, arbitrary priority value. Linux only uses one anyway. */ #define DEFAULT_PMR_VALUE 0xf0
+struct irq_domain *gic_get_irq_domain(void) +{ + return gic_data.domain; +} + static inline unsigned int gic_irq(struct irq_data *d) { return d->hwirq; diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 18e3757..5992224 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -391,6 +391,7 @@ int its_init(struct device_node *node, struct rdists *rdists,
typedef u32 (*its_pci_requester_id_t)(struct pci_dev *, u16); void set_its_pci_requester_id(its_pci_requester_id_t fn); +struct irq_domain *gic_get_irq_domain(void); #endif
#endif
From: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com
Signed-off-by: David Daney david.daney@cavium.com Signed-off-by: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/pci/host/Kconfig | 12 + drivers/pci/host/Makefile | 2 + drivers/pci/host/pcie-thunder-pem.c | 462 ++++++++++++++++++++++++++++++++++++ drivers/pci/host/pcie-thunder.c | 423 +++++++++++++++++++++++++++++++++ 4 files changed, 899 insertions(+) create mode 100644 drivers/pci/host/pcie-thunder-pem.c create mode 100644 drivers/pci/host/pcie-thunder.c
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index c132bdd..06e26ad 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -145,4 +145,16 @@ config PCIE_IPROC_BCMA Say Y here if you want to use the Broadcom iProc PCIe controller through the BCMA bus interface
+config PCI_THUNDER_PEM + bool + +config PCI_THUNDER + bool "Thunder PCIe host controller" + depends on ARM64 || COMPILE_TEST + depends on OF_PCI + select PCI_MSI + select PCI_THUNDER_PEM + help + Say Y here if you want internal PCI support on Thunder SoC. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 140d66f..a355155 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -17,3 +17,5 @@ obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o +obj-$(CONFIG_PCI_THUNDER) += pcie-thunder.o +obj-$(CONFIG_PCI_THUNDER_PEM) += pcie-thunder-pem.o diff --git a/drivers/pci/host/pcie-thunder-pem.c b/drivers/pci/host/pcie-thunder-pem.c new file mode 100644 index 0000000..7861a8a --- /dev/null +++ b/drivers/pci/host/pcie-thunder-pem.c @@ -0,0 +1,462 @@ +/* + * PCIe host controller driver for Cavium Thunder SOC + * + * Copyright (C) 2014,2015 Cavium Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +/* #define DEBUG 1 */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/pci.h> +#include <linux/irqdomain.h> +#include <linux/msi.h> +#include <linux/irqchip/arm-gic-v3.h> + +#define THUNDER_SLI_S2M_REG_ACC_BASE 0x874001000000ull + +#define THUNDER_GIC 0x801000000000ull +#define THUNDER_GICD_SETSPI_NSR 0x801000000040ull +#define THUNDER_GICD_CLRSPI_NSR 0x801000000048ull + +#define THUNDER_GSER_PCIE_MASK 0x01 + +#define PEM_CTL_STATUS 0x000 +#define PEM_RD_CFG 0x030 +#define P2N_BAR0_START 0x080 +#define P2N_BAR1_START 0x088 +#define P2N_BAR2_START 0x090 +#define BAR_CTL 0x0a8 +#define BAR2_MASK 0x0b0 +#define BAR1_INDEX 0x100 +#define PEM_CFG 0x410 +#define PEM_ON 0x420 + +struct thunder_pem { + struct list_head list; /* on thunder_pem_buses */ + bool connected; + unsigned int id; + unsigned int sli; + unsigned int sli_group; + unsigned int node; + u64 sli_window_base; + void __iomem *bar0; + void __iomem *bar4; + void __iomem *sli_s2m; + void __iomem *cfgregion; + struct pci_bus *bus; + int vwire_irqs[4]; + u32 vwire_data[4]; +}; + +static LIST_HEAD(thunder_pem_buses); + +static struct pci_device_id thunder_pem_pci_table[] = { + {PCI_VENDOR_ID_CAVIUM, 0xa020, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, thunder_pem_pci_table); + +enum slix_s2m_ctype { + CTYPE_MEMORY = 0, + CTYPE_CONFIG = 1, + CTYPE_IO = 2 +}; + +static u64 slix_s2m_reg_val(unsigned mac, enum slix_s2m_ctype ctype, + bool merge, bool relaxed, bool snoop, u32 ba_msb) +{ + u64 v; + + v = (u64)(mac % 3) << 49; + v |= (u64)ctype << 53; + if (!merge) + v |= 1ull << 48; + if (relaxed) + v |= 5ull << 40; + if (!snoop) + v |= 5ull << 41; + v |= (u64)ba_msb; + + return v; +} + +static u32 thunder_pcierc_config_read(struct thunder_pem *pem, u32 reg, int size) +{ + unsigned int val; + + writeq(reg & ~3u, pem->bar0 + PEM_RD_CFG); + val = readq(pem->bar0 + PEM_RD_CFG) >> 32; + + if (size == 1) + val = (val >> (8 * (reg & 3))) & 0xff; + else if (size == 2) + val = (val >> (8 * (reg & 3))) & 0xffff; + + return val; +} + +static int thunder_pem_read_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 *val) +{ + void __iomem *addr; + struct thunder_pem *pem = bus->sysdata; + unsigned int busnr = bus->number; + + if (busnr > 255 || devfn > 255 || reg > 4095) + return PCIBIOS_DEVICE_NOT_FOUND; + + addr = pem->cfgregion + ((busnr << 24) | (devfn << 16) | reg); + + switch (size) { + case 1: + *val = readb(addr); + break; + case 2: + *val = readw(addr); + break; + case 4: + *val = readl(addr); + break; + default: + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_pem_write_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 val) +{ + void __iomem *addr; + struct thunder_pem *pem = bus->sysdata; + unsigned int busnr = bus->number; + + if (busnr > 255 || devfn > 255 || reg > 4095) + return PCIBIOS_DEVICE_NOT_FOUND; + + addr = pem->cfgregion + ((busnr << 24) | (devfn << 16) | reg); + + switch (size) { + case 1: + writeb(val, addr); + break; + case 2: + writew(val, addr); + break; + case 4: + writel(val, addr); + break; + default: + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops thunder_pem_ops = { + .read = thunder_pem_read_config, + .write = thunder_pem_write_config, +}; + +static struct thunder_pem *thunder_pem_from_dev(struct pci_dev *dev) +{ + struct thunder_pem *pem; + struct pci_bus *bus = dev->bus; + + while (!pci_is_root_bus(bus)) + bus = bus->parent; + + list_for_each_entry(pem, &thunder_pem_buses, list) { + if (pem->bus == bus) + return pem; + } + return NULL; +} + +int thunder_pem_requester_id(struct pci_dev *dev) +{ + struct thunder_pem *pem = thunder_pem_from_dev(dev); + + if (!pem) + return -ENODEV; + + if (pem->id < 3) + return ((1 << 16) | + ((dev)->bus->number << 8) | + (dev)->devfn); + + if (pem->id < 6) + return ((3 << 16) | + ((dev)->bus->number << 8) | + (dev)->devfn); + + if (pem->id < 9) + return ((1 << 19) | (1 << 16) | + ((dev)->bus->number << 8) | + (dev)->devfn); + + if (pem->id < 12) + return ((1 << 19) | + (3 << 16) | + ((dev)->bus->number << 8) | + (dev)->devfn); + return -ENODEV; +} + +static int thunder_pem_pcibios_add_device(struct pci_dev *dev) +{ + struct thunder_pem *pem; + u8 pin; + + pem = thunder_pem_from_dev(dev); + if (!pem) + return 0; + + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); + + /* Cope with illegal. */ + if (pin > 4) + pin = 1; + + dev->irq = pin > 0 ? pem->vwire_irqs[pin - 1] : 0; + + if (pin) + dev_dbg(&dev->dev, "assigning IRQ %02d\n", dev->irq); + + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); + + return 0; +} + +static int thunder_pem_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct thunder_pem *pem; + resource_size_t bar0_start; + u64 regval; + u64 sliaddr, pciaddr; + u32 cfgval; + int primary_bus; + int i; + int ret = 0; + struct resource *res; + LIST_HEAD(resources); + + set_pcibios_add_device(thunder_pem_pcibios_add_device); + + pem = devm_kzalloc(&pdev->dev, sizeof(*pem), GFP_KERNEL); + if (!pem) + return -ENOMEM; + + pci_set_drvdata(pdev, pem); + + bar0_start = pci_resource_start(pdev, 0); + pem->node = (bar0_start >> 44) & 3; + pem->id = ((bar0_start >> 24) & 7) + (6 * pem->node); + pem->sli = pem->id % 3; + pem->sli_group = (pem->id / 3) % 2; + pem->sli_window_base = 0x880000000000ull | (((u64)pem->node) << 44) | ((u64)pem->sli_group << 40); + pem->sli_window_base += 0x4000000000 * pem->sli; + + ret = pci_enable_device_mem(pdev); + if (ret) + goto out; + + pem->bar0 = pcim_iomap(pdev, 0, 0x100000); + if (!pem->bar0) { + ret = -ENOMEM; + goto out; + } + + pem->bar4 = pcim_iomap(pdev, 4, 0x100000); + if (!pem->bar0) { + ret = -ENOMEM; + goto out; + } + + sliaddr = THUNDER_SLI_S2M_REG_ACC_BASE | ((u64)pem->node << 44) | ((u64)pem->sli_group << 36); + + regval = readq(pem->bar0 + PEM_ON); + if (!(regval & 1)) { + dev_notice(&pdev->dev, "PEM%u_ON not set, skipping...\n", pem->id); + goto out; + } + + regval = readq(pem->bar0 + PEM_CTL_STATUS); + regval |= 0x10; /* Set Link Enable bit */ + writeq(regval, pem->bar0 + PEM_CTL_STATUS); + + udelay(1000); + + cfgval = thunder_pcierc_config_read(pem, 32 * 4, 4); /* PCIERC_CFG032 */ + + if (((cfgval >> 29 & 0x1) == 0x0) || ((cfgval >> 27 & 0x1) == 0x1)) { + dev_notice(&pdev->dev, "PEM%u Link Timeout, skipping...\n", pem->id); + goto out; + } + + pem->sli_s2m = devm_ioremap(&pdev->dev, sliaddr, 0x1000); + if (!pem->sli_s2m) { + ret = -ENOMEM; + goto out; + } + + pem->cfgregion = devm_ioremap(&pdev->dev, pem->sli_window_base, 0x100000000ull); + if (!pem->cfgregion) { + ret = -ENOMEM; + goto out; + } + regval = slix_s2m_reg_val(pem->sli, CTYPE_CONFIG, false, false, false, 0); + writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + 0)); + + cfgval = thunder_pcierc_config_read(pem, 6 * 4, 4); /* PCIERC_CFG006 */ + primary_bus = (cfgval >> 8) & 0xff; + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) { + ret = -ENOMEM; + goto out; + } + res->start = primary_bus; + res->end = 255; + res->flags = IORESOURCE_BUS; + pci_add_resource(&resources, res); + + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) { + ret = -ENOMEM; + goto out; + } + res->start = 0x100000 * pem->id; + res->end = res->start + 0x100000 - 1; + res->flags = IORESOURCE_IO; + pci_add_resource(&resources, res); + regval = slix_s2m_reg_val(pem->sli, CTYPE_IO, false, false, false, 0); + writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + 1)); + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) { + ret = -ENOMEM; + goto out; + } + pciaddr = 0x10000000ull; + res->start = pem->sli_window_base + 0x1000000000ull + pciaddr; + res->end = res->start + 0x1000000000ull - pciaddr - 1; + res->flags = IORESOURCE_MEM; + pci_add_resource_offset(&resources, res, res->start - pciaddr); + for (i = 0; i < 16; i++) { + regval = slix_s2m_reg_val(pem->sli, CTYPE_MEMORY, false, false, false, i); + writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + (0x10 + i))); + } + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) { + ret = -ENOMEM; + goto out; + } + pciaddr = 0x1000000000ull; + res->start = pem->sli_window_base + 0x1000000000ull + pciaddr; + res->end = res->start + 0x1000000000ull - 1; + res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH; + pci_add_resource_offset(&resources, res, res->start - pciaddr); + for (i = 0; i < 16; i++) { + regval = slix_s2m_reg_val(pem->sli, CTYPE_MEMORY, true, true, true, i + 0x10); + writeq(regval, pem->sli_s2m + 0x10 * ((0x40 * pem->sli) + (0x20 + i))); + } + + writeq(0, pem->bar0 + P2N_BAR0_START); + writeq(0, pem->bar0 + P2N_BAR1_START); + writeq(0, pem->bar0 + P2N_BAR2_START); + + regval = 0x10; /* BAR_CTL[BAR1_SIZ] = 1 (64MB) */ + regval |= 0x8; /* BAR_CTL[BAR2_ENB] = 1 */ + writeq(regval, pem->bar0 + BAR_CTL); + + /* 1st 4MB region -> GIC registers so 32-bit MSI can reach the GIC. */ + regval = (THUNDER_GIC + (((u64)pem->node) << 44)) >> 18; + /* BAR1_INDEX[ADDR_V] = 1 */ + regval |= 1; + writeq(regval, pem->bar0 + BAR1_INDEX); + /* Remaining regions linear mapping to physical address space */ + for (i = 1; i < 16; i++) { + regval = (i << 4) | 1; + writeq(regval, pem->bar0 + BAR1_INDEX + 8 * i); + } + + pem->bus = pci_create_root_bus(&pdev->dev, primary_bus, &thunder_pem_ops, pem, &resources); + if (!pem->bus) { + ret = -ENODEV; + goto err_root_bus; + } + pem->bus->is_pcierc = 1; + list_add_tail(&pem->list, &thunder_pem_buses); + + for (i = 0; i < 3; i++) { + pem->vwire_data[i] = 40 + 4 * pem->id + i; + pem->vwire_irqs[i] = irq_create_mapping(gic_get_irq_domain(), pem->vwire_data[i]); + if (!pem->vwire_irqs[i]) { + dev_err(&pdev->dev, "Error: No irq mapping for %u\n", pem->vwire_data[i]); + continue; + } + irq_set_irq_type(pem->vwire_irqs[i], IRQ_TYPE_LEVEL_HIGH); + + writeq(THUNDER_GICD_SETSPI_NSR, pem->bar4 + 0 + (i + 2) * 32); + writeq(pem->vwire_data[i], pem->bar4 + 8 + (i + 2) * 32); + writeq(THUNDER_GICD_CLRSPI_NSR, pem->bar4 + 16 + (i + 2) * 32); + writeq(pem->vwire_data[i], pem->bar4 + 24 + (i + 2) * 32); + } + ret = pci_read_config_dword(pdev, 44 * 4, &cfgval); + if (WARN_ON(ret)) + goto err_free_root_bus; + cfgval &= ~0x40000000; /* Clear FUNM */ + cfgval |= 0x80000000; /* Set MSIXEN */ + pci_write_config_dword(pdev, 44 * 4, cfgval); + pem->bus->msi = pdev->bus->msi; + + pci_scan_child_bus(pem->bus); + pci_bus_add_devices(pem->bus); + pci_assign_unassigned_root_bus_resources(pem->bus); + + return 0; + +err_free_root_bus: + pci_remove_root_bus(pem->bus); +err_root_bus: + pci_free_resource_list(&resources); +out: + return ret; +} + +static void thunder_pem_pci_remove(struct pci_dev *pdev) +{ +} + +static struct pci_driver thunder_pem_driver = { + .name = "thunder_pem", + .id_table = thunder_pem_pci_table, + .probe = thunder_pem_pci_probe, + .remove = thunder_pem_pci_remove +}; + +static int __init thunder_pcie_init(void) +{ + int ret; + + ret = pci_register_driver(&thunder_pem_driver); + + return ret; +} +module_init(thunder_pcie_init); + +static void __exit thunder_pcie_exit(void) +{ + pci_unregister_driver(&thunder_pem_driver); +} +module_exit(thunder_pcie_exit); diff --git a/drivers/pci/host/pcie-thunder.c b/drivers/pci/host/pcie-thunder.c new file mode 100644 index 0000000..cbe4b44 --- /dev/null +++ b/drivers/pci/host/pcie-thunder.c @@ -0,0 +1,423 @@ +/* + * PCIe host controller driver for Cavium Thunder SOC + * + * Copyright (C) 2014, 2015 Cavium Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/of_pci.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/msi.h> +#include <linux/irqchip/arm-gic-v3.h> + +#define PCI_DEVICE_ID_THUNDER_BRIDGE 0xa002 + +#define THUNDER_PCIE_BUS_SHIFT 20 +#define THUNDER_PCIE_DEV_SHIFT 15 +#define THUNDER_PCIE_FUNC_SHIFT 12 + +#define THUNDER_ECAM0_CFG_BASE 0x848000000000 +#define THUNDER_ECAM1_CFG_BASE 0x849000000000 +#define THUNDER_ECAM2_CFG_BASE 0x84a000000000 +#define THUNDER_ECAM3_CFG_BASE 0x84b000000000 +#define THUNDER_ECAM4_CFG_BASE 0x948000000000 +#define THUNDER_ECAM5_CFG_BASE 0x949000000000 +#define THUNDER_ECAM6_CFG_BASE 0x94a000000000 +#define THUNDER_ECAM7_CFG_BASE 0x94b000000000 + +struct thunder_pcie { + struct device_node *node; + struct device *dev; + void __iomem *cfg_base; + struct msi_controller *msi; + int ecam; + bool valid; +}; + +int thunder_pem_requester_id(struct pci_dev *dev); + +static atomic_t thunder_pcie_ecam_probed; + +static u32 pci_requester_id_ecam(struct pci_dev *dev) +{ + return (((pci_domain_nr(dev->bus) >> 2) << 19) | + ((pci_domain_nr(dev->bus) % 4) << 16) | + (dev->bus->number << 8) | dev->devfn); +} + +static u32 thunder_pci_requester_id(struct pci_dev *dev, u16 alias) +{ + int ret; + + ret = thunder_pem_requester_id(dev); + if (ret >= 0) + return (u32)ret; + + return pci_requester_id_ecam(dev); +} + +/* + * This bridge is just for the sake of supporting ARI for + * downstream devices. No resources are attached to it. + * Copy upstream root bus resources to bridge which aide in + * resource claiming for downstream devices + */ +static void pci_bridge_resource_fixup(struct pci_dev *dev) +{ + struct pci_bus *bus; + int resno; + + bus = dev->subordinate; + for (resno = 0; resno < PCI_BRIDGE_RESOURCE_NUM; resno++) { + bus->resource[resno] = pci_bus_resource_n(bus->parent, + PCI_BRIDGE_RESOURCE_NUM + resno); + } + + for (resno = PCI_BRIDGE_RESOURCES; + resno <= PCI_BRIDGE_RESOURCE_END; resno++) { + dev->resource[resno].start = dev->resource[resno].end = 0; + dev->resource[resno].flags = 0; + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_THUNDER_BRIDGE, + pci_bridge_resource_fixup); + +/* + * All PCIe devices in Thunder have fixed resources, shouldn't be reassigned. + * Also claim the device's valid resources to set 'res->parent' hierarchy. + */ +static void pci_dev_resource_fixup(struct pci_dev *dev) +{ + struct resource *res; + int resno; + + /* + * If the ECAM is not yet probed, we must be in a virtual + * machine. In that case, don't mark things as + * IORESOURCE_PCI_FIXED + */ + if (!atomic_read(&thunder_pcie_ecam_probed)) + return; + + for (resno = 0; resno < PCI_NUM_RESOURCES; resno++) + dev->resource[resno].flags |= IORESOURCE_PCI_FIXED; + + for (resno = 0; resno < PCI_BRIDGE_RESOURCES; resno++) { + res = &dev->resource[resno]; + if (res->parent || !(res->flags & IORESOURCE_MEM)) + continue; + pci_claim_resource(dev, resno); + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_CAVIUM, PCI_ANY_ID, + pci_dev_resource_fixup); + +static void __iomem *thunder_pcie_get_cfg_addr(struct thunder_pcie *pcie, + unsigned int busnr, + unsigned int devfn, int reg) +{ + return pcie->cfg_base + + ((busnr << THUNDER_PCIE_BUS_SHIFT) + | (PCI_SLOT(devfn) << THUNDER_PCIE_DEV_SHIFT) + | (PCI_FUNC(devfn) << THUNDER_PCIE_FUNC_SHIFT)) + reg; +} + +static int thunder_pcie_read_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 *val) +{ + struct thunder_pcie *pcie = bus->sysdata; + void __iomem *addr; + unsigned int busnr = bus->number; + + if (busnr > 255 || devfn > 255 || reg > 4095) + return PCIBIOS_DEVICE_NOT_FOUND; + + addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg); + + switch (size) { + case 1: + *val = readb(addr); + break; + case 2: + *val = readw(addr); + break; + case 4: + *val = readl(addr); + break; + default: + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_pcie_write_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 val) +{ + struct thunder_pcie *pcie = bus->sysdata; + void __iomem *addr; + unsigned int busnr = bus->number; + + if (busnr > 255 || devfn > 255 || reg > 4095) + return PCIBIOS_DEVICE_NOT_FOUND; + + addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg); + + switch (size) { + case 1: + writeb(val, addr); + break; + case 2: + writew(val, addr); + break; + case 4: + writel(val, addr); + break; + default: + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops thunder_pcie_ops = { + .read = thunder_pcie_read_config, + .write = thunder_pcie_write_config, +}; + +static int thunder_pcie_msi_enable(struct thunder_pcie *pcie, + struct pci_bus *bus) +{ + struct device_node *msi_node; + + msi_node = of_parse_phandle(pcie->node, "msi-parent", 0); + if (!msi_node) + return -ENODEV; + + pcie->msi = of_pci_find_msi_chip_by_node(msi_node); + if (!pcie->msi) + return -ENODEV; + + pcie->msi->dev = pcie->dev; + bus->msi = pcie->msi; + + return 0; +} + +static void thunder_pcie_config(struct thunder_pcie *pcie, u64 addr) +{ + atomic_set(&thunder_pcie_ecam_probed, 1); + set_its_pci_requester_id(thunder_pci_requester_id); + + pcie->valid = true; + + switch (addr) { + case THUNDER_ECAM0_CFG_BASE: + pcie->ecam = 0; + break; + case THUNDER_ECAM1_CFG_BASE: + pcie->ecam = 1; + break; + case THUNDER_ECAM2_CFG_BASE: + pcie->ecam = 2; + break; + case THUNDER_ECAM3_CFG_BASE: + pcie->ecam = 3; + break; + case THUNDER_ECAM4_CFG_BASE: + pcie->ecam = 4; + break; + case THUNDER_ECAM5_CFG_BASE: + pcie->ecam = 5; + break; + case THUNDER_ECAM6_CFG_BASE: + pcie->ecam = 6; + break; + case THUNDER_ECAM7_CFG_BASE: + pcie->ecam = 7; + break; + default: + pcie->valid = false; + break; + } +} + +static int thunder_pcie_probe(struct platform_device *pdev) +{ + struct thunder_pcie *pcie; + struct resource *cfg_base; + struct pci_bus *bus; + int ret = 0; + LIST_HEAD(res); + + pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->node = of_node_get(pdev->dev.of_node); + pcie->dev = &pdev->dev; + + /* Get controller's configuration space range */ + cfg_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + thunder_pcie_config(pcie, cfg_base->start); + + pcie->cfg_base = devm_ioremap_resource(&pdev->dev, cfg_base); + if (IS_ERR(pcie->cfg_base)) { + ret = PTR_ERR(pcie->cfg_base); + goto err_ioremap; + } + + dev_info(&pdev->dev, "ECAM%d CFG BASE 0x%llx\n", + pcie->ecam, (u64)cfg_base->start); + + ret = of_pci_get_host_bridge_resources(pdev->dev.of_node, + 0, 255, &res, NULL); + if (ret) + goto err_root_bus; + + bus = pci_create_root_bus(&pdev->dev, 0, &thunder_pcie_ops, pcie, &res); + if (!bus) { + ret = -ENODEV; + goto err_root_bus; + } + + /* Set reference to MSI chip */ + ret = thunder_pcie_msi_enable(pcie, bus); + if (ret) { + dev_err(&pdev->dev, + "Unable to set reference to MSI chip: ret=%d\n", ret); + goto err_msi; + } + + platform_set_drvdata(pdev, pcie); + + pci_scan_child_bus(bus); + pci_bus_add_devices(bus); + + return 0; +err_msi: + pci_remove_root_bus(bus); +err_root_bus: + pci_free_resource_list(&res); +err_ioremap: + of_node_put(pcie->node); + return ret; +} + +static const struct of_device_id thunder_pcie_of_match[] = { + { .compatible = "cavium,thunder-pcie", }, + {}, +}; +MODULE_DEVICE_TABLE(of, thunder_pcie_of_match); + +static struct platform_driver thunder_pcie_driver = { + .driver = { + .name = "thunder-pcie", + .owner = THIS_MODULE, + .of_match_table = thunder_pcie_of_match, + }, + .probe = thunder_pcie_probe, +}; +module_platform_driver(thunder_pcie_driver); + +#ifdef CONFIG_ACPI + +static int +thunder_mmcfg_read_config(struct pci_mmcfg_region *cfg, unsigned int busnr, + unsigned int devfn, int reg, int len, u32 *value) +{ + struct thunder_pcie *pcie = cfg->data; + void __iomem *addr; + + if (!pcie->valid) { + /* Not support for now */ + pr_err("RC PEM not supported !!!\n"); + return PCIBIOS_DEVICE_NOT_FOUND; + } + + addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg); + + switch (len) { + case 1: + *value = readb(addr); + break; + case 2: + *value = readw(addr); + break; + case 4: + *value = readl(addr); + break; + default: + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_mmcfg_write_config(struct pci_mmcfg_region *cfg, + unsigned int busnr, unsigned int devfn, int reg, int len, + u32 value) { + struct thunder_pcie *pcie = cfg->data; + void __iomem *addr; + + if (!pcie->valid) { + /* Not support for now */ + pr_err("RC PEM not supported !!!\n"); + return PCIBIOS_DEVICE_NOT_FOUND; + } + + addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg); + + switch (len) { + case 1: + writeb(value, addr); + break; + case 2: + writew(value, addr); + break; + case 4: + writel(value, addr); + break; + default: + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_acpi_mcfg_fixup(struct acpi_pci_root *root, + struct pci_mmcfg_region *cfg) +{ + struct thunder_pcie *pcie; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->dev = &root->device->dev; + + thunder_pcie_config(pcie, cfg->address); + + pcie->cfg_base = cfg->virt; + cfg->data = pcie; + cfg->read = thunder_mmcfg_read_config; + cfg->write = thunder_mmcfg_write_config; + + return 0; +} +DECLARE_ACPI_MCFG_FIXUP("CAVIUM", "THUNDERX", thunder_acpi_mcfg_fixup); +#endif + +MODULE_AUTHOR("Sunil Goutham"); +MODULE_DESCRIPTION("Cavium Thunder ECAM host controller driver"); +MODULE_LICENSE("GPL v2"); +
From: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com
In order to allow KVM to run on Thunder implementations, add the minimal support required.
Signed-off-by: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- arch/arm64/include/asm/cputype.h | 3 +++ arch/arm64/include/uapi/asm/kvm.h | 3 ++- arch/arm64/kvm/guest.c | 6 ++++++ arch/arm64/kvm/sys_regs_generic_v8.c | 2 ++ 4 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/cputype.h b/arch/arm64/include/asm/cputype.h index a84ec60..f603dcd 100644 --- a/arch/arm64/include/asm/cputype.h +++ b/arch/arm64/include/asm/cputype.h @@ -63,6 +63,7 @@ ((partnum) << MIDR_PARTNUM_SHIFT))
#define ARM_CPU_IMP_ARM 0x41 +#define ARM_CPU_IMP_CAVIUM 0x43 #define ARM_CPU_IMP_APM 0x50
#define ARM_CPU_PART_AEM_V8 0xD0F @@ -72,6 +73,8 @@
#define APM_CPU_PART_POTENZA 0x000
+#define ARM_CPU_PART_THUNDER 0x0A1 + #define ID_AA64MMFR0_BIGENDEL0_SHIFT 16 #define ID_AA64MMFR0_BIGENDEL0_MASK (0xf << ID_AA64MMFR0_BIGENDEL0_SHIFT) #define ID_AA64MMFR0_BIGENDEL0(mmfr0) \ diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h index d268320..6c4c556 100644 --- a/arch/arm64/include/uapi/asm/kvm.h +++ b/arch/arm64/include/uapi/asm/kvm.h @@ -59,8 +59,9 @@ struct kvm_regs { #define KVM_ARM_TARGET_CORTEX_A57 2 #define KVM_ARM_TARGET_XGENE_POTENZA 3 #define KVM_ARM_TARGET_CORTEX_A53 4 +#define KVM_ARM_TARGET_CAVIUM_THUNDER 5
-#define KVM_ARM_NUM_TARGETS 5 +#define KVM_ARM_NUM_TARGETS 6
/* KVM_ARM_SET_DEVICE_ADDR ioctl id encoding */ #define KVM_ARM_DEVICE_TYPE_SHIFT 0 diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c index 9535bd5..3751f37 100644 --- a/arch/arm64/kvm/guest.c +++ b/arch/arm64/kvm/guest.c @@ -291,6 +291,12 @@ int __attribute_const__ kvm_target_cpu(void) return KVM_ARM_TARGET_XGENE_POTENZA; }; break; + case ARM_CPU_IMP_CAVIUM: + switch (part_number) { + case ARM_CPU_PART_THUNDER: + return KVM_ARM_TARGET_CAVIUM_THUNDER; + }; + break; };
return -EINVAL; diff --git a/arch/arm64/kvm/sys_regs_generic_v8.c b/arch/arm64/kvm/sys_regs_generic_v8.c index 475fd29..0e48ee8 100644 --- a/arch/arm64/kvm/sys_regs_generic_v8.c +++ b/arch/arm64/kvm/sys_regs_generic_v8.c @@ -94,6 +94,8 @@ static int __init sys_reg_genericv8_init(void) &genericv8_target_table); kvm_register_target_sys_reg_table(KVM_ARM_TARGET_XGENE_POTENZA, &genericv8_target_table); + kvm_register_target_sys_reg_table(KVM_ARM_TARGET_CAVIUM_THUNDER, + &genericv8_target_table);
return 0; }
From: Thanneeru Srinivasulu tsrinivasulu@caviumnetworks.com
Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/net/ethernet/cavium/thunder/thunder_bgx.c | 3 +++ drivers/net/ethernet/cavium/thunder/thunder_bgx.h | 1 + 2 files changed, 4 insertions(+)
diff --git a/drivers/net/ethernet/cavium/thunder/thunder_bgx.c b/drivers/net/ethernet/cavium/thunder/thunder_bgx.c index 633ec05..6356323 100644 --- a/drivers/net/ethernet/cavium/thunder/thunder_bgx.c +++ b/drivers/net/ethernet/cavium/thunder/thunder_bgx.c @@ -866,6 +866,9 @@ static int bgx_probe(struct pci_dev *pdev, const struct pci_device_id *ent) char bgx_sel[5]; u8 lmac;
+ /*Load octeon mdio driver*/ + octeon_mdiobus_force_mod_depencency(); + bgx = devm_kzalloc(dev, sizeof(*bgx), GFP_KERNEL); if (!bgx) return -ENOMEM; diff --git a/drivers/net/ethernet/cavium/thunder/thunder_bgx.h b/drivers/net/ethernet/cavium/thunder/thunder_bgx.h index ba4f53b..c508114 100644 --- a/drivers/net/ethernet/cavium/thunder/thunder_bgx.h +++ b/drivers/net/ethernet/cavium/thunder/thunder_bgx.h @@ -180,6 +180,7 @@ enum MCAST_MODE { #define BCAST_ACCEPT 1 #define CAM_ACCEPT 1
+void octeon_mdiobus_force_mod_depencency(void); void bgx_add_dmac_addr(u64 dmac, int node, int bgx_idx, int lmac); unsigned bgx_get_map(int node); int bgx_get_lmac_count(int node, int bgx);
From: Tomasz Nowicki tomasz.nowicki@linaro.org
Signed-off-by: Tomasz Nowicki tomasz.nowicki@linaro.org Signed-off-by: Robert Richter rrichter@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/tty/n_tty.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index c9c27f6..b9dcc83 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -1701,6 +1701,7 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, { struct n_tty_data *ldata = tty->disc_data; int room, n, rcvd = 0, overflow; + size_t *read_tail_tmp = &ldata->read_tail;
down_read(&tty->termios_rwsem);
@@ -1718,7 +1719,7 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, * the consumer has loaded the data in read_buf up to the new * read_tail (so this producer will not overwrite unread data) */ - size_t tail = smp_load_acquire(&ldata->read_tail); + size_t tail = smp_load_acquire(read_tail_tmp);
room = N_TTY_BUF_SIZE - (ldata->read_head - tail); if (I_PARMRK(tty))
From: Radha Mohan Chintakuntla rchintakuntla@cavium.com
This patch modifies the mdio-octeon driver to work on both ThunderX and Octeon SoCs from Cavium Inc.
Signed-off-by: Sunil Goutham sgoutham@cavium.com Signed-off-by: Radha Mohan Chintakuntla rchintakuntla@cavium.com Signed-off-by: David Daney david.daney@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/net/phy/Kconfig | 9 ++-- drivers/net/phy/mdio-octeon.c | 122 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 20 deletions(-)
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index cf18940..0d6af19 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -145,13 +145,14 @@ config MDIO_GPIO will be called mdio-gpio.
config MDIO_OCTEON - tristate "Support for MDIO buses on Octeon SOCs" - depends on CAVIUM_OCTEON_SOC + tristate "Support for MDIO buses on Octeon and ThunderX SOCs" + depends on 64BIT default y help
- This module provides a driver for the Octeon MDIO busses. - It is required by the Octeon Ethernet device drivers. + This module provides a driver for the Octeon and ThunderX MDIO + busses. It is required by the Octeon and ThunderX ethernet device + drivers.
If in doubt, say Y.
diff --git a/drivers/net/phy/mdio-octeon.c b/drivers/net/phy/mdio-octeon.c index c838ad6..507aade 100644 --- a/drivers/net/phy/mdio-octeon.c +++ b/drivers/net/phy/mdio-octeon.c @@ -7,6 +7,7 @@ */
#include <linux/platform_device.h> +#include <linux/of_address.h> #include <linux/of_mdio.h> #include <linux/delay.h> #include <linux/module.h> @@ -14,11 +15,12 @@ #include <linux/phy.h> #include <linux/io.h>
+#ifdef CONFIG_CAVIUM_OCTEON_SOC #include <asm/octeon/octeon.h> -#include <asm/octeon/cvmx-smix-defs.h> +#endif
-#define DRV_VERSION "1.0" -#define DRV_DESCRIPTION "Cavium Networks Octeon SMI/MDIO driver" +#define DRV_VERSION "1.1" +#define DRV_DESCRIPTION "Cavium Networks Octeon/ThunderX SMI/MDIO driver"
#define SMI_CMD 0x0 #define SMI_WR_DAT 0x8 @@ -26,6 +28,79 @@ #define SMI_CLK 0x18 #define SMI_EN 0x20
+#ifdef __BIG_ENDIAN_BITFIELD +#define OCT_MDIO_BITFIELD_FIELD(field, more) \ + field; \ + more + +#else +#define OCT_MDIO_BITFIELD_FIELD(field, more) \ + more \ + field; + +#endif + +union cvmx_smix_clk { + uint64_t u64; + struct cvmx_smix_clk_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_25_63:39, + OCT_MDIO_BITFIELD_FIELD(u64 mode:1, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_21_23:3, + OCT_MDIO_BITFIELD_FIELD(u64 sample_hi:5, + OCT_MDIO_BITFIELD_FIELD(u64 sample_mode:1, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_14_14:1, + OCT_MDIO_BITFIELD_FIELD(u64 clk_idle:1, + OCT_MDIO_BITFIELD_FIELD(u64 preamble:1, + OCT_MDIO_BITFIELD_FIELD(u64 sample:4, + OCT_MDIO_BITFIELD_FIELD(u64 phase:8, + ;)))))))))) + } s; +}; + +union cvmx_smix_cmd { + uint64_t u64; + struct cvmx_smix_cmd_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 phy_op:2, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_13_15:3, + OCT_MDIO_BITFIELD_FIELD(u64 phy_adr:5, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_5_7:3, + OCT_MDIO_BITFIELD_FIELD(u64 reg_adr:5, + ;)))))) + } s; +}; + +union cvmx_smix_en { + uint64_t u64; + struct cvmx_smix_en_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_1_63:63, + OCT_MDIO_BITFIELD_FIELD(u64 en:1, + ;)) + } s; +}; + +union cvmx_smix_rd_dat { + uint64_t u64; + struct cvmx_smix_rd_dat_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 pending:1, + OCT_MDIO_BITFIELD_FIELD(u64 val:1, + OCT_MDIO_BITFIELD_FIELD(u64 dat:16, + ;)))) + } s; +}; + +union cvmx_smix_wr_dat { + uint64_t u64; + struct cvmx_smix_wr_dat_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 pending:1, + OCT_MDIO_BITFIELD_FIELD(u64 val:1, + OCT_MDIO_BITFIELD_FIELD(u64 dat:16, + ;)))) + } s; +}; + enum octeon_mdiobus_mode { UNINIT = 0, C22, @@ -41,6 +116,21 @@ struct octeon_mdiobus { int phy_irq[PHY_MAX_ADDR]; };
+#ifdef CONFIG_CAVIUM_OCTEON_SOC +static void oct_mdio_writeq(u64 val, u64 addr) +{ + cvmx_write_csr(addr, val); +} + +static u64 oct_mdio_readq(u64 addr) +{ + return cvmx_read_csr(addr); +} +#else +#define oct_mdio_writeq(val, addr) writeq_relaxed(val, (void *)addr) +#define oct_mdio_readq(addr) readq_relaxed((void *)addr) +#endif + static void octeon_mdiobus_set_mode(struct octeon_mdiobus *p, enum octeon_mdiobus_mode m) { @@ -49,10 +139,10 @@ static void octeon_mdiobus_set_mode(struct octeon_mdiobus *p, if (m == p->mode) return;
- smi_clk.u64 = cvmx_read_csr(p->register_base + SMI_CLK); + smi_clk.u64 = oct_mdio_readq(p->register_base + SMI_CLK); smi_clk.s.mode = (m == C45) ? 1 : 0; smi_clk.s.preamble = 1; - cvmx_write_csr(p->register_base + SMI_CLK, smi_clk.u64); + oct_mdio_writeq(smi_clk.u64, p->register_base + SMI_CLK); p->mode = m; }
@@ -67,7 +157,7 @@ static int octeon_mdiobus_c45_addr(struct octeon_mdiobus *p,
smi_wr.u64 = 0; smi_wr.s.dat = regnum & 0xffff; - cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64); + oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT);
regnum = (regnum >> 16) & 0x1f;
@@ -75,14 +165,14 @@ static int octeon_mdiobus_c45_addr(struct octeon_mdiobus *p, smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_45_ADDRESS */ smi_cmd.s.phy_adr = phy_id; smi_cmd.s.reg_adr = regnum; - cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD);
do { /* Wait 1000 clocks so we don't saturate the RSL bus * doing reads. */ __delay(1000); - smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT); + smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); } while (smi_wr.s.pending && --timeout);
if (timeout <= 0) @@ -114,14 +204,14 @@ static int octeon_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum) smi_cmd.s.phy_op = op; smi_cmd.s.phy_adr = phy_id; smi_cmd.s.reg_adr = regnum; - cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD);
do { /* Wait 1000 clocks so we don't saturate the RSL bus * doing reads. */ __delay(1000); - smi_rd.u64 = cvmx_read_csr(p->register_base + SMI_RD_DAT); + smi_rd.u64 = oct_mdio_readq(p->register_base + SMI_RD_DAT); } while (smi_rd.s.pending && --timeout);
if (smi_rd.s.val) @@ -153,20 +243,20 @@ static int octeon_mdiobus_write(struct mii_bus *bus, int phy_id,
smi_wr.u64 = 0; smi_wr.s.dat = val; - cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64); + oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT);
smi_cmd.u64 = 0; smi_cmd.s.phy_op = op; smi_cmd.s.phy_adr = phy_id; smi_cmd.s.reg_adr = regnum; - cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD);
do { /* Wait 1000 clocks so we don't saturate the RSL bus * doing reads. */ __delay(1000); - smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT); + smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); } while (smi_wr.s.pending && --timeout);
if (timeout <= 0) @@ -210,7 +300,7 @@ static int octeon_mdiobus_probe(struct platform_device *pdev)
smi_en.u64 = 0; smi_en.s.en = 1; - cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN);
bus->mii_bus->priv = bus; bus->mii_bus->irq = bus->phy_irq; @@ -234,7 +324,7 @@ fail_register: mdiobus_free(bus->mii_bus); fail: smi_en.u64 = 0; - cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); return err; }
@@ -248,7 +338,7 @@ static int octeon_mdiobus_remove(struct platform_device *pdev) mdiobus_unregister(bus->mii_bus); mdiobus_free(bus->mii_bus); smi_en.u64 = 0; - cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); return 0; }
From: Radha Mohan Chintakuntla rchintakuntla@cavium.com
This patch fixes a possible crash in the octeon_mdiobus_probe function if the return values are not handled properly.
Signed-off-by: Radha Mohan Chintakuntla rchintakuntla@cavium.com Signed-off-by: Tomasz Nowicki tomasz.nowicki@linaro.org Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/net/phy/mdio-octeon.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/drivers/net/phy/mdio-octeon.c b/drivers/net/phy/mdio-octeon.c index 507aade..428ae75 100644 --- a/drivers/net/phy/mdio-octeon.c +++ b/drivers/net/phy/mdio-octeon.c @@ -277,24 +277,28 @@ static int octeon_mdiobus_probe(struct platform_device *pdev) return -ENOMEM;
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res_mem == NULL) { dev_err(&pdev->dev, "found no memory resource\n"); - err = -ENXIO; - goto fail; + return -ENXIO; } + bus->mdio_phys = res_mem->start; bus->regsize = resource_size(res_mem); + if (!devm_request_mem_region(&pdev->dev, bus->mdio_phys, bus->regsize, res_mem->name)) { dev_err(&pdev->dev, "request_mem_region failed\n"); - goto fail; + return -ENXIO; } + bus->register_base = (u64)devm_ioremap(&pdev->dev, bus->mdio_phys, bus->regsize); + if (!bus->register_base) { + dev_err(&pdev->dev, "dev_ioremap failed\n"); + return -ENOMEM; + }
bus->mii_bus = mdiobus_alloc(); - if (!bus->mii_bus) goto fail;
From: Radha Mohan Chintakuntla rchintakuntla@cavium.com
The CONFIG_MDIO_OCTEON is required so that the ThunderX NIC driver can talk to the PHY drivers.
Signed-off-by: Radha Mohan Chintakuntla rchintakuntla@cavium.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/net/ethernet/cavium/Kconfig | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/drivers/net/ethernet/cavium/Kconfig b/drivers/net/ethernet/cavium/Kconfig index c4d6bbe..b756a00 100644 --- a/drivers/net/ethernet/cavium/Kconfig +++ b/drivers/net/ethernet/cavium/Kconfig @@ -37,6 +37,8 @@ config THUNDER_NIC_BGX tristate "Thunder MAC interface driver (BGX)" depends on 64BIT default ARCH_THUNDER + select PHYLIB + select MDIO_OCTEON ---help--- This driver supports programming and controlling of MAC interface from NIC physical function driver.
From: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com
Adding numa support for arm64 based platforms. The memory to node mapping is derived either from dt and acpi based memory tables.
Signed-off-by: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com
Conflicts: arch/arm64/mm/init.c --- arch/arm64/Kconfig | 32 +++ arch/arm64/include/asm/mmzone.h | 32 +++ arch/arm64/include/asm/numa.h | 43 ++++ arch/arm64/kernel/setup.c | 8 + arch/arm64/kernel/smp.c | 2 + arch/arm64/mm/Makefile | 1 + arch/arm64/mm/init.c | 34 ++- arch/arm64/mm/numa.c | 522 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 668 insertions(+), 6 deletions(-) create mode 100644 arch/arm64/include/asm/mmzone.h create mode 100644 arch/arm64/include/asm/numa.h create mode 100644 arch/arm64/mm/numa.c
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 2c542d5..7302706 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -72,6 +72,7 @@ config ARM64 select HAVE_PERF_USER_STACK_DUMP select HAVE_RCU_TABLE_FREE select HAVE_SYSCALL_TRACEPOINTS + select HAVE_MEMBLOCK_NODE_MAP if NUMA select IRQ_DOMAIN select IRQ_FORCED_THREADING select MODULES_USE_ELF_RELA @@ -543,6 +544,37 @@ config HOTPLUG_CPU Say Y here to experiment with turning CPUs off and on. CPUs can be controlled through /sys/devices/system/cpu.
+# Common NUMA Features +config NUMA + bool "Numa Memory Allocation and Scheduler Support" + depends on SMP + ---help--- + Enable NUMA (Non Uniform Memory Access) support. + + The kernel will try to allocate memory used by a CPU on the + local memory controller of the CPU and add some more + NUMA awareness to the kernel. + +config ARM64_DT_NUMA + def_bool n + prompt "DT NUMA detection" + ---help--- + Enable DT based numa. + +config NODES_SHIFT + int "Maximum NUMA Nodes (as a power of 2)" + range 1 10 + default "2" + depends on NEED_MULTIPLE_NODES + ---help--- + Specify the maximum number of NUMA Nodes available on the target + system. Increases memory reserved to accommodate various tables. + +config USE_PERCPU_NUMA_NODE_ID + def_bool y + depends on NUMA + + source kernel/Kconfig.preempt
config UP_LATE_INIT diff --git a/arch/arm64/include/asm/mmzone.h b/arch/arm64/include/asm/mmzone.h new file mode 100644 index 0000000..d27ee66 --- /dev/null +++ b/arch/arm64/include/asm/mmzone.h @@ -0,0 +1,32 @@ +#ifndef __ASM_ARM64_MMZONE_H_ +#define __ASM_ARM64_MMZONE_H_ + +#ifdef CONFIG_NUMA + +#include <linux/mmdebug.h> +#include <asm/smp.h> +#include <linux/types.h> +#include <asm/numa.h> + +extern struct pglist_data *node_data[]; + +#define NODE_DATA(nid) (node_data[nid]) + + +struct numa_memblk { + u64 start; + u64 end; + int nid; +}; + +struct numa_meminfo { + int nr_blks; + struct numa_memblk blk[NR_NODE_MEMBLKS]; +}; + +void __init numa_remove_memblk_from(int idx, struct numa_meminfo *mi); +int __init numa_cleanup_meminfo(struct numa_meminfo *mi); +void __init numa_reset_distance(void); + +#endif /* CONFIG_NUMA */ +#endif /* __ASM_ARM64_MMZONE_H_ */ diff --git a/arch/arm64/include/asm/numa.h b/arch/arm64/include/asm/numa.h new file mode 100644 index 0000000..8be5cb1 --- /dev/null +++ b/arch/arm64/include/asm/numa.h @@ -0,0 +1,43 @@ +#ifndef _ASM_ARM64_NUMA_H +#define _ASM_ARM64_NUMA_H + +#include <linux/nodemask.h> +#include <asm/topology.h> + +#ifdef CONFIG_NUMA + +#define NR_NODE_MEMBLKS (MAX_NUMNODES * 2) +#define ZONE_ALIGN (1UL << (MAX_ORDER + PAGE_SHIFT)) + +/* currently, arm64 implements flat NUMA topology */ +#define parent_node(node) (node) + +/* dummy definitions for pci functions */ +#define pcibus_to_node(node) 0 +#define cpumask_of_pcibus(bus) 0 + +struct __node_cpu_hwid { + u32 node_id; /* logical node containing this CPU */ + u64 cpu_hwid; /* MPIDR for this CPU */ +}; + +const struct cpumask *cpumask_of_node(int node); +/* Mappings between node number and cpus on that node. */ +extern cpumask_var_t node_to_cpumask_map[MAX_NUMNODES]; + +void __init arm64_numa_init(void); +int __init numa_add_memblk(u32 nodeid, u64 start, u64 end); +void numa_store_cpu_info(int cpu); +void numa_set_node(int cpu, int node); +void numa_clear_node(int cpu); +void numa_add_cpu(int cpu); +void numa_remove_cpu(int cpu); +void __init numa_set_distance(int from, int to, int distance); +int dt_get_cpu_node_id(int cpu); +int __init arm64_dt_numa_init(void); +#else /* CONFIG_NUMA */ +static inline void arm64_numa_init(void); +static inline void numa_store_cpu_info(int cpu) { } +static inline void arm64_numa_init(void) { } +#endif /* CONFIG_NUMA */ +#endif /* _ASM_ARM64_NUMA_H */ diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index f3067d4..de22045 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -437,6 +437,9 @@ static int __init topology_init(void) { int i;
+ for_each_online_node(i) + register_one_node(i); + for_each_possible_cpu(i) { struct cpu *cpu = &per_cpu(cpu_data.cpu, i); cpu->hotpluggable = 1; @@ -509,7 +512,12 @@ static int c_show(struct seq_file *m, void *v) * "processor". Give glibc what it expects. */ #ifdef CONFIG_SMP + if (IS_ENABLED(CONFIG_NUMA)) { + seq_printf(m, "processor\t: %d", i); + seq_printf(m, " [nid: %d]\n", cpu_to_node(i)); + } else { seq_printf(m, "processor\t: %d\n", i); + } #endif
/* diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 50fb469..ae3e02c 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -52,6 +52,7 @@ #include <asm/sections.h> #include <asm/tlbflush.h> #include <asm/ptrace.h> +#include <asm/numa.h>
#define CREATE_TRACE_POINTS #include <trace/events/ipi.h> @@ -124,6 +125,7 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle) static void smp_store_cpu_info(unsigned int cpuid) { store_cpu_topology(cpuid); + numa_store_cpu_info(cpuid); }
/* diff --git a/arch/arm64/mm/Makefile b/arch/arm64/mm/Makefile index 773d37a..bb92d41 100644 --- a/arch/arm64/mm/Makefile +++ b/arch/arm64/mm/Makefile @@ -4,3 +4,4 @@ obj-y := dma-mapping.o extable.o fault.o init.o \ context.o proc.o pageattr.o obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o obj-$(CONFIG_ARM64_PTDUMP) += dump.o +obj-$(CONFIG_NUMA) += numa.o diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c index ad87ce8..dc28705 100644 --- a/arch/arm64/mm/init.c +++ b/arch/arm64/mm/init.c @@ -42,6 +42,7 @@ #include <asm/sizes.h> #include <asm/tlb.h> #include <asm/alternative.h> +#include <asm/numa.h>
#include "mm.h"
@@ -77,6 +78,20 @@ static phys_addr_t max_zone_dma_phys(void) return min(offset + (1ULL << 32), memblock_end_of_DRAM()); }
+#ifdef CONFIG_NUMA +static void __init zone_sizes_init(unsigned long min, unsigned long max) +{ + unsigned long max_zone_pfns[MAX_NR_ZONES]; + + memset(max_zone_pfns, 0, sizeof(max_zone_pfns)); + if (IS_ENABLED(CONFIG_ZONE_DMA)) + max_zone_pfns[ZONE_DMA] = PFN_DOWN(max_zone_dma_phys()); + max_zone_pfns[ZONE_NORMAL] = max; + + free_area_init_nodes(max_zone_pfns); +} + +#else static void __init zone_sizes_init(unsigned long min, unsigned long max) { struct memblock_region *reg; @@ -115,6 +130,7 @@ static void __init zone_sizes_init(unsigned long min, unsigned long max)
free_area_init_node(0, zone_size, min, zhole_size); } +#endif /* CONFIG_NUMA */
#ifdef CONFIG_HAVE_ARCH_PFN_VALID int pfn_valid(unsigned long pfn) @@ -132,10 +148,15 @@ static void arm64_memory_present(void) static void arm64_memory_present(void) { struct memblock_region *reg; + int nid = 0;
- for_each_memblock(memory, reg) - memory_present(0, memblock_region_memory_base_pfn(reg), - memblock_region_memory_end_pfn(reg)); + for_each_memblock(memory, reg) { +#ifdef CONFIG_NUMA + nid = reg->nid; +#endif + memory_present(nid, memblock_region_memory_base_pfn(reg), + memblock_region_memory_end_pfn(reg)); + } } #endif
@@ -192,6 +213,10 @@ void __init bootmem_init(void)
early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);
+ high_memory = __va((max << PAGE_SHIFT) - 1) + 1; + max_pfn = max_low_pfn = max; + + arm64_numa_init(); /* * Sparsemem tries to allocate bootmem in memory_present(), so must be * done after the fixed reservations. @@ -200,9 +225,6 @@ void __init bootmem_init(void)
sparse_init(); zone_sizes_init(min, max); - - high_memory = __va((max << PAGE_SHIFT) - 1) + 1; - max_pfn = max_low_pfn = max; }
#ifndef CONFIG_SPARSEMEM_VMEMMAP diff --git a/arch/arm64/mm/numa.c b/arch/arm64/mm/numa.c new file mode 100644 index 0000000..da1d301 --- /dev/null +++ b/arch/arm64/mm/numa.c @@ -0,0 +1,522 @@ +/* + * NUMA support, based on the x86 implementation. + * + * Copyright (C) 2015 Cavium Inc. + * Author: Ganapatrao Kulkarni gkulkarni@cavium.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/memblock.h> +#include <linux/mmzone.h> +#include <linux/ctype.h> +#include <linux/module.h> +#include <linux/nodemask.h> +#include <linux/sched.h> +#include <linux/topology.h> +#include <linux/of.h> +#include <asm/smp_plat.h> + +int __initdata numa_off; +nodemask_t numa_nodes_parsed __initdata; +static int numa_distance_cnt; +static u8 *numa_distance; + +struct pglist_data *node_data[MAX_NUMNODES] __read_mostly; +EXPORT_SYMBOL(node_data); + +static struct __node_cpu_hwid node_cpu_hwid[NR_CPUS]; +static struct numa_meminfo numa_meminfo; + +static __init int numa_setup(char *opt) +{ + if (!opt) + return -EINVAL; + if (!strncmp(opt, "off", 3)) { + pr_info("%s\n", "NUMA turned off"); + numa_off = 1; + } + return 0; +} +early_param("numa", numa_setup); + +cpumask_var_t node_to_cpumask_map[MAX_NUMNODES]; +EXPORT_SYMBOL(node_to_cpumask_map); + +/* + * Returns a pointer to the bitmask of CPUs on Node 'node'. + */ +const struct cpumask *cpumask_of_node(int node) +{ + if (node >= nr_node_ids) { + pr_warn("cpumask_of_node(%d): node > nr_node_ids(%d)\n", + node, nr_node_ids); + dump_stack(); + return cpu_none_mask; + } + if (node_to_cpumask_map[node] == NULL) { + pr_warn("cpumask_of_node(%d): no node_to_cpumask_map!\n", + node); + dump_stack(); + return cpu_online_mask; + } + return node_to_cpumask_map[node]; +} +EXPORT_SYMBOL(cpumask_of_node); + +void numa_clear_node(int cpu) +{ + node_cpu_hwid[cpu].node_id = NUMA_NO_NODE; +} + +/* + * Allocate node_to_cpumask_map based on number of available nodes + * Requires node_possible_map to be valid. + * + * Note: cpumask_of_node() is not valid until after this is done. + * (Use CONFIG_DEBUG_PER_CPU_MAPS to check this.) + */ +void __init setup_node_to_cpumask_map(void) +{ + unsigned int node; + + /* setup nr_node_ids if not done yet */ + if (nr_node_ids == MAX_NUMNODES) + setup_nr_node_ids(); + + /* allocate the map */ + for (node = 0; node < nr_node_ids; node++) + alloc_bootmem_cpumask_var(&node_to_cpumask_map[node]); + + /* cpumask_of_node() will now work */ + pr_debug("Node to cpumask map for %d nodes\n", nr_node_ids); +} + +/* + * Set the cpu to node and mem mapping + */ +void numa_store_cpu_info(int cpu) +{ + if (IS_ENABLED(CONFIG_ARM64_DT_NUMA)) + node_cpu_hwid[cpu].node_id = dt_get_cpu_node_id(cpu); + else + node_cpu_hwid[cpu].node_id = 0; + + /* mapping of MPIDR/hwid, node and logical cpu id */ + node_cpu_hwid[cpu].cpu_hwid = cpu_logical_map(cpu); + cpumask_set_cpu(cpu, node_to_cpumask_map[node_cpu_hwid[cpu].node_id]); + set_numa_node(node_cpu_hwid[cpu].node_id); + set_numa_mem(local_memory_node(node_cpu_hwid[cpu].node_id)); +} + +/** + * numa_add_memblk_to - Add one numa_memblk to a numa_meminfo + */ + +static int __init numa_add_memblk_to(int nid, u64 start, u64 end, + struct numa_meminfo *mi) +{ + /* ignore zero length blks */ + if (start == end) + return 0; + + /* whine about and ignore invalid blks */ + if (start > end || nid < 0 || nid >= MAX_NUMNODES) { + pr_warn("NUMA: Warning: invalid memblk node %d [mem %#010Lx-%#010Lx]\n", + nid, start, end - 1); + return 0; + } + + if (mi->nr_blks >= NR_NODE_MEMBLKS) { + pr_err("NUMA: too many memblk ranges\n"); + return -EINVAL; + } + + pr_info("NUMA: Adding memblock %d [0x%llx - 0x%llx] on node %d\n", + mi->nr_blks, start, end, nid); + mi->blk[mi->nr_blks].start = start; + mi->blk[mi->nr_blks].end = end; + mi->blk[mi->nr_blks].nid = nid; + mi->nr_blks++; + return 0; +} + +/** + * numa_add_memblk - Add one numa_memblk to numa_meminfo + * @nid: NUMA node ID of the new memblk + * @start: Start address of the new memblk + * @end: End address of the new memblk + * + * Add a new memblk to the default numa_meminfo. + * + * RETURNS: + * 0 on success, -errno on failure. + */ +#define MAX_PHYS_ADDR ((phys_addr_t)~0) + +int __init numa_add_memblk(u32 nid, u64 base, u64 size) +{ + const u64 phys_offset = __pa(PAGE_OFFSET); + + base &= PAGE_MASK; + size &= PAGE_MASK; + + if (base > MAX_PHYS_ADDR) { + pr_warn("NUMA: Ignoring memory block 0x%llx - 0x%llx\n", + base, base + size); + return -ENOMEM; + } + + if (base + size > MAX_PHYS_ADDR) { + pr_info("NUMA: Ignoring memory range 0x%lx - 0x%llx\n", + ULONG_MAX, base + size); + size = MAX_PHYS_ADDR - base; + } + + if (base + size < phys_offset) { + pr_warn("NUMA: Ignoring memory block 0x%llx - 0x%llx\n", + base, base + size); + return -ENOMEM; + } + if (base < phys_offset) { + pr_info("NUMA: Ignoring memory range 0x%llx - 0x%llx\n", + base, phys_offset); + size -= phys_offset - base; + base = phys_offset; + } + + return numa_add_memblk_to(nid, base, base+size, &numa_meminfo); +} +EXPORT_SYMBOL(numa_add_memblk); + +/* Initialize NODE_DATA for a node on the local memory */ +static void __init setup_node_data(int nid, u64 start, u64 end) +{ + const size_t nd_size = roundup(sizeof(pg_data_t), PAGE_SIZE); + u64 nd_pa; + void *nd; + int tnid; + + start = roundup(start, ZONE_ALIGN); + + pr_info("Initmem setup node %d [mem %#010Lx-%#010Lx]\n", + nid, start, end - 1); + + /* + * Allocate node data. Try node-local memory and then any node. + */ + nd_pa = memblock_alloc_nid(nd_size, SMP_CACHE_BYTES, nid); + if (!nd_pa) { + nd_pa = __memblock_alloc_base(nd_size, SMP_CACHE_BYTES, + MEMBLOCK_ALLOC_ACCESSIBLE); + if (!nd_pa) { + pr_err("Cannot find %zu bytes in node %d\n", + nd_size, nid); + return; + } + } + nd = __va(nd_pa); + + /* report and initialize */ + pr_info(" NODE_DATA [mem %#010Lx-%#010Lx]\n", + nd_pa, nd_pa + nd_size - 1); + tnid = early_pfn_to_nid(nd_pa >> PAGE_SHIFT); + if (tnid != nid) + pr_info(" NODE_DATA(%d) on node %d\n", nid, tnid); + + node_data[nid] = nd; + memset(NODE_DATA(nid), 0, sizeof(pg_data_t)); + NODE_DATA(nid)->node_id = nid; + NODE_DATA(nid)->node_start_pfn = start >> PAGE_SHIFT; + NODE_DATA(nid)->node_spanned_pages = (end - start) >> PAGE_SHIFT; + + node_set_online(nid); +} + +/* + * Set nodes, which have memory in @mi, in *@nodemask. + */ +static void __init numa_nodemask_from_meminfo(nodemask_t *nodemask, + const struct numa_meminfo *mi) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mi->blk); i++) + if (mi->blk[i].start != mi->blk[i].end && + mi->blk[i].nid != NUMA_NO_NODE) + node_set(mi->blk[i].nid, *nodemask); +} + +/* + * Sanity check to catch more bad NUMA configurations (they are amazingly + * common). Make sure the nodes cover all memory. + */ +static bool __init numa_meminfo_cover_memory(const struct numa_meminfo *mi) +{ + u64 numaram, totalram; + int i; + + numaram = 0; + for (i = 0; i < mi->nr_blks; i++) { + u64 s = mi->blk[i].start >> PAGE_SHIFT; + u64 e = mi->blk[i].end >> PAGE_SHIFT; + + numaram += e - s; + numaram -= __absent_pages_in_range(mi->blk[i].nid, s, e); + if ((s64)numaram < 0) + numaram = 0; + } + + totalram = max_pfn - absent_pages_in_range(0, max_pfn); + + /* We seem to lose 3 pages somewhere. Allow 1M of slack. */ + if ((s64)(totalram - numaram) >= (1 << (20 - PAGE_SHIFT))) { + pr_err("NUMA: nodes only cover %lluMB of your %lluMB Total RAM. Not used.\n", + (numaram << PAGE_SHIFT) >> 20, + (totalram << PAGE_SHIFT) >> 20); + return false; + } + return true; +} + +/** + * numa_reset_distance - Reset NUMA distance table + * + * The current table is freed. The next numa_set_distance() call will + * create a new one. + */ +void __init numa_reset_distance(void) +{ + size_t size = numa_distance_cnt * numa_distance_cnt * + sizeof(numa_distance[0]); + + /* numa_distance could be 1LU marking allocation failure, test cnt */ + if (numa_distance_cnt) + memblock_free(__pa(numa_distance), size); + numa_distance_cnt = 0; + numa_distance = NULL; /* enable table creation */ +} + +static int __init numa_alloc_distance(void) +{ + nodemask_t nodes_parsed; + size_t size; + int i, j, cnt = 0; + u64 phys; + + /* size the new table and allocate it */ + nodes_parsed = numa_nodes_parsed; + numa_nodemask_from_meminfo(&nodes_parsed, &numa_meminfo); + + for_each_node_mask(i, nodes_parsed) + cnt = i; + cnt++; + size = cnt * cnt * sizeof(numa_distance[0]); + + phys = memblock_find_in_range(0, PFN_PHYS(max_pfn), + size, PAGE_SIZE); + if (!phys) { + pr_warning("NUMA: Warning: can't allocate distance table!\n"); + /* don't retry until explicitly reset */ + numa_distance = (void *)1LU; + return -ENOMEM; + } + memblock_reserve(phys, size); + + numa_distance = __va(phys); + numa_distance_cnt = cnt; + + /* fill with the default distances */ + for (i = 0; i < cnt; i++) + for (j = 0; j < cnt; j++) + numa_distance[i * cnt + j] = i == j ? + LOCAL_DISTANCE : REMOTE_DISTANCE; + pr_debug("NUMA: Initialized distance table, cnt=%d\n", cnt); + + return 0; +} + +/** + * numa_set_distance - Set NUMA distance from one NUMA to another + * @from: the 'from' node to set distance + * @to: the 'to' node to set distance + * @distance: NUMA distance + * + * Set the distance from node @from to @to to @distance. If distance table + * doesn't exist, one which is large enough to accommodate all the currently + * known nodes will be created. + * + * If such table cannot be allocated, a warning is printed and further + * calls are ignored until the distance table is reset with + * numa_reset_distance(). + * + * If @from or @to is higher than the highest known node or lower than zero + * at the time of table creation or @distance doesn't make sense, the call + * is ignored. + * This is to allow simplification of specific NUMA config implementations. + */ +void __init numa_set_distance(int from, int to, int distance) +{ + if (!numa_distance && numa_alloc_distance() < 0) + return; + + if (from >= numa_distance_cnt || to >= numa_distance_cnt || + from < 0 || to < 0) { + pr_warn_once("NUMA: Warning: node ids are out of bound, from=%d to=%d distance=%d\n", + from, to, distance); + return; + } + + if ((u8)distance != distance || + (from == to && distance != LOCAL_DISTANCE)) { + pr_warn_once("NUMA: Warning: invalid distance parameter, from=%d to=%d distance=%d\n", + from, to, distance); + return; + } + + numa_distance[from * numa_distance_cnt + to] = distance; +} +EXPORT_SYMBOL(numa_set_distance); + +int __node_distance(int from, int to) +{ + if (from >= numa_distance_cnt || to >= numa_distance_cnt) + return from == to ? LOCAL_DISTANCE : REMOTE_DISTANCE; + return numa_distance[from * numa_distance_cnt + to]; +} +EXPORT_SYMBOL(__node_distance); + +static int __init numa_register_memblks(struct numa_meminfo *mi) +{ + unsigned long uninitialized_var(pfn_align); + int i, nid; + + /* Account for nodes with cpus and no memory */ + node_possible_map = numa_nodes_parsed; + numa_nodemask_from_meminfo(&node_possible_map, mi); + if (WARN_ON(nodes_empty(node_possible_map))) + return -EINVAL; + + for (i = 0; i < mi->nr_blks; i++) { + struct numa_memblk *mb = &mi->blk[i]; + + memblock_set_node(mb->start, mb->end - mb->start, + &memblock.memory, mb->nid); + } + + /* + * If sections array is gonna be used for pfn -> nid mapping, check + * whether its granularity is fine enough. + */ +#ifdef NODE_NOT_IN_PAGE_FLAGS + pfn_align = node_map_pfn_alignment(); + if (pfn_align && pfn_align < PAGES_PER_SECTION) { + pr_warn("Node alignment %lluMB < min %lluMB, rejecting NUMA config\n", + PFN_PHYS(pfn_align) >> 20, + PFN_PHYS(PAGES_PER_SECTION) >> 20); + return -EINVAL; + } +#endif + if (!numa_meminfo_cover_memory(mi)) + return -EINVAL; + + /* Finally register nodes. */ + for_each_node_mask(nid, node_possible_map) { + u64 start = PFN_PHYS(max_pfn); + u64 end = 0; + + for (i = 0; i < mi->nr_blks; i++) { + if (nid != mi->blk[i].nid) + continue; + start = min(mi->blk[i].start, start); + end = max(mi->blk[i].end, end); + } + + if (start < end) + setup_node_data(nid, start, end); + } + + /* Dump memblock with node info and return. */ + memblock_dump_all(); + return 0; +} + +static int __init numa_init(int (*init_func)(void)) +{ + int ret, i; + + nodes_clear(numa_nodes_parsed); + nodes_clear(node_possible_map); + nodes_clear(node_online_map); + + ret = init_func(); + if (ret < 0) + return ret; + + ret = numa_register_memblks(&numa_meminfo); + if (ret < 0) + return ret; + + for (i = 0; i < nr_cpu_ids; i++) + numa_clear_node(i); + + setup_node_to_cpumask_map(); + return 0; +} + +/** + * dummy_numa_init - Fallback dummy NUMA init + * + * Used if there's no underlying NUMA architecture, NUMA initialization + * fails, or NUMA is disabled on the command line. + * + * Must online at least one node and add memory blocks that cover all + * allowed memory. This function must not fail. + */ +static int __init dummy_numa_init(void) +{ + pr_info("%s\n", "No NUMA configuration found"); + pr_info("Faking a node at [mem %#018Lx-%#018Lx]\n", + 0LLU, PFN_PHYS(max_pfn) - 1); + node_set(0, numa_nodes_parsed); + numa_add_memblk(0, 0, PFN_PHYS(max_pfn)); + + return 0; +} + +/** + * arm64_numa_init - Initialize NUMA + * + * Try each configured NUMA initialization method until one succeeds. The + * last fallback is dummy single node config encomapssing whole memory and + * never fails. + */ +void __init arm64_numa_init(void) +{ + int (*init_func)(void) = NULL; + + if (IS_ENABLED(CONFIG_ARM64_DT_NUMA)) + init_func = arm64_dt_numa_init; + + if (!numa_off && init_func) { + if (!numa_init(init_func)) + return; + } + + numa_init(dummy_numa_init); +}
From: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com
DT bindings for numa map for memory, cores and IOs using arm,associativity device node property.
Signed-off-by: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- Documentation/devicetree/bindings/arm/numa.txt | 212 +++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/numa.txt
diff --git a/Documentation/devicetree/bindings/arm/numa.txt b/Documentation/devicetree/bindings/arm/numa.txt new file mode 100644 index 0000000..dc3ef86 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/numa.txt @@ -0,0 +1,212 @@ +============================================================================== +NUMA binding description. +============================================================================== + +============================================================================== +1 - Introduction +============================================================================== + +Systems employing a Non Uniform Memory Access (NUMA) architecture contain +collections of hardware resources including processors, memory, and I/O buses, +that comprise what is commonly known as a NUMA node. +Processor accesses to memory within the local NUMA node is generally faster +than processor accesses to memory outside of the local NUMA node. +DT defines interfaces that allow the platform to convey NUMA node +topology information to OS. + +============================================================================== +2 - arm,associativity +============================================================================== +The mapping is done using arm,associativity device property. +this property needs to be present in every device node which needs to to be +mapped to numa nodes. + +arm,associativity property is set of 32-bit integers which defines level of +topology and boundary in the system at which a significant difference in +performance can be measured between cross-device accesses within +a single location and those spanning multiple locations. +The first cell always contains the broadest subdivision within the system, +while the last cell enumerates the individual devices, such as an SMT thread +of a CPU, or a bus bridge within an SoC". + +ex: + /* board 0, socket 0, cluster 0, core 0 thread 0 */ + arm,associativity = <0 0 0 0 0>; + +============================================================================== +3 - arm,associativity-reference-points +============================================================================== +This property is a set of 32-bit integers, each representing an index into +the arm,associativity nodes. The first integer is the most significant +NUMA boundary and the following are progressively less significant boundaries. +There can be more than one level of NUMA. + +Ex: + arm,associativity-reference-points = <0 1>; + The board Id(index 0) used first to calculate the associativity (node + distance), then follows the socket id(index 1). + + arm,associativity-reference-points = <1 0>; + The socket Id(index 1) used first to calculate the associativity, + then follows the board id(index 0). + + arm,associativity-reference-points = <0>; + Only the board Id(index 0) used to calculate the associativity. + + arm,associativity-reference-points = <1>; + Only socket Id(index 1) used to calculate the associativity. + +============================================================================== +4 - Example dts +============================================================================== + +Example: 2 Node system consists of 2 boards and each board having one socket +and 8 core in each socket. + + arm,associativity-reference-points = <0>; + + memory@00c00000 { + device_type = "memory"; + reg = <0x0 0x00c00000 0x0 0x80000000>; + /* board 0, socket 0, no specific core */ + arm,associativity = <0 0 0xffff>; + }; + + memory@10000000000 { + device_type = "memory"; + reg = <0x100 0x00000000 0x0 0x80000000>; + /* board 1, socket 0, no specific core */ + arm,associativity = <1 0 0xffff>; + }; + + cpus { + #address-cells = <2>; + #size-cells = <0>; + + cpu@000 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x000>; + enable-method = "psci"; + /* board 0, socket 0, core 0*/ + arm,associativity = <0 0 0>; + }; + cpu@001 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x001>; + enable-method = "psci"; + arm,associativity = <0 0 1>; + }; + cpu@002 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x002>; + enable-method = "psci"; + arm,associativity = <0 0 2>; + }; + cpu@003 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x003>; + enable-method = "psci"; + arm,associativity = <0 0 3>; + }; + cpu@004 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x004>; + enable-method = "psci"; + arm,associativity = <0 0 4>; + }; + cpu@005 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x005>; + enable-method = "psci"; + arm,associativity = <0 0 5>; + }; + cpu@006 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x006>; + enable-method = "psci"; + arm,associativity = <0 0 6>; + }; + cpu@007 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x007>; + enable-method = "psci"; + arm,associativity = <0 0 7>; + }; + cpu@008 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x008>; + enable-method = "psci"; + /* board 1, socket 0, core 0*/ + arm,associativity = <1 0 0>; + }; + cpu@009 { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x009>; + enable-method = "psci"; + arm,associativity = <1 0 1>; + }; + cpu@00a { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x00a>; + enable-method = "psci"; + arm,associativity = <0 0 2>; + }; + cpu@00b { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x00b>; + enable-method = "psci"; + arm,associativity = <1 0 3>; + }; + cpu@00c { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x00c>; + enable-method = "psci"; + arm,associativity = <1 0 4>; + }; + cpu@00d { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x00d>; + enable-method = "psci"; + arm,associativity = <1 0 5>; + }; + cpu@00e { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x00e>; + enable-method = "psci"; + arm,associativity = <1 0 6>; + }; + cpu@00f { + device_type = "cpu"; + compatible = "arm,armv8"; + reg = <0x0 0x00f>; + enable-method = "psci"; + arm,associativity = <1 0 7>; + }; + }; + + pcie0: pcie0@0x8480,00000000 { + compatible = "arm,armv8"; + device_type = "pci"; + bus-range = <0 255>; + #size-cells = <2>; + #address-cells = <3>; + reg = <0x8480 0x00000000 0 0x10000000>; /* Configuration space */ + ranges = <0x03000000 0x8010 0x00000000 0x8010 0x00000000 0x70 0x00000000>; /* mem ranges */ + /* board 0, socket 0, pci bus 0*/ + arm,associativity = <0 0 0>; + };
From: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com
Adding dt node pasring for numa topology using property arm,associativity. arm,associativity property can be used to map memory, cpu and io devices to numa node.
Signed-off-by: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- arch/arm64/kernel/Makefile | 1 + arch/arm64/kernel/dt_numa.c | 302 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 arch/arm64/kernel/dt_numa.c
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 426d076..efb18f0 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -36,6 +36,7 @@ arm64-obj-$(CONFIG_EFI) += efi.o efi-stub.o efi-entry.o arm64-obj-$(CONFIG_PCI) += pci.o arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o arm64-obj-$(CONFIG_ACPI) += acpi.o +arm64-obj-$(CONFIG_ARM64_DT_NUMA) += dt_numa.o
obj-y += $(arm64-obj-y) vdso/ obj-m += $(arm64-obj-m) diff --git a/arch/arm64/kernel/dt_numa.c b/arch/arm64/kernel/dt_numa.c new file mode 100644 index 0000000..7c418e2 --- /dev/null +++ b/arch/arm64/kernel/dt_numa.c @@ -0,0 +1,302 @@ +/* + * DT NUMA Parsing support, based on the powerpc implementation. + * + * Copyright (C) 2015 Cavium Inc. + * Author: Ganapatrao Kulkarni gkulkarni@cavium.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/memblock.h> +#include <linux/ctype.h> +#include <linux/module.h> +#include <linux/nodemask.h> +#include <linux/sched.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <asm/smp_plat.h> + +#define MAX_DISTANCE_REF_POINTS 8 +static int min_common_depth; +static int distance_ref_points_depth; +static const __be32 *distance_ref_points; +static int distance_lookup_table[MAX_NUMNODES][MAX_DISTANCE_REF_POINTS]; +static int default_nid; +static int of_node_to_nid_single(struct device_node *device); +static struct device_node *of_cpu_to_node(int cpu); +extern nodemask_t numa_nodes_parsed __initdata; + +static void initialize_distance_lookup_table(int nid, + const __be32 *associativity) +{ + int i; + + for (i = 0; i < distance_ref_points_depth; i++) { + const __be32 *entry; + + entry = &associativity[be32_to_cpu(distance_ref_points[i])]; + distance_lookup_table[nid][i] = of_read_number(entry, 1); + } +} + +/* must hold reference to node during call */ +static const __be32 *of_get_associativity(struct device_node *dev) +{ + return of_get_property(dev, "arm,associativity", NULL); +} + +/* Returns nid in the range [0..MAX_NUMNODES-1], or -1 if no useful numa + * info is found. + */ +static int associativity_to_nid(const __be32 *associativity) +{ + int nid = NUMA_NO_NODE; + + if (min_common_depth == -1) + goto out; + + if (of_read_number(associativity, 1) >= min_common_depth) + nid = of_read_number(&associativity[min_common_depth], 1); + + /* set 0xffff as invalid node */ + if (nid == 0xffff || nid >= MAX_NUMNODES) + nid = NUMA_NO_NODE; + + if (nid != NUMA_NO_NODE) + initialize_distance_lookup_table(nid, associativity); +out: + return nid; +} + +/* Returns the nid associated with the given device tree node, + * or -1 if not found. + */ +static int of_node_to_nid_single(struct device_node *device) +{ + int nid = default_nid; + const __be32 *tmp; + + tmp = of_get_associativity(device); + if (tmp) + nid = associativity_to_nid(tmp); + return nid; +} + +/* Walk the device tree upwards, looking for an associativity id */ +int of_node_to_nid(struct device_node *device) +{ + struct device_node *tmp; + int nid = NUMA_NO_NODE; + + of_node_get(device); + while (device) { + nid = of_node_to_nid_single(device); + if (nid != NUMA_NO_NODE) + break; + + tmp = device; + device = of_get_parent(tmp); + of_node_put(tmp); + } + of_node_put(device); + + return nid; +} + +static int __init find_min_common_depth(unsigned long node) +{ + int depth; + const __be32 *numa_prop; + int nr_address_cells; + + /* + * This property is a set of 32-bit integers, each representing + * an index into the arm,associativity nodes. + * + * With form 1 affinity the first integer is the most significant + * NUMA boundary and the following are progressively less significant + * boundaries. There can be more than one level of NUMA. + */ + + distance_ref_points = of_get_flat_dt_prop(node, + "arm,associativity-reference-points", + &distance_ref_points_depth); + numa_prop = distance_ref_points; + + if (numa_prop) { + nr_address_cells = dt_mem_next_cell( + OF_ROOT_NODE_ADDR_CELLS_DEFAULT, &numa_prop); + nr_address_cells = dt_mem_next_cell( + OF_ROOT_NODE_ADDR_CELLS_DEFAULT, &numa_prop); + } + if (!distance_ref_points) { + pr_debug("NUMA: arm,associativity-reference-points not found.\n"); + goto err; + } + + distance_ref_points_depth /= sizeof(__be32); + + if (!distance_ref_points_depth) { + pr_err("NUMA: missing arm,associativity-reference-points\n"); + goto err; + } + depth = of_read_number(distance_ref_points, 1); + + /* + * Warn and cap if the hardware supports more than + * MAX_DISTANCE_REF_POINTS domains. + */ + if (distance_ref_points_depth > MAX_DISTANCE_REF_POINTS) { + pr_debug("NUMA: distance array capped at %d entries\n", MAX_DISTANCE_REF_POINTS); + distance_ref_points_depth = MAX_DISTANCE_REF_POINTS; + } + + return depth; +err: + return -1; +} + +int dt_get_cpu_node_id(int cpu) +{ + struct device_node *dn = NULL; + int nid = default_nid; + + dn = of_cpu_to_node(cpu); + if (dn) + nid = of_node_to_nid_single(dn); + return nid; +} + +static struct device_node *of_cpu_to_node(int cpu) +{ + struct device_node *dn = NULL; + + while ((dn = of_find_node_by_type(dn, "cpu"))) { + const u32 *cell; + u64 hwid; + + /* + * A cpu node with missing "reg" property is + * considered invalid to build a cpu_logical_map + * entry. + */ + cell = of_get_property(dn, "reg", NULL); + if (!cell) { + pr_err("%s: missing reg property\n", dn->full_name); + return NULL; + } + hwid = of_read_number(cell, of_n_addr_cells(dn)); + + if (cpu_logical_map(cpu) == hwid) + return dn; + } + return NULL; +} + +static int __init parse_memory_node(unsigned long node) +{ + const __be32 *reg, *endp, *associativity; + int length; + int nid = default_nid; + + associativity = of_get_flat_dt_prop(node, "arm,associativity", &length); + + if (associativity) + nid = associativity_to_nid(associativity); + + reg = of_get_flat_dt_prop(node, "reg", &length); + endp = reg + (length / sizeof(__be32)); + + while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) { + u64 base, size; + struct memblock_region *mblk; + + base = dt_mem_next_cell(dt_root_addr_cells, ®); + size = dt_mem_next_cell(dt_root_size_cells, ®); + pr_debug("NUMA-DT: base = %llx , node = %u\n", + base, nid); + for_each_memblock(memory, mblk) { + if (mblk->base == base) { + node_set(nid, numa_nodes_parsed); + numa_add_memblk(nid, mblk->base, mblk->size); + break; + } + } + } + + return 0; +} + +/** + * early_init_dt_scan_numa_map - parse memory node and map nid to memory range. + */ +int __init early_init_dt_scan_numa_map(unsigned long node, const char *uname, + int depth, void *data) +{ + const char *type = of_get_flat_dt_prop(node, "device_type", NULL); + + if (depth == 0) { + min_common_depth = find_min_common_depth(node); + if (min_common_depth < 0) + return min_common_depth; + pr_debug("NUMA associativity depth for CPU/Memory: %d\n", min_common_depth); + return 0; + } + + if (type) { + if (strcmp(type, "memory") == 0) + parse_memory_node(node); + } + return 0; +} + +int dt_get_node_distance(int a, int b) +{ + int i; + int distance = LOCAL_DISTANCE; + + for (i = 0; i < distance_ref_points_depth; i++) { + if (distance_lookup_table[a][i] == distance_lookup_table[b][i]) + break; + + /* Double the distance for each NUMA level */ + distance *= 2; + } + return distance; +} + +/* DT node mapping is done already early_init_dt_scan_memory */ +int __init arm64_dt_numa_init(void) +{ + int i; + u32 nodea, nodeb, distance, node_count = 0; + + of_scan_flat_dt(early_init_dt_scan_numa_map, NULL); + + for_each_node_mask(i, numa_nodes_parsed) + node_count = i; + node_count++; + + for (nodea = 0; nodea < node_count; nodea++) { + for (nodeb = 0; nodeb < node_count; nodeb++) { + distance = dt_get_node_distance(nodea, nodeb); + numa_set_distance(nodea, nodeb, distance); + } + } + return 0; +}
From: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com
Current efistub deletes the memory nodes from the dt before the dt file is passed to linux to avoid memblocks addition from dt early memory scan code.
This patch avoids the deletion and still makes sure the memory is not added from the dt code since, the memory info has to be used from efi tables.
Signed-off-by: Ganapatrao Kulkarni gkulkarni@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/firmware/efi/libstub/fdt.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c index ef5d764..ccdb24f 100644 --- a/drivers/firmware/efi/libstub/fdt.c +++ b/drivers/firmware/efi/libstub/fdt.c @@ -54,13 +54,15 @@ efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt, goto fdt_set_fail;
/* - * Delete any memory nodes present. We must delete nodes which - * early_init_dt_scan_memory may try to use. + * Disable any memory nodes present by adding (or overriding) their + * linux,usable-memory property to (u32)0, which is interpreted as + * a zero sized memory range by the DT code. */ prev = 0; for (;;) { const char *type; int len; + static const u32 usable_memory = 0;
node = fdt_next_node(fdt, prev, NULL); if (node < 0) @@ -68,8 +70,10 @@ efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
type = fdt_getprop(fdt, node, "device_type", &len); if (type && strncmp(type, "memory", len) == 0) { - fdt_del_node(fdt, node); - continue; + status = fdt_setprop(fdt, node, "linux,usable-memory", + &usable_memory, sizeof(u32)); + if (status) + goto fdt_set_fail; }
prev = node;
From: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com
Workaround for Thunder Pass1 Errata 23144. This fix is to avoid cross node ITS and redistrubutors(cpu) mapping. This is applicable only for Multi-node/numa configuration.
Signed-off-by: Ganapatrao Kulkarni ganapatrao.kulkarni@caviumnetworks.com Signed-off-by: Tirumalesh Chalamarla tchalamarla@caviumnetworks.com Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com
Conflicts: arch/arm64/Kconfig --- arch/arm64/Kconfig | 18 ++++++++++++------ drivers/irqchip/irq-gic-v3-its.c | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 7302706..5e450fd 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -246,20 +246,26 @@ config ARCH_THUNDER
menu "Cavium ThunderX errata"
-config THUNDERX_PASS1_ERRATA_23154 - bool "Cavium ThunderX erratum 23154" +config THUNDERX_PASS1_ERRATA_22375 + bool "Cavium ThunderX erratum 22375" depends on ARCH_THUNDER def_bool ARCH_THUNDER help - Enable workaround for erratum 23154. + Enable workaround for erratum 22375.
+config THUNDERX_PASS1_ERRATA_23144 + bool "Cavium ThunderX erratum 23144" + depends on ARCH_THUNDER && NUMA + def_bool ARCH_THUNDER + help + Enable workaround for erratum 23144.
-config THUNDERX_PASS1_ERRATA_22375 - bool "Cavium ThunderX erratum 22375" +config THUNDERX_PASS1_ERRATA_23154 + bool "Cavium ThunderX erratum 23154" depends on ARCH_THUNDER def_bool ARCH_THUNDER help - Enable workaround for erratum 22375. + Enable workaround for erratum 23154.
config THUNDERX_PASS1_ERRATA_24313 bool "Cavium ThunderX erratum 24313" diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 890aa3b..f098341 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -578,6 +578,16 @@ static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val, struct its_collection *target_col; u32 id = its_get_event_id(d);
+#if defined(CONFIG_NUMA) && defined(CONFIG_THUNDERX_PASS1_ERRATA_23144) + u32 node = (its_dev->its->phys_base >> 44) & 0x3; + if (!cpumask_intersects(mask_val, cpumask_of_node(node))) { + pr_debug("Affinity not Set to CPU %d, not belongs to same NODE %d\n", + cpu, node); + return IRQ_SET_MASK_OK; + } + cpu = cpumask_any_and(mask_val, cpumask_of_node(node)); +#endif + if (cpu >= nr_cpu_ids) return -EINVAL;
@@ -1053,6 +1063,11 @@ static void its_cpu_init_collection(void) list_for_each_entry(its, &its_nodes, entry) { u64 target;
+#if defined(CONFIG_NUMA) && defined(CONFIG_THUNDERX_PASS1_ERRATA_23144) + /* avoid cross node core and its mapping*/ + if (((its->phys_base >> 44) & 0x3) != MPIDR_AFFINITY_LEVEL(read_cpuid_mpidr(), 2)) + continue; +#endif /* * We now have to bind each collection to its target * redistributor. @@ -1146,8 +1161,13 @@ static struct its_device *its_create_device(struct its_node *its, u32 dev_id, list_add(&dev->entry, &its->its_device_list); raw_spin_unlock_irqrestore(&its->lock, flags);
+#if defined(CONFIG_NUMA) && defined(CONFIG_THUNDERX_PASS1_ERRATA_23144) + /* Bind the device to the first possible CPU of same NODE*/ + cpu = cpumask_first(cpumask_of_node((its->phys_base >> 44) & 0x3)); +#else /* Bind the device to the first possible CPU */ cpu = cpumask_first(cpu_online_mask); +#endif dev->collection = &its->collections[cpu];
/* Map device to its ITT */
This reverts commit e6316f74ce6f149abf0112e040b0b0f4de089d80. Build issue fix.
Signed-off-by: Vadim Lomovtsev Vadim.Lomovtsev@caviumnetworks.com --- drivers/pci/host/pcie-thunder.c | 88 ----------------------------------------- 1 file changed, 88 deletions(-)
diff --git a/drivers/pci/host/pcie-thunder.c b/drivers/pci/host/pcie-thunder.c index cbe4b44..7428401 100644 --- a/drivers/pci/host/pcie-thunder.c +++ b/drivers/pci/host/pcie-thunder.c @@ -329,94 +329,6 @@ static struct platform_driver thunder_pcie_driver = { }; module_platform_driver(thunder_pcie_driver);
-#ifdef CONFIG_ACPI - -static int -thunder_mmcfg_read_config(struct pci_mmcfg_region *cfg, unsigned int busnr, - unsigned int devfn, int reg, int len, u32 *value) -{ - struct thunder_pcie *pcie = cfg->data; - void __iomem *addr; - - if (!pcie->valid) { - /* Not support for now */ - pr_err("RC PEM not supported !!!\n"); - return PCIBIOS_DEVICE_NOT_FOUND; - } - - addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg); - - switch (len) { - case 1: - *value = readb(addr); - break; - case 2: - *value = readw(addr); - break; - case 4: - *value = readl(addr); - break; - default: - return PCIBIOS_BAD_REGISTER_NUMBER; - } - - return PCIBIOS_SUCCESSFUL; -} - -static int thunder_mmcfg_write_config(struct pci_mmcfg_region *cfg, - unsigned int busnr, unsigned int devfn, int reg, int len, - u32 value) { - struct thunder_pcie *pcie = cfg->data; - void __iomem *addr; - - if (!pcie->valid) { - /* Not support for now */ - pr_err("RC PEM not supported !!!\n"); - return PCIBIOS_DEVICE_NOT_FOUND; - } - - addr = thunder_pcie_get_cfg_addr(pcie, busnr, devfn, reg); - - switch (len) { - case 1: - writeb(value, addr); - break; - case 2: - writew(value, addr); - break; - case 4: - writel(value, addr); - break; - default: - return PCIBIOS_BAD_REGISTER_NUMBER; - } - - return PCIBIOS_SUCCESSFUL; -} - -static int thunder_acpi_mcfg_fixup(struct acpi_pci_root *root, - struct pci_mmcfg_region *cfg) -{ - struct thunder_pcie *pcie; - - pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); - if (!pcie) - return -ENOMEM; - - pcie->dev = &root->device->dev; - - thunder_pcie_config(pcie, cfg->address); - - pcie->cfg_base = cfg->virt; - cfg->data = pcie; - cfg->read = thunder_mmcfg_read_config; - cfg->write = thunder_mmcfg_write_config; - - return 0; -} -DECLARE_ACPI_MCFG_FIXUP("CAVIUM", "THUNDERX", thunder_acpi_mcfg_fixup); -#endif - MODULE_AUTHOR("Sunil Goutham"); MODULE_DESCRIPTION("Cavium Thunder ECAM host controller driver"); MODULE_LICENSE("GPL v2");