Re: [PATCH 1/2] timers/migration: Fix hotplug migrator selection target on asymetric capacity machines

From: Marek Szyprowski

Date: Mon Jun 08 2026 - 05:49:05 EST


Dear All,

On 20.05.2026 00:09, Frederic Weisbecker wrote:
> When a top-level migrator is deactivated, either at CPU down hotplug
> time or when a CPU is domain isolated, a new migrator is elected among
> the available CPUs and woken up to take over the migration duty.
>
> However that election must happen at the scope of a given hierarchy and
> not globally, which the introduction of per-capacity hierarchies failed
> to handle.
>
> As a result a given hierarchy may end up without migrator to handle
> global timers.
>
> Fix it with making sure that the new migrator belongs to the same
> hierarchy as the outgoing CPU.
>
> Fixes: 098cbaad8e57 ("timers/migration: Split per-capacity hierarchies")
> Signed-off-by: Frederic Weisbecker <frederic@xxxxxxxxxx>

This patch landed recently in linux-next as commit e4a70f5fbd43 ("timers/migration:
Fix hotplug migrator selection target on asymetric capacity machines"). In my tests
I found that it breaks system suspend/resume on some legacy big.LITTLE ARM machines.


Reverting $subject, together with dependent commit d4f198c13611 ("timers/migration:
Deactivate per-capacity hierarchies under nohz_full") on top of linux-next fixes
this issue. Here is the log from the system suspend/resume failure introduced by
the $subject patch:


root@target:~# time rtcwake -s10 -mmem
rtcwake: wakeup from "mem" using /dev/rtc0 at Mon Jun  8 11:17:23 2026
PM: suspend entry (deep)
Filesystems sync: 0.000 seconds
Freezing user space processes
Freezing user space processes completed (elapsed 0.002 seconds)
OOM killer disabled.
Freezing remaining freezable tasks
Freezing remaining freezable tasks completed (elapsed 0.042 seconds)
printk: Suspending console(s) (use no_console_suspend to debug)
...
Disabling non-boot CPUs ...
------------[ cut here ]------------
WARNING: kernel/time/timer_migration.c:1505 at tmigr_clear_cpu_available+0x3b8/0x3c8, CPU#5: cpuhp/5/40
Modules linked in:
CPU: 5 UID: 0 PID: 40 Comm: cpuhp/5 Not tainted 7.1.0-rc1-00028-ge4a70f5fbd43-dirty #16750 PREEMPT
Hardware name: Samsung Exynos (Flattened Device Tree)
Call trace:
 unwind_backtrace from show_stack+0x10/0x14
 show_stack from dump_stack_lvl+0x68/0x88
 dump_stack_lvl from __warn+0x94/0x204
 __warn from warn_slowpath_fmt+0x1b0/0x1bc
 warn_slowpath_fmt from tmigr_clear_cpu_available+0x3b8/0x3c8
 tmigr_clear_cpu_available from cpuhp_invoke_callback+0x190/0x380
 cpuhp_invoke_callback from cpuhp_thread_fun+0x1a8/0x2e8
 cpuhp_thread_fun from smpboot_thread_fn+0x174/0x32c
 smpboot_thread_fn from kthread+0x128/0x168
 kthread from ret_from_fork+0x14/0x28
Exception stack(0xf092dfb0 to 0xf092dff8)
dfa0:                                     00000000 00000000 00000000 00000000
dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
dfe0: 00000000 00000000 00000000 00000000 00000013 00000000
irq event stamp: 1295
hardirqs last  enabled at (1301): [<c01c4798>] __up_console_sem+0x50/0x60
hardirqs last disabled at (1306): [<c01c4784>] __up_console_sem+0x3c/0x60
softirqs last  enabled at (0): [<c01326c8>] copy_process+0xa0c/0x1c9c
softirqs last disabled at (0): [<00000000>] 0x0
---[ end trace 0000000000000000 ]---
Error taking CPU5 down: -22
Non-boot CPUs are not disabled
Enabling non-boot CPUs ...
CPU6 is up
CPU7 is up
...
OOM killer enabled.
Restarting tasks: Starting
Restarting tasks: Done
random: crng reseeded on system resumption
PM: suspend exit
rtcwake: write error


> ---
> kernel/time/timer_migration.c | 42 ++++++++++++++++++++++++++---------
> 1 file changed, 32 insertions(+), 10 deletions(-)
>
> diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c
> index 25e3c563eb74..8032b0044f44 100644
> --- a/kernel/time/timer_migration.c
> +++ b/kernel/time/timer_migration.c
> @@ -1464,6 +1464,18 @@ static long tmigr_trigger_active(void *unused)
> return 0;
> }
>
> +static struct tmigr_hierarchy *__tmigr_get_hierarchy(unsigned int capacity)
> +{
> + struct tmigr_hierarchy *iter;
> +
> + list_for_each_entry(iter, &tmigr_hierarchy_list, node) {
> + if (iter->capacity == capacity)
> + return iter;
> + }
> +
> + return NULL;
> +}
> +
> static int tmigr_clear_cpu_available(unsigned int cpu)
> {
> struct tmigr_cpu *tmc = this_cpu_ptr(&tmigr_cpu);
> @@ -1488,8 +1500,21 @@ static int tmigr_clear_cpu_available(unsigned int cpu)
> }
>
> if (firstexp != KTIME_MAX) {
> - migrator = cpumask_any(tmigr_available_cpumask);
> - work_on_cpu(migrator, tmigr_trigger_active, NULL);
> + struct tmigr_hierarchy *hier = __tmigr_get_hierarchy(arch_scale_cpu_capacity(cpu));
> +
> + if (WARN_ON_ONCE(!hier))
> + return -EINVAL;
> +
> + migrator = cpumask_any_and(tmigr_available_cpumask, hier->cpumask);
> + if (migrator < nr_cpu_ids) {
> + work_on_cpu(migrator, tmigr_trigger_active, NULL);
> + } else {
> + /*
> + * If deactivation returned an expiration, it belongs to an available
> + * nohz CPU in the hierarchy.
> + */
> + WARN_ONCE(1, "Expected available CPU in the hierarchy\n");
> + }
> }
>
> return 0;
> @@ -1915,12 +1940,9 @@ static int tmigr_setup_groups(struct tmigr_hierarchy *hier, unsigned int cpu,
>
> static struct tmigr_hierarchy *tmigr_get_hierarchy(unsigned int capacity)
> {
> - struct tmigr_hierarchy *hier = NULL, *iter;
> + struct tmigr_hierarchy *hier;
>
> - list_for_each_entry(iter, &tmigr_hierarchy_list, node) {
> - if (iter->capacity == capacity)
> - hier = iter;
> - }
> + hier = __tmigr_get_hierarchy(capacity);
>
> if (hier)
> return hier;
> @@ -1978,9 +2000,9 @@ static long connect_old_root_work(void *arg)
> struct tmigr_hierarchy *hier;
> int cpu = smp_processor_id();
>
> - hier = tmigr_get_hierarchy(arch_scale_cpu_capacity(cpu));
> - if (IS_ERR(hier))
> - return PTR_ERR(hier);
> + hier = __tmigr_get_hierarchy(arch_scale_cpu_capacity(cpu));
> + if (WARN_ON_ONCE(!hier))
> + return -EINVAL;
>
> return tmigr_connect_old_root(hier, cpu, old_root, true);
> }

Best regards
--
Marek Szyprowski, PhD
Samsung R&D Institute Poland