[PATCH 1/2] stop_machine: Track when a CPU executes a stopper callback

From: Aditya Chillara

Date: Thu Aug 27 2026 - 01:21:33 EST


The cpu stopper thread runs in stop_sched_class, above every other
scheduling class. While a stopper callback executes, nothing else on that
CPU is scheduled, including RT kthreads such as a watchdog pet thread. A
callback that performs a long, slow operation can therefore starve those
threads, and for multi_cpu_stop() stall the whole state machine. The same
is true of an interrupt taken during the callback: multi_cpu_stop() keeps
interrupts enabled during MULTI_STOP_PREPARE, and softirqs may run on irq
exit.

Add a per-CPU flag covering execution of a stopper callback, set in the
dispatch path before the callback runs and cleared after it returns, and
expose in_cpu_stop() so callers can detect this context and defer
operations that could hold the CPU.

Assisted-by: LLM
Signed-off-by: Aditya Chillara <aditya.chillara@xxxxxxxxxxxxxxxx>
---
include/linux/stop_machine.h | 13 +++++++++++++
kernel/stop_machine.c | 22 ++++++++++++++++++++++
2 files changed, 35 insertions(+)

diff --git a/include/linux/stop_machine.h b/include/linux/stop_machine.h
index 84e7fb627ba4..ae71a829f036 100644
--- a/include/linux/stop_machine.h
+++ b/include/linux/stop_machine.h
@@ -38,6 +38,14 @@ void stop_machine_park(int cpu);
void stop_machine_unpark(int cpu);
void stop_machine_yield(const struct cpumask *cpumask);

+/*
+ * True while the current CPU is executing a cpu stopper callback. The
+ * stopper runs above every other scheduling class, so nothing else on the
+ * CPU is scheduled until the callback returns. Callers use this to defer
+ * work that could hold the CPU too long (e.g. slow console flushes).
+ */
+bool in_cpu_stop(void);
+
extern void print_stop_info(const char *log_lvl, struct task_struct *task);

#else /* CONFIG_SMP */
@@ -84,6 +92,11 @@ static inline void stop_one_cpu_nowait(unsigned int cpu,

static inline void print_stop_info(const char *log_lvl, struct task_struct *task) { }

+static inline bool in_cpu_stop(void)
+{
+ return false;
+}
+
#endif /* CONFIG_SMP */

/*
diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c
index d085ba1f4b44..d38fabb0b9f1 100644
--- a/kernel/stop_machine.c
+++ b/kernel/stop_machine.c
@@ -48,8 +48,21 @@ struct cpu_stopper {
};

static DEFINE_PER_CPU(struct cpu_stopper, cpu_stopper);
+static DEFINE_PER_CPU(bool, cpu_stop_active);
static bool stop_machine_initialized = false;

+bool in_cpu_stop(void)
+{
+ bool ret;
+
+ preempt_disable();
+ ret = READ_ONCE(*this_cpu_ptr(&cpu_stop_active));
+ preempt_enable();
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(in_cpu_stop);
+
void print_stop_info(const char *log_lvl, struct task_struct *task)
{
/*
@@ -507,7 +520,16 @@ static void cpu_stopper_thread(unsigned int cpu)
stopper->caller = work->caller;
stopper->fn = fn;
preempt_count_inc();
+
+ /*
+ * Set before the callback runs so an interrupt taken during it,
+ * and any softirq run on irq exit, sees the CPU as inside a
+ * stopper callback.
+ */
+ this_cpu_write(cpu_stop_active, true);
ret = fn(arg);
+ this_cpu_write(cpu_stop_active, false);
+
if (done) {
if (ret)
done->ret = ret;

--
2.34.1