Adding numa dt binding support for arm64 based platforms. dt node parsing for numa topology is done using device property numa-node-id and device node distance-map. Reviewed-by: Robert Richter <rrichter@xxxxxxxxxx> Signed-off-by: Ganapatrao Kulkarni <gkulkarni@xxxxxxxxxxxxxxxxxx> --- arch/arm64/Kconfig | 10 ++ arch/arm64/include/asm/numa.h | 10 ++ arch/arm64/kernel/Makefile | 1 + arch/arm64/kernel/of_numa.c | 265 ++++++++++++++++++++++++++++++++++++++++++ arch/arm64/kernel/smp.c | 2 + arch/arm64/mm/numa.c | 10 +- 6 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 arch/arm64/kernel/of_numa.c diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 74f5d73..775cf4a 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -515,6 +515,16 @@ config NUMA local memory of the CPU and add some more NUMA awareness to the kernel. +config OF_NUMA + bool "Device Tree NUMA support" + depends on NUMA + depends on OF + default y + help + Enable Device Tree NUMA support. + This enables the numa mapping of cpu, memory, io and + inter node distances using dt bindings. + config NODES_SHIFT int "Maximum NUMA Nodes (as a power of 2)" range 1 10 diff --git a/arch/arm64/include/asm/numa.h b/arch/arm64/include/asm/numa.h index 6efc85c..dd16639 100644 --- a/arch/arm64/include/asm/numa.h +++ b/arch/arm64/include/asm/numa.h @@ -43,4 +43,14 @@ void numa_store_cpu_info(unsigned int cpu); static inline void numa_store_cpu_info(unsigned int cpu) { } static inline void arm64_numa_init(void) { } #endif /* CONFIG_NUMA */ + +struct device_node; +#ifdef CONFIG_OF_NUMA +int __init arm64_of_numa_init(void); +void __init of_numa_set_node_info(unsigned int cpu, struct device_node *dn); +#else +static inline void of_numa_set_node_info(unsigned int cpu, + struct device_node *dn) { } +#endif + #endif /* __ASM_NUMA_H */ diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 474691f..7987763 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -41,6 +41,7 @@ arm64-obj-$(CONFIG_EFI) += efi.o efi-entry.stub.o arm64-obj-$(CONFIG_PCI) += pci.o arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o arm64-obj-$(CONFIG_ACPI) += acpi.o +arm64-obj-$(CONFIG_OF_NUMA) += of_numa.o obj-y += $(arm64-obj-y) vdso/ obj-m += $(arm64-obj-m) diff --git a/arch/arm64/kernel/of_numa.c b/arch/arm64/kernel/of_numa.c new file mode 100644 index 0000000..40b1ce52 --- /dev/null +++ b/arch/arm64/kernel/of_numa.c @@ -0,0 +1,265 @@ +/* + * OF NUMA Parsing support. + * + * Copyright (C) 2015 Cavium Inc. + * Author: Ganapatrao Kulkarni <gkulkarni@xxxxxxxxxx> + * + * 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/ctype.h> +#include <linux/memblock.h> +#include <linux/module.h> +#include <linux/nodemask.h> +#include <linux/of.h> +#include <linux/of_fdt.h> + +#include <asm/smp_plat.h> + +/* define default numa node to 0 */ +#define DEFAULT_NODE 0 +#define OF_NUMA_PROP "numa-node-id" + +/* Returns nid in the range [0..MAX_NUMNODES-1], + * or NUMA_NO_NODE if no valid numa-node-id entry found + * or DEFAULT_NODE if no numa-node-id entry exists + */ +static int of_numa_prop_to_nid(const __be32 *of_numa_prop, int length) +{ + int nid; + + if (!of_numa_prop) + return DEFAULT_NODE; + + if (length != sizeof(*of_numa_prop)) { + pr_warn("NUMA: Invalid of_numa_prop length %d found.\n", + length); + return NUMA_NO_NODE; + } + + nid = of_read_number(of_numa_prop, 1); + if (nid >= MAX_NUMNODES) { + pr_warn("NUMA: Invalid numa node %d found.\n", nid); + return NUMA_NO_NODE; + } + + return nid; +} + +/* Must hold reference to node during call */ +static int of_get_numa_nid(struct device_node *device) +{ + int length; + const __be32 *of_numa_prop; + + of_numa_prop = of_get_property(device, OF_NUMA_PROP, &length); + + return of_numa_prop_to_nid(of_numa_prop, length); +} + +static int __init early_init_of_get_numa_nid(unsigned long node) +{ + int length; + const __be32 *of_numa_prop; + + of_numa_prop = of_get_flat_dt_prop(node, OF_NUMA_PROP, &length); + + return of_numa_prop_to_nid(of_numa_prop, length); +} + +/* Walk the device tree upwards, looking for a numa-node-id property */ +int of_node_to_nid(struct device_node *device) +{ + struct device_node *parent; + int nid = NUMA_NO_NODE; + + of_node_get(device); + while (device) { + const __be32 *of_numa_prop; + int length; + + of_numa_prop = of_get_property(device, OF_NUMA_PROP, &length); + if (of_numa_prop) { + nid = of_numa_prop_to_nid(of_numa_prop, length); + break; + } + + parent = device; + device = of_get_parent(parent); + of_node_put(parent); + } + of_node_put(device); + + return nid; +} + +void __init of_numa_set_node_info(unsigned int cpu, struct device_node *device) +{ + int nid = DEFAULT_NODE; + + if (device) + nid = of_get_numa_nid(device); + + cpu_to_node_map[cpu] = nid; +} + +/* + * Even though we connect cpus to numa domains later in SMP + * init, we need to know the node ids now for all cpus. +*/ +static int __init early_init_parse_cpu_node(unsigned long node) +{ + int nid; + + const char *type = of_get_flat_dt_prop(node, "device_type", NULL); + + /* We are scanning "cpu" nodes only */ + if (type == NULL) + return 0; + else if (strcmp(type, "cpu") != 0) + return 0; + + nid = early_init_of_get_numa_nid(node); + + if (nid == NUMA_NO_NODE) + return -EINVAL; + + node_set(nid, numa_nodes_parsed); + return 0; +} + +static int __init early_init_parse_memory_node(unsigned long node) +{ + const __be32 *reg, *endp; + int length; + int nid; + + const char *type = of_get_flat_dt_prop(node, "device_type", NULL); + + /* We are scanning "memory" nodes only */ + if (type == NULL) + return 0; + else if (strcmp(type, "memory") != 0) + return 0; + + nid = early_init_of_get_numa_nid(node); + + if (nid == NUMA_NO_NODE) + return -EINVAL; + + 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) { + if (numa_add_memblk(nid, + mblk->base, + mblk->size) < 0) + return -EINVAL; + break; + } + } + } + + return 0; +} + +static int __init early_init_parse_distance_map_v1(unsigned long node, + const char *uname) +{ + + const __be32 *prop_dist_matrix; + int length = 0, i, matrix_count; + int nr_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT; + + pr_info("NUMA: parsing numa-distance-map-v1\n"); + + prop_dist_matrix = + of_get_flat_dt_prop(node, "distance-matrix", &length); + + if (!length) { + pr_err("NUMA: failed to parse distance-matrix\n"); + return -ENODEV; + } + + matrix_count = ((length / sizeof(__be32)) / (3 * nr_size_cells)); + + if ((matrix_count * sizeof(__be32) * 3 * nr_size_cells) != length) { + pr_warn("NUMA: invalid distance-matrix length %d\n", length); + return -EINVAL; + } + + for (i = 0; i < matrix_count; i++) { + u32 nodea, nodeb, distance; + + nodea = dt_mem_next_cell(nr_size_cells, &prop_dist_matrix); + nodeb = dt_mem_next_cell(nr_size_cells, &prop_dist_matrix); + distance = dt_mem_next_cell(nr_size_cells, &prop_dist_matrix); + numa_set_distance(nodea, nodeb, distance); + pr_debug("NUMA-DT: distance[node%d -> node%d] = %d\n", + nodea, nodeb, distance); + + /* Set default distance of node B->A same as A->B */ + if (nodeb > nodea) + numa_set_distance(nodeb, nodea, distance); + } + + return 0; +} + +static int __init early_init_parse_distance_map(unsigned long node, + const char *uname) +{ + + if (strcmp(uname, "distance-map") != 0) + return 0; + + if (of_flat_dt_is_compatible(node, "numa-distance-map-v1")) + return early_init_parse_distance_map_v1(node, uname); + + return -EINVAL; +} + +/** + * early_init_of_scan_numa_map - parse memory node and map nid to memory range. + */ +int __init early_init_of_scan_numa_map(unsigned long node, const char *uname, + int depth, void *data) +{ + int ret; + + ret = early_init_parse_cpu_node(node); + + if (!ret) + ret = early_init_parse_memory_node(node); + + if (!ret) + ret = early_init_parse_distance_map(node, uname); + + return ret; +} + +/* DT node mapping is done already early_init_of_scan_memory */ +int __init arm64_of_numa_init(void) +{ + return of_scan_flat_dt(early_init_of_scan_numa_map, NULL); +} diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index d6e7d6a..a2a8c2d 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -520,6 +520,8 @@ static void __init of_parse_and_init_cpus(void) pr_debug("cpu logical map 0x%llx\n", hwid); cpu_logical_map(cpu_count) = hwid; + /* map logical cpu to node */ + of_numa_set_node_info(cpu_count, dn); next: cpu_count++; } diff --git a/arch/arm64/mm/numa.c b/arch/arm64/mm/numa.c index 8dcec55..2a8c3a7 100644 --- a/arch/arm64/mm/numa.c +++ b/arch/arm64/mm/numa.c @@ -383,5 +383,13 @@ static int __init dummy_numa_init(void) */ void __init arm64_numa_init(void) { - numa_init(dummy_numa_init); + int ret = -ENODEV; + +#ifdef CONFIG_OF_NUMA + if (!numa_off) + ret = numa_init(arm64_of_numa_init); +#endif + + if (ret) + numa_init(dummy_numa_init); } -- 1.8.1.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html