[PATCH v5 3/4] arm64, numa, dt: adding dt based numa support using dt node property arm, associativity
Ganapatrao Kulkarni
gkulkarni at caviumnetworks.com
Fri Aug 14 09:39:33 PDT 2015
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>
---
arch/arm64/Kconfig | 6 +
arch/arm64/include/asm/numa.h | 7 +
arch/arm64/kernel/Makefile | 1 +
arch/arm64/kernel/dt_numa.c | 316 ++++++++++++++++++++++++++++++++++++++++++
arch/arm64/kernel/smp.c | 1 +
arch/arm64/mm/numa.c | 13 ++
6 files changed, 344 insertions(+)
create mode 100644 arch/arm64/kernel/dt_numa.c
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index fa37a5d..ca0f701 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -571,6 +571,12 @@ config NUMA
local memory controller of the CPU and add some more
NUMA awareness to the kernel.
+config ARM64_DT_NUMA
+ bool "Device Tree NUMA support"
+ depends on NUMA
+ help
+ Enable Device Tree NUMA support.
+
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 59b834e..40c0997 100644
--- a/arch/arm64/include/asm/numa.h
+++ b/arch/arm64/include/asm/numa.h
@@ -33,10 +33,17 @@ int __init numa_add_memblk(u32 nodeid, u64 start, u64 end);
void numa_store_cpu_info(int cpu);
void __init build_cpu_to_node_map(void);
void __init numa_set_distance(int from, int to, int distance);
+#if defined(CONFIG_ARM64_DT_NUMA)
+void __init dt_numa_set_node_info(u32 cpu, u64 hwid, void *dn);
+#else
+static inline void dt_numa_set_node_info(u32 cpu, u64 hwid, void *dn) { }
+#endif
+int __init arm64_dt_numa_init(void);
#else /* CONFIG_NUMA */
static inline void numa_store_cpu_info(int cpu) { }
static inline void arm64_numa_init(void) { }
static inline void build_cpu_to_node_map(void) { }
static inline void numa_set_distance(int from, int to, int distance) { }
+static inline void dt_numa_set_node_info(u32 cpu, u64 hwid, void *dn) { }
#endif /* CONFIG_NUMA */
#endif /* _ASM_NUMA_H */
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 9add37b..bc7954b 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -37,6 +37,7 @@ arm64-obj-$(CONFIG_PCI) += pci.o
arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o
arm64-obj-$(CONFIG_ACPI) += pci-acpi.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..02b0a57
--- /dev/null
+++ b/arch/arm64/kernel/dt_numa.c
@@ -0,0 +1,316 @@
+/*
+ * 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);
+
+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;
+}
+
+void __init dt_numa_set_node_info(u32 cpu, u64 hwid, void *dn_ptr)
+{
+ struct device_node *dn = (struct device_node *) dn_ptr;
+ int nid = default_nid;
+
+ if (dn)
+ nid = of_node_to_nid_single(dn);
+
+ node_cpu_hwid[cpu].node_id = nid;
+ node_cpu_hwid[cpu].cpu_hwid = hwid;
+ node_set(nid, numa_nodes_parsed);
+}
+
+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;
+}
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index ae3e02c..bdf0358 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -503,6 +503,7 @@ void __init of_parse_and_init_cpus(void)
pr_debug("cpu logical map 0x%llx\n", hwid);
cpu_logical_map(cpu_count) = hwid;
+ dt_numa_set_node_info(cpu_count, hwid, (void *)dn);
next:
cpu_count++;
}
diff --git a/arch/arm64/mm/numa.c b/arch/arm64/mm/numa.c
index 2be83de..0c879eb 100644
--- a/arch/arm64/mm/numa.c
+++ b/arch/arm64/mm/numa.c
@@ -28,6 +28,7 @@
#include <linux/nodemask.h>
#include <linux/sched.h>
#include <linux/topology.h>
+#include <linux/of.h>
#include <linux/mmzone.h>
#include <asm/smp_plat.h>
@@ -546,5 +547,17 @@ static int __init dummy_numa_init(void)
*/
void __init arm64_numa_init(void)
{
+ int (*init_func)(void);
+
+ if (IS_ENABLED(CONFIG_ARM64_DT_NUMA))
+ init_func = arm64_dt_numa_init;
+ else
+ init_func = NULL;
+
+ if (!numa_off && init_func) {
+ if (!numa_init(init_func))
+ return;
+ }
+
numa_init(dummy_numa_init);
}
--
1.8.1.4
More information about the linux-arm-kernel
mailing list