From: Ganapatrao Kulkarni <gkulkarni at 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 at caviumnetworks.com> Signed-off-by: Vadim Lomovtsev <Vadim.Lomovtsev at 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 at 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; +} -- 2.4.3