[PATCH 2/6] timers/migration: Defer initialization after capacity topology is setup

From: Frederic Weisbecker

Date: Thu Jun 25 2026 - 12:42:37 EST


In order for the timer migration code to get informations from the CPU
capacity list, the tree must be prepared after the scheduler topology is
initialized.

Defer this part and the hotplug callbacks to a late initcall. Two new
cases are taken care of now that the registration of the callbacks
is made after CPUs have booted:

* The prepare callback may now run on the target.

* A subsequent root level can be created even if no CPU is available in
the hierarchy because prepare callbacks are called on all CPUs before
online callbacks.

Signed-off-by: Frederic Weisbecker <frederic@xxxxxxxxxx>
---
kernel/time/timer_migration.c | 92 +++++++++++++++++++++++++----------
1 file changed, 67 insertions(+), 25 deletions(-)

diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c
index 52c15affdbff..4209e695ec7b 100644
--- a/kernel/time/timer_migration.c
+++ b/kernel/time/timer_migration.c
@@ -1631,7 +1631,6 @@ static int __init tmigr_init_isolation(void)
/* Protect against RCU torture hotplug testing */
return tmigr_isolated_exclude_cpumask(cpumask);
}
-late_initcall(tmigr_init_isolation);

static void tmigr_init_group(struct tmigr_group *group, unsigned int lvl,
int node)
@@ -1703,7 +1702,7 @@ static struct tmigr_group *tmigr_get_group(int node, unsigned int lvl)
return group;
}

-static bool tmigr_init_root(struct tmigr_group *group, bool activate)
+static bool tmigr_init_root(struct tmigr_group *group, bool root_up)
{
if (!group->parent && group != tmigr_root) {
/*
@@ -1712,7 +1711,7 @@ static bool tmigr_init_root(struct tmigr_group *group, bool activate)
* created in the future and made visible before this groupmask.
*/
group->groupmask = BIT(0);
- WARN_ON_ONCE(activate);
+ WARN_ON_ONCE(root_up);

return true;
}
@@ -1723,9 +1722,9 @@ static bool tmigr_init_root(struct tmigr_group *group, bool activate)

static void tmigr_connect_child_parent(struct tmigr_group *child,
struct tmigr_group *parent,
- bool activate)
+ bool root_up)
{
- if (tmigr_init_root(parent, activate)) {
+ if (tmigr_init_root(parent, root_up)) {
/*
* The previous top level had prepared its groupmask already,
* simply account it in advance as the first child. If some groups
@@ -1736,7 +1735,7 @@ static void tmigr_connect_child_parent(struct tmigr_group *child,
}

/* Connecting old root to new root ? */
- if (!parent->parent && activate) {
+ if (!parent->parent && root_up) {
/*
* @child is the old top, or in case of node mismatch, some
* intermediate group between the old top and the new one in
@@ -1831,7 +1830,7 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node,
tmc->tmgroup = group;
tmc->groupmask = BIT(group->num_children++);

- tmigr_init_root(group, activate);
+ tmigr_init_root(group, start);

trace_tmigr_connect_cpu_parent(tmc);

@@ -1839,7 +1838,7 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node,
continue;
} else {
child = stack[i - 1];
- tmigr_connect_child_parent(child, group, activate);
+ tmigr_connect_child_parent(child, group, start);
}
}

@@ -1894,8 +1893,14 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node,
if (state.active) {
data.childmask = start->groupmask;
__walk_groups_from(tmigr_active_up, &data, start, start->parent);
+ } else if (start) {
+ union tmigr_state state;
+
+ /* No available CPU so the old root should be inactive */
+ state.state = atomic_read(&start->migr_state);
+ WARN_ON_ONCE(state.active);
}
- }
+ }

/* Root update */
if (list_is_singular(&tmigr_level_list[top])) {
@@ -1914,18 +1919,9 @@ static int tmigr_setup_groups(unsigned int cpu, unsigned int node,
return err;
}

-static int tmigr_add_cpu(unsigned int cpu)
+static int tmigr_connect_old_root(int cpu, struct tmigr_group *old_root, bool activate)
{
- struct tmigr_group *old_root = tmigr_root;
- int node = cpu_to_node(cpu);
- int ret;
-
- guard(mutex)(&tmigr_mutex);
-
- ret = tmigr_setup_groups(cpu, node, NULL, false);
-
- /* Root has changed? Connect the old one to the new */
- if (ret >= 0 && old_root && old_root != tmigr_root) {
+ if (activate) {
/*
* The target CPU must never do the prepare work, except
* on early boot when the boot CPU is the target. Otherwise
@@ -1933,13 +1929,55 @@ static int tmigr_add_cpu(unsigned int cpu)
* the new one (nevertheless whether old top level group is
* active or not) and/or release an uninitialized childmask.
*/
- WARN_ON_ONCE(cpu == raw_smp_processor_id());
+ WARN_ON_ONCE(cpu == smp_processor_id());
/*
- * The (likely) current CPU is expected to be online in the hierarchy,
+ * The current CPU is expected to be online in the hierarchy,
* otherwise the old root may not be active as expected.
*/
- WARN_ON_ONCE(!per_cpu_ptr(&tmigr_cpu, raw_smp_processor_id())->available);
- ret = tmigr_setup_groups(-1, old_root->numa_node, old_root, true);
+ WARN_ON_ONCE(!__this_cpu_read(tmigr_cpu.available));
+ }
+
+ return tmigr_setup_groups(-1, old_root->numa_node, old_root, activate);
+}
+
+static long connect_old_root_work(void *arg)
+{
+ struct tmigr_group *old_root = arg;
+
+ return tmigr_connect_old_root(smp_processor_id(), old_root, true);
+}
+
+static int tmigr_add_cpu(unsigned int cpu)
+{
+ struct tmigr_group *old_root = tmigr_root;
+ int node = cpu_to_node(cpu);
+ int ret;
+
+ guard(mutex)(&tmigr_mutex);
+
+ ret = tmigr_setup_groups(cpu, node, NULL, false);
+
+ if (ret < 0 || !old_root || old_root == tmigr_root)
+ return ret;
+
+ /* Root has changed. Connect the old one to the new */
+ guard(migrate)();
+ if (cpumask_test_cpu(smp_processor_id(), tmigr_available_cpumask)) {
+ /*
+ * If the CPU is available in the hierarchy, the old root is expected
+ * to be active. Link and propagate to the new root.
+ */
+ ret = tmigr_connect_old_root(cpu, old_root, true);
+ } else {
+ int target = cpumask_first(tmigr_available_cpumask);
+
+ if (target < nr_cpu_ids) {
+ /* Defer the connection to an available CPU to propagate activation */
+ ret = work_on_cpu(target, connect_old_root_work, old_root);
+ } else {
+ /* No CPU available yet, connect but don't activate */
+ ret = tmigr_connect_old_root(cpu, old_root, false);
+ }
}

return ret;
@@ -2044,10 +2082,14 @@ static int __init tmigr_init(void)
if (ret)
goto err;

+ ret = tmigr_init_isolation();
+ if (ret)
+ goto err;
+
return 0;

err:
pr_err("Timer migration setup failed\n");
return ret;
}
-early_initcall(tmigr_init);
+late_initcall(tmigr_init);
--
2.53.0