* [PATCH v4 1/3] xen/device-tree: Parse 'cpu-map' node for CPU topology exploration
2026-07-03 9:15 [PATCH v4 0/3] xen/arm: Device Tree based CPU topology support Hirokazu Takahashi
@ 2026-07-03 9:15 ` Hirokazu Takahashi
2026-07-03 9:15 ` [PATCH v4 2/3] xen/sched: Link CPU topology to scheduler Hirokazu Takahashi
2026-07-03 9:15 ` [PATCH v4 3/3] xen/sched: Make cpu_nr_siblings() architecture-specific Hirokazu Takahashi
2 siblings, 0 replies; 6+ messages in thread
From: Hirokazu Takahashi @ 2026-07-03 9:15 UTC (permalink / raw)
To: xen-devel
Cc: Hirokazu Takahashi, Stefano Stabellini, Julien Grall,
Bertrand Marquis, Michal Orzel, Volodymyr Babchuk, Andrew Cooper,
Anthony PERARD, Jan Beulich, Roger Pau Monné
Parse the 'cpu-map' node in the Device Tree to extract CPU topology
information. If the 'cpu-map' node is absent, fall back to
generating the topology data from the NUMA information. This
generation assumes exactly one socket per NUMA node and that SMT
is unsupported.
Signed-off-by: Hirokazu Takahashi <taka@valinux.co.jp>
---
Changes in v4:
- Only display the CPU topology configuration prompt in common/Kconfig
if the architecture defines HAS_GENERIC_CPU_TOPOLOGY.
- Move the definition of the global 'cpu_topology' pointer to
common/cpu-topology.c.
- Update the Makefile to explicitly build objects as .init.o when all
functions and data within a file are annotated with __init/__initdata,
ensuring their memory is reclaimed after system initialization.
- Add an error log in the CPU-to-node mapping function for out-of-bounds
cases.
- Use ARRAY_SIZE() instead of raw macros when guarding array accesses.
- Rename variables and functions to avoid ambiguous or misleading terms:
- Avoid 'cpuid' to prevent confusion with x86 CPUID features/instructions.
- Avoid 'node' where it could be confused with a NUMA node, explicitly
renaming them to clarify they refer to a Device Tree node.
- Move local variable declarations into the narrowest possible scope.
- Replace the unsupported "%pOF" printk format specifier with "%s" and
explicit node name retrieval.
- Remove #include <dt-cpu-topology.h> from cpu-topology.h, and ensure
the header directly includes only what its definitions require.
- Remove #include <xen/device_tree.h> from dt-cpu-topology.h, replacing
it with a forward declaration of 'struct dt_device_node'.
- Use 'const' qualifiers for pointer declarations where the pointed-to
structure is not modified.
Changes in v3:
- Use (nr_cpu_ids - 1) as the maximum CPU ID here. The fix for the sparse
map mismatch issue on ARM Xen has been split out into a separate patch.
- Switch topology sibling masks to cpumask_var_t for dynamic allocation.
- Allow the system to keep running with a degraded fallback even if
the topology table allocation fails.
- Clean up the code to follow the Xen coding style. Please let me know
if I missed any style nits!
- Verify successful builds across x86, RISC-V, and PPC environments.
Changes in v2:
- Generate topology information even when ACPI is enabled. Note that
this is a temporary implementation and doesn't yet parse the PPTT
(Processor Properties Topology Table).
- Added support for cpu-map node in Device Tree that doesn't contain
explicit cluster node definitions.
xen/arch/arm/Kconfig | 1 +
xen/arch/arm/smpboot.c | 7 +
xen/common/Kconfig | 18 ++
xen/common/Makefile | 1 +
xen/common/cpu-topology.c | 62 +++++
xen/common/device-tree/Makefile | 1 +
xen/common/device-tree/cpu-topology.c | 355 ++++++++++++++++++++++++++
xen/drivers/acpi/Kconfig | 3 +
xen/drivers/acpi/Makefile | 1 +
xen/drivers/acpi/topology.c | 38 +++
xen/include/xen/acpi.h | 2 +
xen/include/xen/cpu-topology.h | 35 +++
xen/include/xen/dt-cpu-topology.h | 29 +++
13 files changed, 553 insertions(+)
create mode 100644 xen/common/cpu-topology.c
create mode 100644 xen/common/device-tree/cpu-topology.c
create mode 100644 xen/drivers/acpi/topology.c
create mode 100644 xen/include/xen/cpu-topology.h
create mode 100644 xen/include/xen/dt-cpu-topology.h
diff --git a/xen/arch/arm/Kconfig b/xen/arch/arm/Kconfig
index 5fa89fcb24..a390844432 100644
--- a/xen/arch/arm/Kconfig
+++ b/xen/arch/arm/Kconfig
@@ -22,6 +22,7 @@ config ARM
select HAS_GRANT_CACHE_FLUSH if GRANT_TABLE
select HAS_STACK_PROTECTOR
select HAS_UBSAN
+ select HAS_GENERIC_CPU_TOPOLOGY
config ARCH_DEFCONFIG
string
diff --git a/xen/arch/arm/smpboot.c b/xen/arch/arm/smpboot.c
index 7f3cfa812e..5ce2bcf6ec 100644
--- a/xen/arch/arm/smpboot.c
+++ b/xen/arch/arm/smpboot.c
@@ -9,10 +9,12 @@
#include <xen/acpi.h>
#include <xen/cpu.h>
+#include <xen/cpu-topology.h>
#include <xen/cpumask.h>
#include <xen/delay.h>
#include <xen/device_tree.h>
#include <xen/domain_page.h>
+#include <xen/dt-cpu-topology.h>
#include <xen/errno.h>
#include <xen/init.h>
#include <xen/mm.h>
@@ -242,6 +244,9 @@ static void __init dt_smp_init_cpus(void)
}
else
tmp_map[i] = hwid;
+
+ /* Pass the info to dt_init_cpu_topology() */
+ map_cpu_to_dt_node(i, cpu);
}
if ( !bootcpu_valid )
@@ -279,6 +284,8 @@ void __init smp_init_cpus(void)
else
acpi_smp_init_cpus();
+ init_cpu_topology();
+
if ( opt_hmp_unsafe )
warning_add("WARNING: HMP COMPUTING HAS BEEN ENABLED.\n"
"It has implications on the security and stability of the system,\n"
diff --git a/xen/common/Kconfig b/xen/common/Kconfig
index 5ff71480ee..6ee689cbdd 100644
--- a/xen/common/Kconfig
+++ b/xen/common/Kconfig
@@ -188,6 +188,24 @@ config VM_EVENT
config NEEDS_LIBELF
bool
+config HAS_GENERIC_CPU_TOPOLOGY
+ bool
+
+config DT_CPU_TOPOLOGY
+ bool
+
+config GENERIC_CPU_TOPOLOGY
+ bool "CPU topology support (UNSUPPORTED)" if UNSUPPORTED
+ depends on HAS_GENERIC_CPU_TOPOLOGY
+ select DT_CPU_TOPOLOGY if DEVICE_TREE_PARSE
+ select ACPI_CPU_TOPOLOGY if ACPI
+ help
+ Retrieve CPU topology information from the device tree to optimize
+ virtual CPU scheduling.
+
+ Note: Implementation for parsing CPU topology from the ACPI PPTT
+ is currently missing.
+
config NUMA
bool
diff --git a/xen/common/Makefile b/xen/common/Makefile
index 6018e25614..34b333cdb3 100644
--- a/xen/common/Makefile
+++ b/xen/common/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_GENERIC_BUG_FRAME) += bug.o
obj-$(CONFIG_HYPFS_CONFIG) += config_data.o
obj-$(CONFIG_CORE_PARKING) += core_parking.o
obj-y += cpu.o
+obj-$(CONFIG_GENERIC_CPU_TOPOLOGY) += cpu-topology.o
obj-$(CONFIG_DEBUG_TRACE) += debugtrace.o
obj-$(CONFIG_HAS_DEVICE_TREE_DISCOVERY) += device.o
obj-$(filter-out $(CONFIG_X86),$(CONFIG_ACPI)) += device.o
diff --git a/xen/common/cpu-topology.c b/xen/common/cpu-topology.c
new file mode 100644
index 0000000000..94cb9ba312
--- /dev/null
+++ b/xen/common/cpu-topology.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <xen/acpi.h>
+#include <xen/cpu-topology.h>
+#include <xen/cpumask.h>
+#include <xen/dt-cpu-topology.h>
+#include <xen/init.h>
+
+struct cpu_topology *__ro_after_init cpu_topology;
+
+static void __init free_topology_table(void)
+{
+ unsigned int cpu;
+
+ for ( cpu = 0; cpu < nr_cpu_ids; cpu++ )
+ {
+ free_cpumask_var(cpu_topology[cpu].thread_sibling);
+ free_cpumask_var(cpu_topology[cpu].core_sibling);
+ free_cpumask_var(cpu_topology[cpu].cluster_sibling);
+ }
+
+ XFREE(cpu_topology);
+}
+
+void __init init_cpu_topology(void)
+{
+ unsigned int cpu;
+
+ cpu_topology = xzalloc_array(struct cpu_topology, nr_cpu_ids);
+ if ( !cpu_topology )
+ {
+ printk(XENLOG_ERR "Failed to allocate memory for cpu_topology table\n");
+ return;
+ }
+
+ for ( cpu = 0; cpu < nr_cpu_ids; cpu++ )
+ {
+ if ( !zalloc_cpumask_var(&cpu_topology[cpu].thread_sibling) ||
+ !zalloc_cpumask_var(&cpu_topology[cpu].core_sibling) ||
+ !zalloc_cpumask_var(&cpu_topology[cpu].cluster_sibling) )
+ {
+ free_topology_table();
+ printk(XENLOG_ERR "Failed to allocate memory for cpu_topology table\n");
+ return;
+ }
+ }
+
+ if ( acpi_disabled )
+ dt_init_cpu_topology();
+ else
+ acpi_init_cpu_topology();
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/common/device-tree/Makefile b/xen/common/device-tree/Makefile
index 9036e455d6..6ee670b5f4 100644
--- a/xen/common/device-tree/Makefile
+++ b/xen/common/device-tree/Makefile
@@ -1,6 +1,7 @@
obj-y += bootfdt.init.o
obj-$(CONFIG_HAS_DEVICE_TREE_DISCOVERY) += bootinfo-fdt.init.o
obj-$(CONFIG_HAS_DEVICE_TREE_DISCOVERY) += bootinfo.init.o
+obj-$(CONFIG_DT_CPU_TOPOLOGY) += cpu-topology.init.o
obj-y += device-tree.o
obj-$(CONFIG_DOMAIN_BUILD_HELPERS) += domain-build.init.o
obj-$(filter $(CONFIG_DOM0LESS_BOOT),$(CONFIG_HAS_DEVICE_TREE_DISCOVERY)) += dom0less-build.init.o
diff --git a/xen/common/device-tree/cpu-topology.c b/xen/common/device-tree/cpu-topology.c
new file mode 100644
index 0000000000..b653227ef4
--- /dev/null
+++ b/xen/common/device-tree/cpu-topology.c
@@ -0,0 +1,355 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Derived from Linux kernel 7.0's $drivers/base/arch_topology.c
+ * Parse cpu topology information.
+ */
+
+#include <xen/acpi.h>
+#include <xen/cpu-topology.h>
+#include <xen/cpumask.h>
+#include <xen/device_tree.h>
+#include <xen/errno.h>
+#include <xen/init.h>
+#include <xen/numa.h>
+
+struct cpu_map {
+ unsigned int thread_id;
+ unsigned int core_id;
+ unsigned int cluster_id;
+ unsigned int package_id;
+};
+
+static const unsigned int __initdata invalid_topo_id = ~0U;
+static struct cpu_map __initdata cpu_map[NR_CPUS] = {
+ [0 ... NR_CPUS - 1] = {invalid_topo_id, invalid_topo_id, invalid_topo_id, 0}
+};
+static struct dt_device_node *__initdata dt_cpu_table[NR_CPUS];
+
+static void __init setup_siblings_masks(unsigned int target_cpu)
+{
+ const struct cpu_topology *target_topo = &cpu_topology[target_cpu];
+ const struct cpu_map *target_map = &cpu_map[target_cpu];
+ unsigned int cpu;
+
+ /* Update core and thread sibling masks */
+ for_each_possible_cpu(cpu)
+ {
+ const struct cpu_topology *cpu_topo = &cpu_topology[cpu];
+ const struct cpu_map *map = &cpu_map[cpu];
+
+ if ( target_map->package_id != map->package_id )
+ continue;
+
+ cpumask_set_cpu(target_cpu, cpu_topo->core_sibling);
+ cpumask_set_cpu(cpu, target_topo->core_sibling);
+
+ if ( target_map->cluster_id != map->cluster_id )
+ continue;
+
+ if ( target_map->cluster_id != invalid_topo_id )
+ {
+ cpumask_set_cpu(target_cpu, cpu_topo->cluster_sibling);
+ cpumask_set_cpu(cpu, target_topo->cluster_sibling);
+ }
+
+ if ( target_map->core_id != map->core_id )
+ continue;
+
+ cpumask_set_cpu(target_cpu, cpu_topo->thread_sibling);
+ cpumask_set_cpu(cpu, target_topo->thread_sibling);
+ }
+}
+
+static struct dt_device_node *__init
+ dt_find_child_node_by_name(const struct dt_device_node *dt,
+ const char *name)
+{
+ struct dt_device_node *np;
+
+ dt_for_each_child_node(dt, np)
+ if ( np->name && (dt_node_cmp(np->name, name) == 0) )
+ break;
+
+ return np;
+}
+
+void __init map_cpu_to_dt_node(unsigned int cpu,
+ struct dt_device_node *cpu_node)
+{
+ if ( cpu < ARRAY_SIZE(dt_cpu_table) )
+ dt_cpu_table[cpu] = cpu_node;
+ else
+ printk(XENLOG_WARNING
+ "cpu id %u exceeds the max cores %lu\n",
+ cpu, ARRAY_SIZE(dt_cpu_table));
+}
+
+static unsigned int __init cpu_node_to_id(const struct dt_device_node *cpu_node)
+{
+ unsigned int cpu;
+
+ for_each_possible_cpu(cpu)
+ if ( cpu_node == dt_cpu_table[cpu] )
+ return cpu;
+
+ return invalid_topo_id;
+}
+
+/*
+ * This function returns the logical cpu number of the DT node.
+ */
+static unsigned int __init
+ get_cpu_for_node(const struct dt_device_node *dt_node)
+{
+ const struct dt_device_node *cpu_node = dt_parse_phandle(dt_node, "cpu", 0);
+
+ if ( !cpu_node )
+ return invalid_topo_id;
+
+ return cpu_node_to_id(cpu_node);
+}
+
+static int __init parse_core(const struct dt_device_node *core,
+ unsigned int package_id,
+ unsigned int cluster_id,
+ unsigned int core_id)
+{
+ bool leaf = true;
+ unsigned int i = 0;
+ unsigned int cpu;
+
+ do {
+ const struct dt_device_node *t;
+ char name[20];
+
+ snprintf(name, sizeof(name), "thread%u", i);
+ t = dt_find_child_node_by_name(core, name);
+
+ if ( !t )
+ break;
+
+ leaf = false;
+ cpu = get_cpu_for_node(t);
+ if ( cpu != invalid_topo_id )
+ {
+ cpu_map[cpu].package_id = package_id;
+ cpu_map[cpu].cluster_id = cluster_id;
+ cpu_map[cpu].core_id = core_id;
+ cpu_map[cpu].thread_id = i;
+ }
+ else
+ {
+ printk(XENLOG_ERR
+ "ERROR: %s: Can't get CPU for thread\n", dt_node_name(t));
+ return -EINVAL;
+ }
+ i++;
+ } while ( true );
+
+ cpu = get_cpu_for_node(core);
+
+ if ( cpu != invalid_topo_id )
+ {
+ if ( !leaf )
+ {
+ printk(XENLOG_ERR "ERROR: %s: Core has both threads and CPU\n",
+ dt_node_name(core));
+ return -EINVAL;
+ }
+
+ cpu_map[cpu].package_id = package_id;
+ cpu_map[cpu].cluster_id = cluster_id;
+ cpu_map[cpu].core_id = core_id;
+ cpu_map[cpu].thread_id = 0;
+ }
+ else if ( leaf )
+ {
+ printk(XENLOG_ERR
+ "ERROR: %s: Can't get CPU for leaf core\n", dt_node_name(core));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __init parse_cluster(const struct dt_device_node *cluster,
+ unsigned int package_id,
+ unsigned int cluster_id,
+ unsigned int depth)
+{
+ bool leaf = true;
+ bool has_cores = false;
+ unsigned int core_id = 0;
+ unsigned int i = 0;
+
+ /*
+ * First check for child clusters; we currently ignore any
+ * information about the nesting of clusters and present the
+ * scheduler with a flat list of them.
+ */
+ do {
+ const struct dt_device_node *c;
+ char name[20];
+ int ret;
+
+ snprintf(name, sizeof(name), "cluster%u", i);
+ c = dt_find_child_node_by_name(cluster, name);
+
+ if ( !c )
+ break;
+
+ leaf = false;
+ ret = parse_cluster(c, package_id, i, depth + 1);
+ if ( depth > 0 )
+ printk(XENLOG_WARNING
+ "WARNING: Topology for clusters of clusters not yet supported\n");
+ if ( ret != 0 )
+ return ret;
+ i++;
+ } while ( true );
+
+ /* Now check for cores */
+ i = 0;
+ do {
+ const struct dt_device_node *c;
+ char name[20];
+ int ret;
+
+ snprintf(name, sizeof(name), "core%u", i);
+ c = dt_find_child_node_by_name(cluster, name);
+
+ if ( !c )
+ break;
+
+ has_cores = true;
+
+ if ( depth == 0 )
+ {
+ printk(XENLOG_ERR
+ "ERROR: %s: cpu-map children should be clusters\n",
+ dt_node_name(c));
+ return -EINVAL;
+ }
+
+ if ( leaf )
+ {
+ ret = parse_core(c, package_id, cluster_id, core_id++);
+ if ( ret != 0 )
+ return ret;
+ }
+ else
+ {
+ printk(XENLOG_ERR "ERROR: %s: Non-leaf cluster with core %s\n",
+ dt_node_name(cluster), name);
+ return -EINVAL;
+ }
+
+ i++;
+ } while ( true );
+
+ if ( leaf && !has_cores )
+ printk(XENLOG_WARNING "WARNING: %s: empty cluster\n",
+ dt_node_name(cluster));
+
+ return 0;
+}
+
+static int __init parse_socket(const struct dt_device_node *socket)
+{
+ bool has_socket = false;
+ unsigned int package_id = 0;
+ int ret;
+
+ do {
+ const struct dt_device_node *c;
+ char name[20];
+
+ snprintf(name, sizeof(name), "socket%u", package_id);
+ c = dt_find_child_node_by_name(socket, name);
+
+ if ( !c )
+ break;
+
+ has_socket = true;
+ ret = parse_cluster(c, package_id, invalid_topo_id, 0);
+ if ( ret != 0 )
+ return ret;
+
+ package_id++;
+ } while ( true );
+
+ if ( !has_socket )
+ ret = parse_cluster(socket, 0, invalid_topo_id, 0);
+
+ return ret;
+}
+
+/*
+ * Generate cpu topology information when cpu-map node doesn't exist.
+ * It assumes that the cpu doesn't have SMT and all CPUs on a NUMA
+ * node belong to the same socket.
+ */
+static void __init fixup_topology(void)
+{
+ unsigned int cpu;
+ unsigned int clid = 0;
+ unsigned int pkgid = 0;
+
+ for_each_possible_cpu(cpu)
+ {
+ struct cpu_map *map = &cpu_map[cpu];
+
+ map->package_id = cpu_to_node(cpu);
+ if ( map->package_id != pkgid )
+ {
+ pkgid = map->package_id;
+ clid = 0;
+ }
+ map->cluster_id = clid++;
+ map->core_id = 0;
+ map->thread_id = 0;
+ }
+}
+
+int __init parse_dt_topology(void)
+{
+ const struct dt_device_node *cpus;
+ const struct dt_device_node *map;
+
+ cpus = dt_find_node_by_path("/cpus");
+ if ( !cpus )
+ {
+ printk(XENLOG_ERR "ERROR: No CPU information found in DT\n");
+ return -EINVAL;
+ }
+
+ map = dt_find_child_node_by_name(cpus, "cpu-map");
+ if ( !map )
+ return -ENOENT;
+
+ return parse_socket(map);
+}
+
+void __init dt_init_cpu_topology(void)
+{
+ unsigned int cpu;
+
+ BUG_ON(!acpi_disabled);
+ BUG_ON(!cpu_topology);
+
+ if ( parse_dt_topology() )
+ fixup_topology();
+
+ for_each_possible_cpu(cpu)
+ setup_siblings_masks(cpu);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/drivers/acpi/Kconfig b/xen/drivers/acpi/Kconfig
index e3f3d8f4b1..5277b7bf83 100644
--- a/xen/drivers/acpi/Kconfig
+++ b/xen/drivers/acpi/Kconfig
@@ -8,3 +8,6 @@ config ACPI_LEGACY_TABLES_LOOKUP
config ACPI_NUMA
bool
select NUMA
+
+config ACPI_CPU_TOPOLOGY
+ bool
diff --git a/xen/drivers/acpi/Makefile b/xen/drivers/acpi/Makefile
index 477408afbe..bcdb1b5e1b 100644
--- a/xen/drivers/acpi/Makefile
+++ b/xen/drivers/acpi/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_PM_OP) += pm-op.o
obj-$(CONFIG_X86) += hwregs.o
obj-$(CONFIG_X86) += reboot.o
+obj-$(CONFIG_ACPI_CPU_TOPOLOGY) += topology.init.o
diff --git a/xen/drivers/acpi/topology.c b/xen/drivers/acpi/topology.c
new file mode 100644
index 0000000000..6bd2d96ebb
--- /dev/null
+++ b/xen/drivers/acpi/topology.c
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <xen/acpi.h>
+#include <xen/cpu-topology.h>
+#include <xen/cpumask.h>
+#include <xen/init.h>
+
+/*
+ * TODO: Populate the topology information by scanning the ACPI
+ * PPTT (Processor Properties Topology Table).
+ */
+void __init acpi_init_cpu_topology(void)
+{
+ unsigned int cpu;
+
+ /*
+ * Generate temporary cpu topology information for now.
+ * It assumes that the cpu doesn't have SMT and all CPUs
+ * belong to the same socket.
+ */
+ for_each_possible_cpu(cpu)
+ {
+ struct cpu_topology *topo = &cpu_topology[cpu];
+
+ cpumask_set_cpu(cpu, topo->thread_sibling);
+ cpumask_copy(topo->core_sibling, &cpu_possible_map);
+ }
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/include/xen/acpi.h b/xen/include/xen/acpi.h
index 2fdf38cf74..d531c76370 100644
--- a/xen/include/xen/acpi.h
+++ b/xen/include/xen/acpi.h
@@ -101,6 +101,8 @@ void acpi_table_print (struct acpi_table_header *header, unsigned long phys_addr
void acpi_table_print_madt_entry (struct acpi_subtable_header *madt);
void acpi_table_print_srat_entry (struct acpi_subtable_header *srat);
+void acpi_init_cpu_topology(void);
+
/* the following four functions are architecture-dependent */
void acpi_numa_slit_init (struct acpi_table_slit *slit);
void acpi_numa_processor_affinity_init(const struct acpi_srat_cpu_affinity *);
diff --git a/xen/include/xen/cpu-topology.h b/xen/include/xen/cpu-topology.h
new file mode 100644
index 0000000000..f64820febf
--- /dev/null
+++ b/xen/include/xen/cpu-topology.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef XEN_CPU_TOPOLOGY_H
+#define XEN_CPU_TOPOLOGY_H
+
+#include <xen/cpumask.h>
+
+#ifdef CONFIG_GENERIC_CPU_TOPOLOGY
+
+struct cpu_topology {
+ cpumask_var_t thread_sibling;
+ cpumask_var_t core_sibling;
+ cpumask_var_t cluster_sibling;
+};
+
+extern struct cpu_topology *cpu_topology;
+void init_cpu_topology(void);
+
+#else /* CONFIG_GENERIC_CPU_TOPOLOGY */
+
+#define cpu_topology ((struct cpu_topology *)NULL)
+static inline void init_cpu_topology(void) {}
+
+#endif /* CONFIG_GENERIC_CPU_TOPOLOGY */
+
+#endif /* XEN_CPU_TOPOLOGY_H */
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/include/xen/dt-cpu-topology.h b/xen/include/xen/dt-cpu-topology.h
new file mode 100644
index 0000000000..cb8a266479
--- /dev/null
+++ b/xen/include/xen/dt-cpu-topology.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef XEN_DT_CPU_TOPOLOGY_H
+#define XEN_DT_CPU_TOPOLOGY_H
+
+struct dt_device_node;
+
+#ifdef CONFIG_DT_CPU_TOPOLOGY
+
+void map_cpu_to_dt_node(unsigned int cpu, struct dt_device_node *cpu_node);
+void dt_init_cpu_topology(void);
+
+#else /* CONFIG_DT_CPU_TOPOLOGY */
+
+static inline void map_cpu_to_dt_node(unsigned int cpu,
+ struct dt_device_node *cpu_node) {}
+
+#endif /* CONFIG_DT_CPU_TOPOLOGY */
+
+#endif /* XEN_DT_CPU_TOPOLOGY_H */
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v4 2/3] xen/sched: Link CPU topology to scheduler
2026-07-03 9:15 [PATCH v4 0/3] xen/arm: Device Tree based CPU topology support Hirokazu Takahashi
2026-07-03 9:15 ` [PATCH v4 1/3] xen/device-tree: Parse 'cpu-map' node for CPU topology exploration Hirokazu Takahashi
@ 2026-07-03 9:15 ` Hirokazu Takahashi
2026-07-03 11:44 ` Oleksii Kurochko
2026-07-03 9:15 ` [PATCH v4 3/3] xen/sched: Make cpu_nr_siblings() architecture-specific Hirokazu Takahashi
2 siblings, 1 reply; 6+ messages in thread
From: Hirokazu Takahashi @ 2026-07-03 9:15 UTC (permalink / raw)
To: xen-devel
Cc: Hirokazu Takahashi, Stefano Stabellini, Julien Grall,
Bertrand Marquis, Michal Orzel, Volodymyr Babchuk, Andrew Cooper,
Anthony PERARD, Jan Beulich, Roger Pau Monné,
Timothy Pearson, Alistair Francis, Connor Davis, Oleksii Kurochko,
Dario Faggioli, Juergen Gross, George Dunlap
Make CPU topology information available to the Xen scheduler.
Additionally, ensure that this topology information is displayed
when executing the 'xl info -n' command.
Signed-off-by: Hirokazu Takahashi <taka@valinux.co.jp>
---
Changes in v4
- Explicitly #include <asm/processor.h> in cpu-topology.h to guarantee
that arch-specific definitions of cpu_to_core() and cpu_to_socket()
take precedence over the generic fallbacks.
- Introduce inline initialization functions for cpu_sibling_mask and
cpu_core_mask in cpu-topology.h, providing separate variants for both
when CONFIG_GENERIC_CPU_TOPOLOGY is enabled and disabled.
Changes in v3
- Remove the temporary definitions of cpu_to_core() and cpu_to_socket()
from RISC-V and PPC processor.h.
- Minimize the use of #ifdef blocks, leveraging compiler Dead Code
Elimination (DCE) where possible.
xen/arch/arm/include/asm/processor.h | 4 --
xen/arch/arm/smpboot.c | 8 +---
xen/arch/ppc/include/asm/processor.h | 4 --
xen/arch/riscv/include/asm/processor.h | 4 --
xen/common/device-tree/cpu-topology.c | 51 ++++++++++++++++++++++++++
xen/common/sched/credit2.c | 3 ++
xen/common/sysctl.c | 1 +
xen/drivers/acpi/topology.c | 3 ++
xen/include/xen/cpu-topology.h | 45 ++++++++++++++++++++++-
9 files changed, 103 insertions(+), 20 deletions(-)
diff --git a/xen/arch/arm/include/asm/processor.h b/xen/arch/arm/include/asm/processor.h
index a3753c317f..41fa73cfc4 100644
--- a/xen/arch/arm/include/asm/processor.h
+++ b/xen/arch/arm/include/asm/processor.h
@@ -613,10 +613,6 @@ void show_stack(const struct cpu_user_regs *regs);
#define cpu_relax() barrier() /* Could yield? */
-/* All a bit UP for the moment */
-#define cpu_to_core(_cpu) (0)
-#define cpu_to_socket(_cpu) (0)
-
struct vcpu;
void vcpu_regs_hyp_to_user(const struct vcpu *vcpu,
struct vcpu_guest_core_regs *regs);
diff --git a/xen/arch/arm/smpboot.c b/xen/arch/arm/smpboot.c
index 5ce2bcf6ec..3c9f2a5c53 100644
--- a/xen/arch/arm/smpboot.c
+++ b/xen/arch/arm/smpboot.c
@@ -92,13 +92,7 @@ static int setup_cpu_sibling_map(int cpu)
!zalloc_cpumask_var(&per_cpu(cpu_core_mask, cpu)) )
return -ENOMEM;
- /*
- * Currently we assume there is no multithread and NUMA, so
- * a CPU is a sibling with itself, and the all possible CPUs
- * are supposed to belong to the same socket (NUMA node).
- */
- cpumask_set_cpu(cpu, per_cpu(cpu_sibling_mask, cpu));
- cpumask_copy(per_cpu(cpu_core_mask, cpu), &cpu_possible_map);
+ init_cpu_sibling_map(cpu);
return 0;
}
diff --git a/xen/arch/ppc/include/asm/processor.h b/xen/arch/ppc/include/asm/processor.h
index 242346cab9..1bf6f6c66c 100644
--- a/xen/arch/ppc/include/asm/processor.h
+++ b/xen/arch/ppc/include/asm/processor.h
@@ -141,10 +141,6 @@
/* Macro to adjust thread priority for hardware multithreading */
#define HMT_very_low() asm volatile ( "or %r31, %r31, %r31" )
-/* TODO: This isn't correct */
-#define cpu_to_core(cpu) (0)
-#define cpu_to_socket(cpu) (0)
-
/*
* User-accessible registers: most of these need to be saved/restored
* for every nested Xen invocation.
diff --git a/xen/arch/riscv/include/asm/processor.h b/xen/arch/riscv/include/asm/processor.h
index 6b89df4a2d..d478ffb76b 100644
--- a/xen/arch/riscv/include/asm/processor.h
+++ b/xen/arch/riscv/include/asm/processor.h
@@ -54,10 +54,6 @@ struct cpu_user_regs
unsigned long pregs;
};
-/* TODO: need to implement */
-#define cpu_to_core(cpu) 0
-#define cpu_to_socket(cpu) 0
-
static inline void cpu_relax(void)
{
#ifdef __riscv_zihintpause
diff --git a/xen/common/device-tree/cpu-topology.c b/xen/common/device-tree/cpu-topology.c
index b653227ef4..43322a153f 100644
--- a/xen/common/device-tree/cpu-topology.c
+++ b/xen/common/device-tree/cpu-topology.c
@@ -330,6 +330,55 @@ int __init parse_dt_topology(void)
return parse_socket(map);
}
+static void __init setup_cpu_topology_ids(void)
+{
+ unsigned int cpu;
+ unsigned int next_core_id = 0;
+ unsigned int next_cluster_id = 0;
+ unsigned int next_socket_id = 0;
+
+ for_each_possible_cpu(cpu)
+ {
+ unsigned int first_cpu;
+ struct cpu_topology *topo = &cpu_topology[cpu];
+
+ first_cpu = cpumask_first(topo->thread_sibling);
+ if ( first_cpu == cpu )
+ {
+ topo->phys_core_id = next_core_id;
+ next_core_id++;
+ }
+ else
+ topo->phys_core_id = cpu_topology[first_cpu].phys_core_id;
+
+ /* Reuse the calculated core id if clustering is not supported */
+ if ( cpumask_empty(topo->cluster_sibling) )
+ topo->phys_cluster_id = topo->phys_core_id;
+ else
+ {
+ first_cpu = cpumask_first(topo->cluster_sibling);
+ if ( first_cpu == cpu )
+ {
+ topo->phys_cluster_id = next_cluster_id;
+ next_cluster_id++;
+ }
+ else
+ topo->phys_cluster_id = cpu_topology[first_cpu].phys_cluster_id;
+ }
+
+ first_cpu = cpumask_first(topo->core_sibling);
+ if ( first_cpu == cpu )
+ {
+ topo->phys_socket_id = next_socket_id;
+ next_socket_id++;
+ }
+ else
+ topo->phys_socket_id = cpu_topology[first_cpu].phys_socket_id;
+
+ topo->num_siblings = cpumask_weight(topo->thread_sibling);
+ }
+}
+
void __init dt_init_cpu_topology(void)
{
unsigned int cpu;
@@ -342,6 +391,8 @@ void __init dt_init_cpu_topology(void)
for_each_possible_cpu(cpu)
setup_siblings_masks(cpu);
+
+ setup_cpu_topology_ids();
}
/*
diff --git a/xen/common/sched/credit2.c b/xen/common/sched/credit2.c
index 95946634d1..ada430f262 100644
--- a/xen/common/sched/credit2.c
+++ b/xen/common/sched/credit2.c
@@ -9,6 +9,7 @@
* Based on an earlier verson by Emmanuel Ackaouy.
*/
+#include <xen/cpu-topology.h>
#include <xen/errno.h>
#include <xen/init.h>
#include <xen/lib.h>
@@ -37,6 +38,8 @@ static unsigned int cpu_nr_siblings(unsigned int cpu)
{
#ifdef CONFIG_X86
return cpu_data[cpu].x86_num_siblings;
+#elif defined(CONFIG_CPU_TOPOLOGY)
+ return cpu_topology ? cpu_topology[cpu].num_siblings : 1;
#else
return 1;
#endif
diff --git a/xen/common/sysctl.c b/xen/common/sysctl.c
index 5207664252..81a68fe24c 100644
--- a/xen/common/sysctl.c
+++ b/xen/common/sysctl.c
@@ -28,6 +28,7 @@
#include <xen/pmstat.h>
#include <xen/livepatch.h>
#include <xen/coverage.h>
+#include <xen/cpu-topology.h>
long do_sysctl(XEN_GUEST_HANDLE_PARAM(xen_sysctl_t) u_sysctl)
{
diff --git a/xen/drivers/acpi/topology.c b/xen/drivers/acpi/topology.c
index 6bd2d96ebb..9155edc0be 100644
--- a/xen/drivers/acpi/topology.c
+++ b/xen/drivers/acpi/topology.c
@@ -22,6 +22,9 @@ void __init acpi_init_cpu_topology(void)
{
struct cpu_topology *topo = &cpu_topology[cpu];
+ topo->phys_core_id = cpu;
+ topo->num_siblings = 1;
+
cpumask_set_cpu(cpu, topo->thread_sibling);
cpumask_copy(topo->core_sibling, &cpu_possible_map);
}
diff --git a/xen/include/xen/cpu-topology.h b/xen/include/xen/cpu-topology.h
index f64820febf..ee34425680 100644
--- a/xen/include/xen/cpu-topology.h
+++ b/xen/include/xen/cpu-topology.h
@@ -4,6 +4,9 @@
#define XEN_CPU_TOPOLOGY_H
#include <xen/cpumask.h>
+#include <xen/percpu.h>
+#include <asm/processor.h>
+#include <asm/smp.h>
#ifdef CONFIG_GENERIC_CPU_TOPOLOGY
@@ -11,16 +14,56 @@ struct cpu_topology {
cpumask_var_t thread_sibling;
cpumask_var_t core_sibling;
cpumask_var_t cluster_sibling;
+ unsigned int phys_core_id;
+ unsigned int phys_cluster_id;
+ unsigned int phys_socket_id;
+ unsigned int num_siblings;
};
extern struct cpu_topology *cpu_topology;
void init_cpu_topology(void);
+static inline void init_cpu_sibling_map(unsigned int cpu)
+{
+ if ( cpu_topology )
+ {
+ cpumask_copy(per_cpu(cpu_sibling_mask, cpu),
+ cpu_topology[cpu].thread_sibling);
+ cpumask_copy(per_cpu(cpu_core_mask, cpu),
+ cpu_topology[cpu].core_sibling);
+ }
+ else
+ {
+ cpumask_set_cpu(cpu, per_cpu(cpu_sibling_mask, cpu));
+ cpumask_copy(per_cpu(cpu_core_mask, cpu), &cpu_possible_map);
+ }
+}
+
+#define cpu_to_core(cpu) (cpu_topology ? cpu_topology[cpu].phys_core_id : 0)
+#define cpu_to_socket(cpu) (cpu_topology ? cpu_topology[cpu].phys_socket_id : 0)
+
#else /* CONFIG_GENERIC_CPU_TOPOLOGY */
-#define cpu_topology ((struct cpu_topology *)NULL)
static inline void init_cpu_topology(void) {}
+static inline void init_cpu_sibling_map(unsigned int cpu)
+{
+ /*
+ * If CONFIG_GENERIC_CPU_TOPOLOGY is disabled, it is assumed that
+ * all CPUs reside in the same socket and that SMT is not used.
+ */
+ cpumask_set_cpu(cpu, per_cpu(cpu_sibling_mask, cpu));
+ cpumask_copy(per_cpu(cpu_core_mask, cpu), &cpu_possible_map);
+}
+
+#ifndef cpu_to_core
+#define cpu_to_core(cpu) (0)
+#endif
+
+#ifndef cpu_to_socket
+#define cpu_to_socket(cpu) (0)
+#endif
+
#endif /* CONFIG_GENERIC_CPU_TOPOLOGY */
#endif /* XEN_CPU_TOPOLOGY_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v4 3/3] xen/sched: Make cpu_nr_siblings() architecture-specific
2026-07-03 9:15 [PATCH v4 0/3] xen/arm: Device Tree based CPU topology support Hirokazu Takahashi
2026-07-03 9:15 ` [PATCH v4 1/3] xen/device-tree: Parse 'cpu-map' node for CPU topology exploration Hirokazu Takahashi
2026-07-03 9:15 ` [PATCH v4 2/3] xen/sched: Link CPU topology to scheduler Hirokazu Takahashi
@ 2026-07-03 9:15 ` Hirokazu Takahashi
2 siblings, 0 replies; 6+ messages in thread
From: Hirokazu Takahashi @ 2026-07-03 9:15 UTC (permalink / raw)
To: xen-devel
Cc: Hirokazu Takahashi, Jan Beulich, Andrew Cooper,
Roger Pau Monné, Teddy Astie, Anthony PERARD, Michal Orzel,
Julien Grall, Stefano Stabellini, Dario Faggioli, Juergen Gross,
George Dunlap
Make cpu_nr_siblings() an architecture-specific function.
This patch provides the implementation for x86 and a common
version for Device Tree-based architectures.
Signed-off-by: Hirokazu Takahashi <taka@valinux.co.jp>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
xen/arch/x86/include/asm/processor.h | 1 +
xen/common/sched/credit2.c | 22 +++-------------------
xen/include/xen/cpu-topology.h | 5 +++++
3 files changed, 9 insertions(+), 19 deletions(-)
diff --git a/xen/arch/x86/include/asm/processor.h b/xen/arch/x86/include/asm/processor.h
index 8ca6799a81..9758060129 100644
--- a/xen/arch/x86/include/asm/processor.h
+++ b/xen/arch/x86/include/asm/processor.h
@@ -106,6 +106,7 @@ extern void intel_init_arat(void);
#define cpu_to_core(_cpu) (cpu_data[_cpu].cpu_core_id)
#define cpu_to_socket(_cpu) (cpu_data[_cpu].phys_proc_id)
+#define cpu_nr_siblings(_cpu) (cpu_data[_cpu].x86_num_siblings)
unsigned int apicid_to_socket(unsigned int apicid);
diff --git a/xen/common/sched/credit2.c b/xen/common/sched/credit2.c
index ada430f262..6c645f0e21 100644
--- a/xen/common/sched/credit2.c
+++ b/xen/common/sched/credit2.c
@@ -29,22 +29,6 @@
/* #define d2printk printk */
#define d2printk(x...)
-/*
- * TODO: Abstract this properly, and figure out what Credit2 wants to do with
- * the fact that x86_num_siblings doesn't even have the same meaning
- * between x86 vendors.
- */
-static unsigned int cpu_nr_siblings(unsigned int cpu)
-{
-#ifdef CONFIG_X86
- return cpu_data[cpu].x86_num_siblings;
-#elif defined(CONFIG_CPU_TOPOLOGY)
- return cpu_topology ? cpu_topology[cpu].num_siblings : 1;
-#else
- return 1;
-#endif
-}
-
/*
* Credit2 tracing events ("only" 512 available!). Check
* include/public/trace.h for more details.
@@ -882,9 +866,9 @@ cpu_runqueue_match(const struct csched2_runqueue_data *rqd, unsigned int cpu)
/*
* Additional checks, to avoid separating siblings in different runqueues.
- * This deals with both Intel's HTs and AMD's CUs. An arch that does not have
- * any similar concept will just have cpu_nr_siblings() always return 1, and
- * setup the cpu_sibling_mask-s acordingly (as currently does ARM), and things
+ * This deals with Intel's HTs, AMD's CUs and ARM's SMT. An arch that
+ * does not have similar concept will just have cpu_nr_siblings() always
+ * return 1, and setup the cpu_sibling_mask-s accordingly, and things
* will just work as well.
*/
static bool
diff --git a/xen/include/xen/cpu-topology.h b/xen/include/xen/cpu-topology.h
index ee34425680..646cd0ff04 100644
--- a/xen/include/xen/cpu-topology.h
+++ b/xen/include/xen/cpu-topology.h
@@ -41,6 +41,7 @@ static inline void init_cpu_sibling_map(unsigned int cpu)
#define cpu_to_core(cpu) (cpu_topology ? cpu_topology[cpu].phys_core_id : 0)
#define cpu_to_socket(cpu) (cpu_topology ? cpu_topology[cpu].phys_socket_id : 0)
+#define cpu_nr_siblings(cpu) (cpu_topology ? cpu_topology[cpu].num_siblings : 1)
#else /* CONFIG_GENERIC_CPU_TOPOLOGY */
@@ -64,6 +65,10 @@ static inline void init_cpu_sibling_map(unsigned int cpu)
#define cpu_to_socket(cpu) (0)
#endif
+#ifndef cpu_nr_siblings
+#define cpu_nr_siblings(cpu) (1)
+#endif
+
#endif /* CONFIG_GENERIC_CPU_TOPOLOGY */
#endif /* XEN_CPU_TOPOLOGY_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread