]> xenbits.xensource.com Git - people/julieng/linux-arm.git/commitdiff
arm64, numa: adding numa support for arm64 platforms.
authorGanapatrao Kulkarni <gkulkarni@caviumnetworks.com>
Fri, 14 Aug 2015 16:17:20 +0000 (21:47 +0530)
committerJulien Grall <julien.grall@citrix.com>
Mon, 28 Sep 2015 11:05:15 +0000 (12:05 +0100)
Adding numa support for arm64 based platforms.
This patch adds by default the dummy numa node and
maps all memory and cpus to node 0.
using this patch, numa can be simulated on single node arm64 platforms.

Signed-off-by: Ganapatrao Kulkarni <gkulkarni@caviumnetworks.com>
Signed-off-by: Vadim Lomovtsev <Vadim.Lomovtsev@caviumnetworks.com>
arch/arm64/Kconfig
arch/arm64/include/asm/numa.h
arch/arm64/kernel/Makefile
arch/arm64/kernel/dt_numa.c [new file with mode: 0644]
arch/arm64/kernel/smp.c
arch/arm64/mm/numa.c

index e5b959dd184f23acb96f03c3c07da8dba10e3a20..13a25f9d5aac0f4e5bd9e2c65d7754cddb4ab23d 100644 (file)
@@ -540,6 +540,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
index 59b834e1f86dd20557401ef35fb257b48a82c18f..40c0997f43cc0c5f8c6a94095d5f3642782a60cd 100644 (file)
@@ -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 */
index 426d0763c81bf3ac9113314a25d367e997cf2e17..efb18f09ad27a01fda27bc62b4242398fbbfd0a1 100644 (file)
@@ -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 (file)
index 0000000..02b0a57
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * 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);
+
+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, &reg);
+               size = dt_mem_next_cell(dt_root_size_cells, &reg);
+               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;
+}
index ae3e02c80b37753a2617d2d78442cf5283fa596c..bdf0358a5aa31800f121d8254ed9a24c12011463 100644 (file)
@@ -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++;
        }
index 2be83deef4fc187993793697b396b79c55f1c02e..0c879eb66dbdbeb5db01d0effcf5f197a585406b 100644 (file)
@@ -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);
 }