[RFC PATCH v2 01/23] sched/topology: Add llc_to_node() to translate LLC id to NUMA node

From: Jianyong Wu

Date: Thu Aug 27 2026 - 08:34:50 EST


Building an LLC affinity/distance matrix requires knowing which NUMA
node each LLC belongs to. Add a per-LLC-id -> NUMA-node map
(llc_to_node_map) derived from the possible CPU set, and expose it
via llc_to_node().

Signed-off-by: Jianyong Wu <wujianyong@xxxxxxxx>
---
include/linux/topology.h | 4 +++
kernel/sched/topology.c | 56 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 60 insertions(+)

diff --git a/include/linux/topology.h b/include/linux/topology.h
index 709a2dcf4c73..9967739a180c 100644
--- a/include/linux/topology.h
+++ b/include/linux/topology.h
@@ -177,6 +177,10 @@ static inline int cpu_to_mem(int cpu)

#endif /* [!]CONFIG_HAVE_MEMORYLESS_NODES */

+#ifdef CONFIG_SCHED_CACHE
+int llc_to_node(int llc);
+#endif
+
#if defined(topology_die_id) && defined(topology_die_cpumask)
#define TOPOLOGY_DIE_SYSFS
#endif
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 622e2e01974c..c6928c6b17b6 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -685,6 +685,11 @@ DEFINE_PER_CPU(struct sched_domain __rcu *, sd_asym_cpucapacity);
DEFINE_STATIC_KEY_FALSE(sched_asym_cpucapacity);
DEFINE_STATIC_KEY_FALSE(sched_cluster_active);

+#ifdef CONFIG_SCHED_CACHE
+static int __rcu *llc_to_node_map;
+static void rebuild_llc_node_map(int size);
+#endif
+
static void update_top_cache_domain(int cpu)
{
struct sched_domain_shared *sds = NULL;
@@ -856,6 +861,56 @@ DEFINE_STATIC_KEY_FALSE(sched_cache_active);
/* user wants cache aware scheduling [0 or 1] */
int sysctl_sched_cache_user = 1;

+int llc_to_node(int llc)
+{
+ int node = -1;
+ int *map = NULL;
+
+ rcu_read_lock();
+ map = rcu_dereference(llc_to_node_map);
+ if (map && llc >= 0 && llc <= max_lid)
+ node = map[llc];
+ rcu_read_unlock();
+
+ return node;
+}
+
+static void rebuild_llc_node_map(int size)
+{
+ int *new_map, *old_map;
+ u8 *seen_llc;
+ int cpu, llc;
+
+ new_map = kcalloc(size, sizeof(int), GFP_KERNEL);
+ if (!new_map)
+ return;
+ seen_llc = kcalloc(size, sizeof(*seen_llc), GFP_KERNEL);
+ if (!seen_llc) {
+ kfree(new_map);
+ return;
+ }
+
+ /*
+ * for_each_possible_cpu() revisits the same LLC non-consecutively
+ * under SMT (each node's LLCs are walked once per thread), so
+ * dedup by llc id via seen_llc[], not by comparing against the
+ * immediately preceding CPU's llc.
+ */
+ for_each_possible_cpu(cpu) {
+ llc = per_cpu(sd_llc_id, cpu);
+ if (llc < 0 || llc >= size || seen_llc[llc])
+ continue;
+ seen_llc[llc] = 1;
+ new_map[llc] = cpu_to_node(cpu);
+ }
+ kfree(seen_llc);
+
+ old_map = rcu_dereference_protected(llc_to_node_map, true);
+ rcu_assign_pointer(llc_to_node_map, new_map);
+ synchronize_rcu();
+ kfree(old_map);
+}
+
/*
* Get the effective LLC size in bytes that @cpu's bottom sched_domain
* can use. A CPU within a cpuset partition can only use a proportion
@@ -925,6 +980,7 @@ static bool alloc_sd_llc(const struct cpumask *cpu_map,
}
}

+ rebuild_llc_node_map(max_lid + 1);
return true;
err:
for_each_cpu(i, cpu_map) {
--
2.34.1