From 40dcc9bdbef3d93a516c8ee32cb1b50eddeeebcc Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 11 Sep 2026 16:38:13 +0200 Subject: irq_work: Flush lazy work CPU down on PREEMPT_RT PREEMPT_RT invokes IRQ_WORK_LAZY callbacks and callbacks which are not explicitly marked IRQ_WORK_HARD_IRQ from thread context. If a CPU gets shutdown, the irq_work is flushed during smpcfd_dying_cpu() which is invoked on the target CPU with disabled interrupts. The callbacks enqueued on lazy_list on PREEMPT_RT are not flushed because they require thread context. The callbacks remain and get processed once the CPU gets back online. per-CPU irq_work items continue to work for enqueues on their local-CPU, only the work on the "offline" CPU get stuck. "Single" irq_work are worse because they remain "claimed" and can not be used again. One "symptom" is the late printk() in CPU down path which enqueues the irq_work for printing but it never gets scheduled. Further prints don't print to the console because the irq_work is "pending" of the offline CPU. Another problem is the canceling/ flushing of the irq_work which is stuck. Since the irq_work will not continue, the task waiting for its completion will block waiting. While looking through the users of irq_work, it does not matter if the callback is enqueued on another CPU. This makes it possible to invoke the callbacks from a remote CPU by smpcfd_dead_cpu(). This is invoked from the control CPU with enabled interrupts shortly after the hotplugged CPU is shutdown so the llist can be accessed safely. Add irq_work_run_cpu() which invokes the irq_work callbacks of the specified dead CPU. Invoke it from smpcfd_dead_cpu(). Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911143815.997254-3-bigeasy@linutronix.de --- kernel/irq_work.c | 11 +++++++++++ kernel/smp.c | 1 + 2 files changed, 12 insertions(+) (limited to 'kernel') diff --git a/kernel/irq_work.c b/kernel/irq_work.c index 9f19c05c9962..73eabcbdcd50 100644 --- a/kernel/irq_work.c +++ b/kernel/irq_work.c @@ -263,6 +263,17 @@ void irq_work_run(void) } EXPORT_SYMBOL_GPL(irq_work_run); +void irq_work_run_cpu(unsigned int cpu) +{ + if (WARN_ON_ONCE(!cpumask_test_cpu(cpu, cpu_dying_mask))) + return; + + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) + return; + + irq_work_run_list(per_cpu_ptr(&lazy_list, cpu)); +} + void irq_work_tick(void) { struct llist_head *raised = this_cpu_ptr(&raised_list); diff --git a/kernel/smp.c b/kernel/smp.c index b696bcc60c08..8f530a094c30 100644 --- a/kernel/smp.c +++ b/kernel/smp.c @@ -87,6 +87,7 @@ int smpcfd_dead_cpu(unsigned int cpu) free_cpumask_var(cfd->cpumask); free_cpumask_var(cfd->cpumask_ipi); + irq_work_run_cpu(cpu); return 0; } -- cgit v1.2.3