* [PATCH 1/9] sched: add SCHED_WARN_ON()/SCHED_WARN_ON_ONCE()/SCHED_WARN()/SCHED_WARN_ONCE()
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 2/9] sched/core: defer WARN console output under rq->lock Rik van Riel
` (8 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
WARN_ON(), WARN_ON_ONCE(), WARN() and WARN_ONCE() emit at KERN_WARNING.
With a legacy (or boot) console registered, vprintk_emit() takes the
synchronous "legacy_direct" path: console_trylock_spinning() +
console_unlock() -> up(&console_sem) -> wake_up_process() ->
try_to_wake_up(), which acquires the woken task's ->pi_lock and its
rq->lock.
If the WARN fires while the current CPU already holds an rq->lock or a
->pi_lock -- the case for almost every WARN in the scheduling-class hot
paths -- this re-enters the scheduler and can deadlock (recursively on
rq->lock, or via the pi_lock/rq->lock ordering). The nbcon and klogd
wakeups are deferred via irq_work and are safe; only the legacy console
path is synchronous. Plain WARN*() emit at KERN_WARNING rather than
LOGLEVEL_SCHED, so they do not get the scheduler-safe deferral that
printk_deferred() does.
Add SCHED_WARN_ON()/SCHED_WARN_ON_ONCE() (and the SCHED_WARN()/
SCHED_WARN_ONCE() forms that carry a format message), which behave
exactly like their WARN*() counterparts but bracket the report in a
printk_deferred section, so the console output is handed to irq_work
instead of being emitted synchronously. The bracket is entered only on
the (cold) firing path, so the hot path cost is unchanged: just the
condition test.
printk_deferred_enter()/exit() toggle a per-CPU counter and must be
balanced on one CPU, so the caller must have preemption disabled. That
is always true while an rq->lock or ->pi_lock (both raw_spinlock_t,
which disable preemption) is held. A lockdep_assert_preemption_disabled()
guards against misuse from preemptible context and compiles away without
CONFIG_PROVE_LOCKING.
WARN_ON()/WARN_ON_ONCE() do not stringify their condition, so passing
the already-evaluated result as a constant produces identical console
output; the condition is evaluated exactly once and its boolean result
is returned, preserving the semantics for callers that test it.
No conversions are done here; this only adds the macros.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/sched.h | 52 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index c7c2dea65edd..60739ccfc32f 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -44,6 +44,7 @@
#include <linux/lockdep_api.h>
#include <linux/lockdep.h>
#include <linux/memblock.h>
+#include <linux/printk.h>
#include <linux/memcontrol.h>
#include <linux/minmax.h>
#include <linux/mm.h>
@@ -99,6 +100,57 @@ struct cpuidle_state;
#define TASK_ON_RQ_QUEUED 1
#define TASK_ON_RQ_MIGRATING 2
+/*
+ * SCHED_WARN_ON() / SCHED_WARN_ON_ONCE() / SCHED_WARN() / SCHED_WARN_ONCE():
+ * WARN_ON() / WARN_ON_ONCE() / WARN() / WARN_ONCE() variants that are safe to
+ * call while holding an rq->lock or a task's ->pi_lock.
+ *
+ * A plain WARN emits at KERN_WARNING. With a legacy console registered, the
+ * printk takes the synchronous path console_unlock() -> up(&console_sem) ->
+ * wake_up_process() -> try_to_wake_up(), which grabs ->pi_lock and rq->lock --
+ * and so deadlocks if such a lock is already held by the WARNing context.
+ *
+ * Bracket the report in a printk_deferred section so the console output is
+ * handed to irq_work instead. This is done only on the (cold) firing path, so
+ * the hot path keeps just the condition test. printk_deferred_enter()/exit()
+ * toggle a per-CPU counter and must be balanced on one CPU; the caller must
+ * therefore have preemption disabled, which is always true while an rq/pi
+ * raw_spinlock is held. The lockdep assert catches misuse from preemptible
+ * context and compiles away without CONFIG_PROVE_LOCKING.
+ */
+#define __SCHED_WARN_DEFERRED(__warn, x) \
+({ \
+ int __ret = !!(x); \
+ \
+ lockdep_assert_preemption_disabled(); \
+ if (unlikely(__ret)) { \
+ printk_deferred_enter(); \
+ __warn(1); \
+ printk_deferred_exit(); \
+ } \
+ __ret; \
+})
+
+#define SCHED_WARN_ON(x) __SCHED_WARN_DEFERRED(WARN_ON, x)
+#define SCHED_WARN_ON_ONCE(x) __SCHED_WARN_DEFERRED(WARN_ON_ONCE, x)
+
+/* As above, for the WARN()/WARN_ONCE() forms that carry a format message. */
+#define __SCHED_WARN_FMT_DEFERRED(__warn, x, fmt...) \
+({ \
+ int __ret = !!(x); \
+ \
+ lockdep_assert_preemption_disabled(); \
+ if (unlikely(__ret)) { \
+ printk_deferred_enter(); \
+ __warn(1, fmt); \
+ printk_deferred_exit(); \
+ } \
+ __ret; \
+})
+
+#define SCHED_WARN(x, fmt...) __SCHED_WARN_FMT_DEFERRED(WARN, x, fmt)
+#define SCHED_WARN_ONCE(x, fmt...) __SCHED_WARN_FMT_DEFERRED(WARN_ONCE, x, fmt)
+
extern __read_mostly int scheduler_running;
extern unsigned long calc_load_update;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 2/9] sched/core: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
2026-06-11 2:14 ` [PATCH 1/9] sched: add SCHED_WARN_ON()/SCHED_WARN_ON_ONCE()/SCHED_WARN()/SCHED_WARN_ONCE() Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 3/9] sched/fair: " Rik van Riel
` (7 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/core.c | 68 ++++++++++++++++++++++-----------------------
1 file changed, 34 insertions(+), 34 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 2f4530eb543f..d83ee1fcdd5e 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -868,7 +868,7 @@ void update_rq_clock(struct rq *rq)
return;
if (sched_feat(WARN_DOUBLE_CLOCK))
- WARN_ON_ONCE(rq->clock_update_flags & RQCF_UPDATED);
+ SCHED_WARN_ON_ONCE(rq->clock_update_flags & RQCF_UPDATED);
rq->clock_update_flags |= RQCF_UPDATED;
clock = sched_clock_cpu(cpu_of(rq));
@@ -1826,7 +1826,7 @@ static inline void uclamp_rq_dec_id(struct rq *rq, struct task_struct *p,
bucket = &uc_rq->bucket[uc_se->bucket_id];
- WARN_ON_ONCE(!bucket->tasks);
+ SCHED_WARN_ON_ONCE(!bucket->tasks);
if (likely(bucket->tasks))
bucket->tasks--;
@@ -1846,7 +1846,7 @@ static inline void uclamp_rq_dec_id(struct rq *rq, struct task_struct *p,
* Defensive programming: this should never happen. If it happens,
* e.g. due to future modification, warn and fix up the expected value.
*/
- WARN_ON_ONCE(bucket->value > rq_clamp);
+ SCHED_WARN_ON_ONCE(bucket->value > rq_clamp);
if (bucket->value >= rq_clamp) {
bkt_clamp = uclamp_rq_max_value(rq, clamp_id, uc_se->value);
uclamp_rq_set(rq, clamp_id, bkt_clamp);
@@ -2229,7 +2229,7 @@ void activate_task(struct rq *rq, struct task_struct *p, int flags)
void deactivate_task(struct rq *rq, struct task_struct *p, int flags)
{
- WARN_ON_ONCE(flags & DEQUEUE_SLEEP);
+ SCHED_WARN_ON_ONCE(flags & DEQUEUE_SLEEP);
WRITE_ONCE(p->on_rq, TASK_ON_RQ_MIGRATING);
ASSERT_EXCLUSIVE_WRITER(p->on_rq);
@@ -2556,7 +2556,7 @@ static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
rq = cpu_rq(new_cpu);
rq_lock(rq, rf);
- WARN_ON_ONCE(task_cpu(p) != new_cpu);
+ SCHED_WARN_ON_ONCE(task_cpu(p) != new_cpu);
activate_task(rq, p, 0);
wakeup_preempt(rq, p, 0);
@@ -2642,7 +2642,7 @@ static int migration_cpu_stop(void *data)
* If we were passed a pending, then ->stop_pending was set, thus
* p->migration_pending must have remained stable.
*/
- WARN_ON_ONCE(pending && pending != p->migration_pending);
+ SCHED_WARN_ON_ONCE(pending && pending != p->migration_pending);
/*
* If task_rq(p) != rq, it cannot be migrated here, because we're
@@ -2701,7 +2701,7 @@ static int migration_cpu_stop(void *data)
* determine is_migration_disabled() and so have to chase after
* it.
*/
- WARN_ON_ONCE(!pending->stop_pending);
+ SCHED_WARN_ON_ONCE(!pending->stop_pending);
preempt_disable();
rq_unlock(rq, &rf);
raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
@@ -3044,7 +3044,7 @@ static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flag
*
* Either way, we really should have a @pending here.
*/
- if (WARN_ON_ONCE(!pending)) {
+ if (SCHED_WARN_ON_ONCE(!pending)) {
task_rq_unlock(rq, p, rf);
return -EINVAL;
}
@@ -3101,7 +3101,7 @@ static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flag
wait_var_event(&my_pending.refs, !refcount_read(&my_pending.refs));
/* ARGH */
- WARN_ON_ONCE(my_pending.stop_pending);
+ SCHED_WARN_ON_ONCE(my_pending.stop_pending);
return 0;
}
@@ -3156,7 +3156,7 @@ static int __set_cpus_allowed_ptr_locked(struct task_struct *p,
goto out;
}
- if (WARN_ON_ONCE(p == current &&
+ if (SCHED_WARN_ON_ONCE(p == current &&
is_migration_disabled(p) &&
!cpumask_test_cpu(task_cpu(p), ctx->new_mask))) {
ret = -EBUSY;
@@ -3346,14 +3346,14 @@ void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
* We should never call set_task_cpu() on a blocked task,
* ttwu() will sort out the placement.
*/
- WARN_ON_ONCE(state != TASK_RUNNING && state != TASK_WAKING && !p->on_rq);
+ SCHED_WARN_ON_ONCE(state != TASK_RUNNING && state != TASK_WAKING && !p->on_rq);
/*
* Migrating fair class task must have p->on_rq = TASK_ON_RQ_MIGRATING,
* because schedstat_wait_{start,end} rebase migrating task's wait_start
* time relying on p->on_rq.
*/
- WARN_ON_ONCE(state == TASK_RUNNING &&
+ SCHED_WARN_ON_ONCE(state == TASK_RUNNING &&
p->sched_class == &fair_sched_class &&
(p->on_rq && !task_on_rq_migrating(p)));
@@ -3368,15 +3368,15 @@ void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
* Furthermore, all task_rq users should acquire both locks, see
* task_rq_lock().
*/
- WARN_ON_ONCE(debug_locks && !(lockdep_is_held(&p->pi_lock) ||
+ SCHED_WARN_ON_ONCE(debug_locks && !(lockdep_is_held(&p->pi_lock) ||
lockdep_is_held(__rq_lockp(task_rq(p)))));
#endif
/*
* Clearly, migrating tasks to offline CPUs is a fairly daft thing.
*/
- WARN_ON_ONCE(!cpu_online(new_cpu));
+ SCHED_WARN_ON_ONCE(!cpu_online(new_cpu));
- WARN_ON_ONCE(is_migration_disabled(p));
+ SCHED_WARN_ON_ONCE(is_migration_disabled(p));
trace_sched_migrate_task(p, new_cpu);
@@ -3902,10 +3902,10 @@ void sched_ttwu_pending(void *arg)
update_rq_clock(rq);
llist_for_each_entry_safe(p, t, llist, wake_entry.llist) {
- if (WARN_ON_ONCE(p->on_cpu))
+ if (SCHED_WARN_ON_ONCE(p->on_cpu))
smp_cond_load_acquire(&p->on_cpu, !VAL);
- if (WARN_ON_ONCE(task_cpu(p) != cpu_of(rq)))
+ if (SCHED_WARN_ON_ONCE(task_cpu(p) != cpu_of(rq)))
set_task_cpu(p, cpu_of(rq));
ttwu_do_activate(rq, p, p->sched_remote_wakeup ? WF_MIGRATED : 0, &rf);
@@ -4102,7 +4102,7 @@ bool ttwu_state_match(struct task_struct *p, unsigned int state, int *success)
int match;
if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)) {
- WARN_ON_ONCE((state & TASK_RTLOCK_WAIT) &&
+ SCHED_WARN_ON_ONCE((state & TASK_RTLOCK_WAIT) &&
state != TASK_RTLOCK_WAIT);
}
@@ -5333,7 +5333,7 @@ static struct rq *finish_task_switch(struct task_struct *prev)
*
* Also, see FORK_PREEMPT_COUNT.
*/
- if (WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET,
+ if (SCHED_WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET,
"corrupted preempt_count: %s/%d/0x%x\n",
current->comm, current->pid, preempt_count()))
preempt_count_set(FORK_PREEMPT_COUNT);
@@ -5861,7 +5861,7 @@ static void sched_tick_remote(struct work_struct *work)
* we are always sure that there is no proxy (only a
* single task is running).
*/
- WARN_ON_ONCE(rq->curr != rq->donor);
+ SCHED_WARN_ON_ONCE(rq->curr != rq->donor);
update_rq_clock(rq);
if (!is_idle_task(curr)) {
@@ -5870,7 +5870,7 @@ static void sched_tick_remote(struct work_struct *work)
* reasonable amount of time.
*/
u64 delta = rq_clock_task(rq) - curr->se.exec_start;
- WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 30);
+ SCHED_WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 30);
}
curr->sched_class->task_tick(rq, curr, 0);
@@ -6302,7 +6302,7 @@ pick_next_task(struct rq *rq, struct rq_flags *rf)
* For robustness, update the min_vruntime_fi for
* unconstrained picks as well.
*/
- WARN_ON_ONCE(fi_before);
+ SCHED_WARN_ON_ONCE(fi_before);
task_vruntime_update(rq, next, false);
goto out_set_next;
}
@@ -6380,7 +6380,7 @@ pick_next_task(struct rq *rq, struct rq_flags *rf)
rq->core_sched_seq = rq->core->core_pick_seq;
/* Something should have been selected for current CPU */
- WARN_ON_ONCE(!next);
+ SCHED_WARN_ON_ONCE(!next);
/*
* Reschedule siblings
@@ -6423,7 +6423,7 @@ pick_next_task(struct rq *rq, struct rq_flags *rf)
}
/* Did we break L1TF mitigation requirements? */
- WARN_ON_ONCE(!cookie_match(next, rq_i->core_pick));
+ SCHED_WARN_ON_ONCE(!cookie_match(next, rq_i->core_pick));
if (rq_i->curr == rq_i->core_pick) {
rq_i->core_pick = NULL;
@@ -6561,7 +6561,7 @@ static void sched_core_cpu_starting(unsigned int cpu)
guard(core_lock)(&cpu);
- WARN_ON_ONCE(rq->core != rq);
+ SCHED_WARN_ON_ONCE(rq->core != rq);
/* if we're the first, we'll be our own leader */
if (cpumask_weight(smt_mask) == 1)
@@ -6578,7 +6578,7 @@ static void sched_core_cpu_starting(unsigned int cpu)
}
}
- if (WARN_ON_ONCE(!core_rq)) /* whoopsie */
+ if (SCHED_WARN_ON_ONCE(!core_rq)) /* whoopsie */
return;
/* install and validate core_rq */
@@ -6588,7 +6588,7 @@ static void sched_core_cpu_starting(unsigned int cpu)
if (t == cpu)
rq->core = core_rq;
- WARN_ON_ONCE(rq->core != core_rq);
+ SCHED_WARN_ON_ONCE(rq->core != core_rq);
}
}
@@ -6602,7 +6602,7 @@ static void sched_core_cpu_deactivate(unsigned int cpu)
/* if we're the last man standing, nothing to do */
if (cpumask_weight(smt_mask) == 1) {
- WARN_ON_ONCE(rq->core != rq);
+ SCHED_WARN_ON_ONCE(rq->core != rq);
return;
}
@@ -6618,7 +6618,7 @@ static void sched_core_cpu_deactivate(unsigned int cpu)
break;
}
- if (WARN_ON_ONCE(!core_rq)) /* impossible */
+ if (SCHED_WARN_ON_ONCE(!core_rq)) /* impossible */
return;
/* copy the shared state to the new leader */
@@ -7004,7 +7004,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
static struct task_struct *
find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
{
- WARN_ONCE(1, "This should never be called in the !SCHED_PROXY_EXEC case\n");
+ SCHED_WARN_ONCE(1, "This should never be called in the !SCHED_PROXY_EXEC case\n");
return donor;
}
#endif /* SCHED_PROXY_EXEC */
@@ -7666,8 +7666,8 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task)
* real need to boost.
*/
if (unlikely(p == rq->idle)) {
- WARN_ON(p != rq->curr);
- WARN_ON(p->pi_blocked_on);
+ SCHED_WARN_ON(p != rq->curr);
+ SCHED_WARN_ON(p->pi_blocked_on);
goto out_unlock;
}
@@ -8499,7 +8499,7 @@ static void balance_push_set(int cpu, bool on)
rq_lock_irqsave(rq, &rf);
if (on) {
- WARN_ON_ONCE(rq->balance_callback);
+ SCHED_WARN_ON_ONCE(rq->balance_callback);
rq->balance_callback = &balance_push_callback;
} else if (rq->balance_callback == &balance_push_callback) {
rq->balance_callback = NULL;
@@ -8832,7 +8832,7 @@ int sched_cpu_dying(unsigned int cpu)
rq_lock_irqsave(rq, &rf);
update_rq_clock(rq);
if (rq->nr_running != 1 || rq_has_pinned_tasks(rq)) {
- WARN(true, "Dying CPU not properly vacated!");
+ SCHED_WARN(true, "Dying CPU not properly vacated!");
dump_rq_tasks(rq, KERN_WARNING);
}
dl_server_stop(&rq->fair_server);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 3/9] sched/fair: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
2026-06-11 2:14 ` [PATCH 1/9] sched: add SCHED_WARN_ON()/SCHED_WARN_ON_ONCE()/SCHED_WARN()/SCHED_WARN_ONCE() Rik van Riel
2026-06-11 2:14 ` [PATCH 2/9] sched/core: defer WARN console output under rq->lock Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 4/9] sched/deadline: " Rik van Riel
` (6 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/fair.c | 66 ++++++++++++++++++++++-----------------------
1 file changed, 33 insertions(+), 33 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 49b48c5f5746..0cddbbe2e6a5 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -404,7 +404,7 @@ static inline void list_del_leaf_cfs_rq(struct cfs_rq *cfs_rq)
static inline void assert_list_leaf_cfs_rq(struct rq *rq)
{
- WARN_ON_ONCE(rq->tmp_alone_branch != &rq->leaf_cfs_rq_list);
+ SCHED_WARN_ON_ONCE(rq->tmp_alone_branch != &rq->leaf_cfs_rq_list);
}
/* Iterate through all leaf cfs_rq's on a runqueue */
@@ -689,7 +689,7 @@ __sum_w_vruntime_add(struct cfs_rq *cfs_rq, struct sched_entity *se)
s64 w_vruntime, key = entity_key(cfs_rq, se);
w_vruntime = key * weight;
- WARN_ON_ONCE((w_vruntime >> 63) != (w_vruntime >> 62));
+ SCHED_WARN_ON_ONCE((w_vruntime >> 63) != (w_vruntime >> 62));
cfs_rq->sum_w_vruntime += w_vruntime;
cfs_rq->sum_weight += weight;
@@ -861,7 +861,7 @@ bool update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
u64 avruntime = avg_vruntime(cfs_rq);
s64 vlag = entity_lag(cfs_rq, se, avruntime);
- WARN_ON_ONCE(!se->on_rq);
+ SCHED_WARN_ON_ONCE(!se->on_rq);
if (se->sched_delayed) {
/* previous vlag < 0 otherwise se would not be delayed */
@@ -1153,7 +1153,7 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq, bool protect)
if (sched_feat(PICK_BUDDY) && protect &&
cfs_rq->next && entity_eligible(cfs_rq, cfs_rq->next)) {
/* ->next will never be delayed */
- WARN_ON_ONCE(cfs_rq->next->sched_delayed);
+ SCHED_WARN_ON_ONCE(cfs_rq->next->sched_delayed);
return cfs_rq->next;
}
@@ -4918,7 +4918,7 @@ static inline bool load_avg_is_decayed(struct sched_avg *sa)
* Make sure that rounding and/or propagation of PELT values never
* break this.
*/
- WARN_ON_ONCE(sa->load_avg ||
+ SCHED_WARN_ON_ONCE(sa->load_avg ||
sa->util_avg ||
sa->runnable_avg);
@@ -6065,7 +6065,7 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
weight = avg_vruntime_weight(cfs_rq, se->load.weight);
lag *= load + weight;
- if (WARN_ON_ONCE(!load))
+ if (SCHED_WARN_ON_ONCE(!load))
load = 1;
lag = div64_long(lag, load);
@@ -6258,7 +6258,7 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
clear_buddies(cfs_rq, se);
if (flags & DEQUEUE_DELAYED) {
- WARN_ON_ONCE(!se->sched_delayed);
+ SCHED_WARN_ON_ONCE(!se->sched_delayed);
} else {
bool delay = sleep;
/*
@@ -6268,7 +6268,7 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
if (flags & (DEQUEUE_SPECIAL | DEQUEUE_THROTTLE))
delay = false;
- WARN_ON_ONCE(delay && se->sched_delayed);
+ SCHED_WARN_ON_ONCE(delay && se->sched_delayed);
if (sched_feat(DELAY_DEQUEUE) && delay &&
!entity_eligible(cfs_rq, se)) {
@@ -6360,7 +6360,7 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, bool first)
}
update_stats_curr_start(cfs_rq, se);
- WARN_ON_ONCE(cfs_rq->curr);
+ SCHED_WARN_ON_ONCE(cfs_rq->curr);
cfs_rq->curr = se;
/*
@@ -6422,7 +6422,7 @@ static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
/* in !on_rq case, update occurred at dequeue */
update_load_avg(cfs_rq, prev, 0);
}
- WARN_ON_ONCE(cfs_rq->curr != prev);
+ SCHED_WARN_ON_ONCE(cfs_rq->curr != prev);
cfs_rq->curr = NULL;
}
@@ -6768,7 +6768,7 @@ static int tg_unthrottle_up(struct task_group *tg, void *data)
cfs_rq->throttled_clock_self = 0;
- if (WARN_ON_ONCE((s64)delta < 0))
+ if (SCHED_WARN_ON_ONCE((s64)delta < 0))
delta = 0;
cfs_rq->throttled_clock_self_time += delta;
@@ -6855,8 +6855,8 @@ static int tg_throttle_down(struct task_group *tg, void *data)
cfs_rq->pelt_clock_throttled = 1;
}
- WARN_ON_ONCE(cfs_rq->throttled_clock_self);
- WARN_ON_ONCE(!list_empty(&cfs_rq->throttled_limbo_list));
+ SCHED_WARN_ON_ONCE(cfs_rq->throttled_clock_self);
+ SCHED_WARN_ON_ONCE(!list_empty(&cfs_rq->throttled_limbo_list));
return 0;
}
@@ -6910,7 +6910,7 @@ static bool throttle_cfs_rq(struct cfs_rq *cfs_rq)
* throttled-list. rq->lock protects completion.
*/
cfs_rq->throttled = 1;
- WARN_ON_ONCE(cfs_rq->throttled_clock);
+ SCHED_WARN_ON_ONCE(cfs_rq->throttled_clock);
/*
* If current hierarchy was throttled, add throttle work to the
@@ -7029,7 +7029,7 @@ static inline void __unthrottle_cfs_rq_async(struct cfs_rq *cfs_rq)
}
/* Already enqueued */
- if (WARN_ON_ONCE(!list_empty(&cfs_rq->throttled_csd_list)))
+ if (SCHED_WARN_ON_ONCE(!list_empty(&cfs_rq->throttled_csd_list)))
return;
first = list_empty(&rq->cfsb_csd_list);
@@ -7042,7 +7042,7 @@ static void unthrottle_cfs_rq_async(struct cfs_rq *cfs_rq)
{
lockdep_assert_rq_held(rq_of(cfs_rq));
- if (WARN_ON_ONCE(!cfs_rq_throttled(cfs_rq) ||
+ if (SCHED_WARN_ON_ONCE(!cfs_rq_throttled(cfs_rq) ||
cfs_rq->runtime_remaining <= 0))
return;
@@ -7083,7 +7083,7 @@ static bool distribute_cfs_runtime(struct cfs_bandwidth *cfs_b)
}
/* By the above checks, this should never be true */
- WARN_ON_ONCE(cfs_rq->runtime_remaining > 0);
+ SCHED_WARN_ON_ONCE(cfs_rq->runtime_remaining > 0);
scoped_guard(raw_spinlock, &cfs_b->lock) {
runtime = -cfs_rq->runtime_remaining + 1;
@@ -7671,7 +7671,7 @@ static void hrtick_start_fair(struct rq *rq, struct task_struct *p)
u64 vdelta;
u64 delta;
- WARN_ON_ONCE(task_rq(p) != rq);
+ SCHED_WARN_ON_ONCE(task_rq(p) != rq);
if (rq->cfs.h_nr_queued <= 1)
return;
@@ -7794,8 +7794,8 @@ requeue_delayed_entity(struct sched_entity *se)
* Because a delayed entity is one that is still on
* the runqueue competing until elegibility.
*/
- WARN_ON_ONCE(!se->sched_delayed);
- WARN_ON_ONCE(!se->on_rq);
+ SCHED_WARN_ON_ONCE(!se->sched_delayed);
+ SCHED_WARN_ON_ONCE(!se->on_rq);
if (update_entity_lag(cfs_rq, se)) {
cfs_rq->nr_queued--;
@@ -8032,8 +8032,8 @@ static int dequeue_entities(struct rq *rq, struct sched_entity *se, int flags)
rq->next_balance = jiffies;
if (p && task_delayed) {
- WARN_ON_ONCE(!task_sleep);
- WARN_ON_ONCE(p->on_rq != 1);
+ SCHED_WARN_ON_ONCE(!task_sleep);
+ SCHED_WARN_ON_ONCE(p->on_rq != 1);
/*
* Fix-up what block_task() skipped.
@@ -9709,7 +9709,7 @@ static void set_cpus_allowed_fair(struct task_struct *p, struct affinity_context
static void set_next_buddy(struct sched_entity *se)
{
for_each_sched_entity(se) {
- if (WARN_ON_ONCE(!se->on_rq))
+ if (SCHED_WARN_ON_ONCE(!se->on_rq))
return;
if (se_is_idle(se))
return;
@@ -9756,7 +9756,7 @@ preempt_sync(struct rq *rq, int wake_flags,
* WF_SYNC without WF_TTWU is not expected so warn if it happens even
* though it is likely harmless.
*/
- WARN_ON_ONCE(!(wake_flags & WF_TTWU));
+ SCHED_WARN_ON_ONCE(!(wake_flags & WF_TTWU));
threshold = sysctl_sched_migration_cost;
delta = rq_clock_task(rq) - se->exec_start;
@@ -9828,7 +9828,7 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f
return;
find_matching_se(&se, &pse);
- WARN_ON_ONCE(!pse);
+ SCHED_WARN_ON_ONCE(!pse);
cse_is_idle = se_is_idle(se);
pse_is_idle = se_is_idle(pse);
@@ -10861,8 +10861,8 @@ static void detach_task(struct task_struct *p, struct lb_env *env)
schedstat_inc(p->stats.nr_forced_migrations);
}
- WARN_ON(task_current(env->src_rq, p));
- WARN_ON(task_current_donor(env->src_rq, p));
+ SCHED_WARN_ON(task_current(env->src_rq, p));
+ SCHED_WARN_ON(task_current_donor(env->src_rq, p));
deactivate_task(env->src_rq, p, DEQUEUE_NOCLOCK);
set_task_cpu(p, env->dst_cpu);
@@ -13334,7 +13334,7 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq,
goto out_balanced;
}
- WARN_ON_ONCE(busiest == env.dst_rq);
+ SCHED_WARN_ON_ONCE(busiest == env.dst_rq);
update_lb_imbalance_stat(&env, sd, idle);
@@ -13651,7 +13651,7 @@ static int active_load_balance_cpu_stop(void *data)
* we need to fix it. Originally reported by
* Bjorn Helgaas on a 128-CPU setup.
*/
- WARN_ON_ONCE(busiest_rq == target_rq);
+ SCHED_WARN_ON_ONCE(busiest_rq == target_rq);
/* Search for an sd spanning us and the target CPU. */
rcu_read_lock();
@@ -14802,7 +14802,7 @@ bool cfs_prio_less(const struct task_struct *a, const struct task_struct *b,
struct cfs_rq *cfs_rqb;
s64 delta;
- WARN_ON_ONCE(task_rq(b)->core != rq->core);
+ SCHED_WARN_ON_ONCE(task_rq(b)->core != rq->core);
#ifdef CONFIG_FAIR_GROUP_SCHED
/*
@@ -15020,7 +15020,7 @@ static void switched_from_fair(struct rq *rq, struct task_struct *p)
static void switched_to_fair(struct rq *rq, struct task_struct *p)
{
- WARN_ON_ONCE(p->se.sched_delayed);
+ SCHED_WARN_ON_ONCE(p->se.sched_delayed);
attach_task_cfs_rq(p);
@@ -15077,7 +15077,7 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)
if (!first)
return;
- WARN_ON_ONCE(se->sched_delayed);
+ SCHED_WARN_ON_ONCE(se->sched_delayed);
if (hrtick_enabled_fair(rq))
hrtick_start_fair(rq, p);
@@ -15311,7 +15311,7 @@ int sched_group_set_idle(struct task_group *tg, long idle)
rq_lock_irqsave(rq, &rf);
grp_cfs_rq->idle = idle;
- if (WARN_ON_ONCE(was_idle == cfs_rq_is_idle(grp_cfs_rq)))
+ if (SCHED_WARN_ON_ONCE(was_idle == cfs_rq_is_idle(grp_cfs_rq)))
goto next_cpu;
idle_task_delta = grp_cfs_rq->h_nr_queued -
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 4/9] sched/deadline: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
` (2 preceding siblings ...)
2026-06-11 2:14 ` [PATCH 3/9] sched/fair: " Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 5/9] sched/rt: " Rik van Riel
` (5 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/deadline.c | 72 ++++++++++++++++++++---------------------
1 file changed, 36 insertions(+), 36 deletions(-)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 5ccb06effea0..089ff8e56ebf 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -217,8 +217,8 @@ void __add_running_bw(u64 dl_bw, struct dl_rq *dl_rq)
lockdep_assert_rq_held(rq_of_dl_rq(dl_rq));
dl_rq->running_bw += dl_bw;
- WARN_ON_ONCE(dl_rq->running_bw < old); /* overflow */
- WARN_ON_ONCE(dl_rq->running_bw > dl_rq->this_bw);
+ SCHED_WARN_ON_ONCE(dl_rq->running_bw < old); /* overflow */
+ SCHED_WARN_ON_ONCE(dl_rq->running_bw > dl_rq->this_bw);
/* kick cpufreq (see the comment in kernel/sched/sched.h). */
cpufreq_update_util(rq_of_dl_rq(dl_rq), 0);
}
@@ -230,7 +230,7 @@ void __sub_running_bw(u64 dl_bw, struct dl_rq *dl_rq)
lockdep_assert_rq_held(rq_of_dl_rq(dl_rq));
dl_rq->running_bw -= dl_bw;
- WARN_ON_ONCE(dl_rq->running_bw > old); /* underflow */
+ SCHED_WARN_ON_ONCE(dl_rq->running_bw > old); /* underflow */
if (dl_rq->running_bw > old)
dl_rq->running_bw = 0;
/* kick cpufreq (see the comment in kernel/sched/sched.h). */
@@ -244,7 +244,7 @@ void __add_rq_bw(u64 dl_bw, struct dl_rq *dl_rq)
lockdep_assert_rq_held(rq_of_dl_rq(dl_rq));
dl_rq->this_bw += dl_bw;
- WARN_ON_ONCE(dl_rq->this_bw < old); /* overflow */
+ SCHED_WARN_ON_ONCE(dl_rq->this_bw < old); /* overflow */
}
static inline
@@ -254,10 +254,10 @@ void __sub_rq_bw(u64 dl_bw, struct dl_rq *dl_rq)
lockdep_assert_rq_held(rq_of_dl_rq(dl_rq));
dl_rq->this_bw -= dl_bw;
- WARN_ON_ONCE(dl_rq->this_bw > old); /* underflow */
+ SCHED_WARN_ON_ONCE(dl_rq->this_bw > old); /* underflow */
if (dl_rq->this_bw > old)
dl_rq->this_bw = 0;
- WARN_ON_ONCE(dl_rq->running_bw > dl_rq->this_bw);
+ SCHED_WARN_ON_ONCE(dl_rq->running_bw > dl_rq->this_bw);
}
static inline
@@ -335,7 +335,7 @@ void cancel_inactive_timer(struct sched_dl_entity *dl_se)
static void dl_change_utilization(struct task_struct *p, u64 new_bw)
{
- WARN_ON_ONCE(p->dl.flags & SCHED_FLAG_SUGOV);
+ SCHED_WARN_ON_ONCE(p->dl.flags & SCHED_FLAG_SUGOV);
if (task_on_rq_queued(p))
return;
@@ -416,7 +416,7 @@ static void task_non_contending(struct sched_dl_entity *dl_se, bool dl_task)
if (dl_entity_is_special(dl_se))
return;
- WARN_ON(dl_se->dl_non_contending);
+ SCHED_WARN_ON(dl_se->dl_non_contending);
zerolag_time = dl_se->deadline -
div64_long((dl_se->runtime * dl_se->dl_period),
@@ -582,7 +582,7 @@ static void enqueue_pushable_dl_task(struct rq *rq, struct task_struct *p)
{
struct rb_node *leftmost;
- WARN_ON_ONCE(!RB_EMPTY_NODE(&p->pushable_dl_tasks));
+ SCHED_WARN_ON_ONCE(!RB_EMPTY_NODE(&p->pushable_dl_tasks));
leftmost = rb_add_cached(&p->pushable_dl_tasks,
&rq->dl.pushable_dl_tasks_root,
@@ -664,7 +664,7 @@ static struct rq *dl_task_offline_migration(struct rq *rq, struct task_struct *p
* Failed to find any suitable CPU.
* The task will never come back!
*/
- WARN_ON_ONCE(dl_bandwidth_enabled());
+ SCHED_WARN_ON_ONCE(dl_bandwidth_enabled());
/*
* If admission control is disabled we
@@ -756,8 +756,8 @@ static inline void setup_new_dl_entity(struct sched_dl_entity *dl_se)
struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
struct rq *rq = rq_of_dl_rq(dl_rq);
- WARN_ON(is_dl_boosted(dl_se));
- WARN_ON(dl_time_before(rq_clock(rq), dl_se->deadline));
+ SCHED_WARN_ON(is_dl_boosted(dl_se));
+ SCHED_WARN_ON(dl_time_before(rq_clock(rq), dl_se->deadline));
/*
* We are racing with the deadline timer. So, do nothing because
@@ -801,7 +801,7 @@ static void replenish_dl_entity(struct sched_dl_entity *dl_se)
struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
struct rq *rq = rq_of_dl_rq(dl_rq);
- WARN_ON_ONCE(pi_of(dl_se)->dl_runtime <= 0);
+ SCHED_WARN_ON_ONCE(pi_of(dl_se)->dl_runtime <= 0);
/*
* This could be the case for a !-dl task that is boosted.
@@ -975,7 +975,7 @@ update_dl_revised_wakeup(struct sched_dl_entity *dl_se, struct rq *rq)
*
* See update_dl_entity() comments for further details.
*/
- WARN_ON(dl_time_before(dl_se->deadline, rq_clock(rq)));
+ SCHED_WARN_ON(dl_time_before(dl_se->deadline, rq_clock(rq)));
dl_se->runtime = (dl_se->dl_density * laxity) >> BW_SHIFT;
}
@@ -1080,7 +1080,7 @@ static int start_dl_timer(struct sched_dl_entity *dl_se)
* (current u > U).
*/
if (dl_se->dl_defer_armed) {
- WARN_ON_ONCE(!dl_se->dl_throttled);
+ SCHED_WARN_ON_ONCE(!dl_se->dl_throttled);
act = ns_to_ktime(dl_se->deadline - dl_se->runtime);
} else {
/* act = deadline - rel-deadline + period */
@@ -1451,7 +1451,7 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
/*
* Non-servers would never get time accounted while throttled.
*/
- WARN_ON_ONCE(!dl_server(dl_se));
+ SCHED_WARN_ON_ONCE(!dl_server(dl_se));
/*
* While the server is marked idle, do not push out the
@@ -1492,7 +1492,7 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
* and queue right away. Otherwise nothing might queue it. That's similar
* to what enqueue_dl_entity() does on start_dl_timer==0. For now, just warn.
*/
- WARN_ON_ONCE(!start_dl_timer(dl_se));
+ SCHED_WARN_ON_ONCE(!start_dl_timer(dl_se));
return;
}
@@ -1806,7 +1806,7 @@ void dl_server_start(struct sched_dl_entity *dl_se)
*/
rq->donor->sched_class->update_curr(rq);
- if (WARN_ON_ONCE(!cpu_online(cpu_of(rq))))
+ if (SCHED_WARN_ON_ONCE(!cpu_online(cpu_of(rq))))
return;
trace_sched_dl_server_start_tp(dl_se, cpu_of(rq), dl_get_type(dl_se, rq));
@@ -1855,7 +1855,7 @@ void sched_init_dl_servers(void)
dl_se = &rq->fair_server;
- WARN_ON(dl_server(dl_se));
+ SCHED_WARN_ON(dl_server(dl_se));
dl_server_apply_params(dl_se, runtime, period, 1);
@@ -1866,7 +1866,7 @@ void sched_init_dl_servers(void)
#ifdef CONFIG_SCHED_CLASS_EXT
dl_se = &rq->ext_server;
- WARN_ON(dl_server(dl_se));
+ SCHED_WARN_ON(dl_server(dl_se));
dl_server_apply_params(dl_se, runtime, period, 1);
@@ -2098,7 +2098,7 @@ int dl_server_swap_bw(struct sched_dl_entity *detach_se,
struct dl_bw *dl_b;
int cpus, ret;
- WARN_ON_ONCE(attach_se->rq != rq);
+ SCHED_WARN_ON_ONCE(attach_se->rq != rq);
scoped_guard (raw_spinlock, &dl_bw_of(cpu)->lock) {
dl_b = dl_bw_of(cpu);
@@ -2265,7 +2265,7 @@ void inc_dl_tasks(struct sched_dl_entity *dl_se, struct dl_rq *dl_rq)
static inline
void dec_dl_tasks(struct sched_dl_entity *dl_se, struct dl_rq *dl_rq)
{
- WARN_ON(!dl_rq->dl_nr_running);
+ SCHED_WARN_ON(!dl_rq->dl_nr_running);
dl_rq->dl_nr_running--;
if (!dl_server(dl_se))
@@ -2357,7 +2357,7 @@ static void __enqueue_dl_entity(struct sched_dl_entity *dl_se)
{
struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
- WARN_ON_ONCE(!RB_EMPTY_NODE(&dl_se->rb_node));
+ SCHED_WARN_ON_ONCE(!RB_EMPTY_NODE(&dl_se->rb_node));
rb_add_cached(&dl_se->rb_node, &dl_rq->root, __dl_less);
@@ -2381,7 +2381,7 @@ static void __dequeue_dl_entity(struct sched_dl_entity *dl_se)
static void
enqueue_dl_entity(struct sched_dl_entity *dl_se, int flags)
{
- WARN_ON_ONCE(on_dl_rq(dl_se));
+ SCHED_WARN_ON_ONCE(on_dl_rq(dl_se));
update_stats_enqueue_dl(dl_rq_of_se(dl_se), dl_se, flags);
@@ -2782,7 +2782,7 @@ static void set_next_task_dl(struct rq *rq, struct task_struct *p, bool first)
/* You can't push away the running task */
dequeue_pushable_dl_task(rq, p);
- WARN_ON_ONCE(dl_rq->curr);
+ SCHED_WARN_ON_ONCE(dl_rq->curr);
dl_rq->curr = dl_se;
if (!first)
@@ -2822,7 +2822,7 @@ static struct task_struct *__pick_task_dl(struct rq *rq, struct rq_flags *rf)
return NULL;
dl_se = pick_next_dl_entity(dl_rq);
- WARN_ON_ONCE(!dl_se);
+ SCHED_WARN_ON_ONCE(!dl_se);
if (dl_server(dl_se)) {
p = dl_se->server_pick_task(dl_se, rf);
@@ -2855,7 +2855,7 @@ static void put_prev_task_dl(struct rq *rq, struct task_struct *p, struct task_s
update_dl_rq_load_avg(rq_clock_pelt(rq), rq, 1);
- WARN_ON_ONCE(dl_rq->curr != dl_se);
+ SCHED_WARN_ON_ONCE(dl_rq->curr != dl_se);
dl_rq->curr = NULL;
if (task_is_blocked(p))
@@ -3037,12 +3037,12 @@ static struct task_struct *pick_next_pushable_dl_task(struct rq *rq)
if (!p)
return NULL;
- WARN_ON_ONCE(rq->cpu != task_cpu(p));
- WARN_ON_ONCE(task_current(rq, p));
- WARN_ON_ONCE(p->nr_cpus_allowed <= 1);
+ SCHED_WARN_ON_ONCE(rq->cpu != task_cpu(p));
+ SCHED_WARN_ON_ONCE(task_current(rq, p));
+ SCHED_WARN_ON_ONCE(p->nr_cpus_allowed <= 1);
- WARN_ON_ONCE(!task_on_rq_queued(p));
- WARN_ON_ONCE(!dl_task(p));
+ SCHED_WARN_ON_ONCE(!task_on_rq_queued(p));
+ SCHED_WARN_ON_ONCE(!dl_task(p));
return p;
}
@@ -3158,7 +3158,7 @@ static int push_dl_task(struct rq *rq)
if (is_migration_disabled(next_task))
return 0;
- if (WARN_ON(next_task == rq->curr))
+ if (SCHED_WARN_ON(next_task == rq->curr))
return 0;
/* We might release rq lock */
@@ -3264,8 +3264,8 @@ static void pull_dl_task(struct rq *this_rq)
*/
if (p && dl_time_before(p->dl.deadline, dmin) &&
dl_task_is_earliest_deadline(p, this_rq)) {
- WARN_ON(p == src_rq->curr);
- WARN_ON(!task_on_rq_queued(p));
+ SCHED_WARN_ON(p == src_rq->curr);
+ SCHED_WARN_ON(!task_on_rq_queued(p));
/*
* Then we pull iff p has actually an earlier
@@ -3324,7 +3324,7 @@ static void set_cpus_allowed_dl(struct task_struct *p,
struct root_domain *src_rd;
struct rq *rq;
- WARN_ON_ONCE(!dl_task(p));
+ SCHED_WARN_ON_ONCE(!dl_task(p));
rq = task_rq(p);
src_rd = rq->rd;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 5/9] sched/rt: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
` (3 preceding siblings ...)
2026-06-11 2:14 ` [PATCH 4/9] sched/deadline: " Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 6/9] sched_ext: " Rik van Riel
` (4 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/rt.c | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index e474c31d8fe6..adbb8f3d6510 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -170,7 +170,7 @@ static void destroy_rt_bandwidth(struct rt_bandwidth *rt_b)
static inline struct task_struct *rt_task_of(struct sched_rt_entity *rt_se)
{
- WARN_ON_ONCE(!rt_entity_is_task(rt_se));
+ SCHED_WARN_ON_ONCE(!rt_entity_is_task(rt_se));
return container_of(rt_se, struct task_struct, rt);
}
@@ -178,13 +178,13 @@ static inline struct task_struct *rt_task_of(struct sched_rt_entity *rt_se)
static inline struct rq *rq_of_rt_rq(struct rt_rq *rt_rq)
{
/* Cannot fold with non-CONFIG_RT_GROUP_SCHED version, layout */
- WARN_ON(!rt_group_sched_enabled() && rt_rq->tg != &root_task_group);
+ SCHED_WARN_ON(!rt_group_sched_enabled() && rt_rq->tg != &root_task_group);
return rt_rq->rq;
}
static inline struct rt_rq *rt_rq_of_se(struct sched_rt_entity *rt_se)
{
- WARN_ON(!rt_group_sched_enabled() && rt_se->rt_rq->tg != &root_task_group);
+ SCHED_WARN_ON(!rt_group_sched_enabled() && rt_se->rt_rq->tg != &root_task_group);
return rt_se->rt_rq;
}
@@ -192,7 +192,7 @@ static inline struct rq *rq_of_rt_se(struct sched_rt_entity *rt_se)
{
struct rt_rq *rt_rq = rt_se->rt_rq;
- WARN_ON(!rt_group_sched_enabled() && rt_rq->tg != &root_task_group);
+ SCHED_WARN_ON(!rt_group_sched_enabled() && rt_rq->tg != &root_task_group);
return rt_rq->rq;
}
@@ -723,7 +723,7 @@ static void __disable_runtime(struct rq *rq)
* We cannot be left wanting - that would mean some runtime
* leaked out of the system.
*/
- WARN_ON_ONCE(want);
+ SCHED_WARN_ON_ONCE(want);
balanced:
/*
* Disable all the borrow logic by pretending we have inf
@@ -1094,7 +1094,7 @@ dec_rt_prio(struct rt_rq *rt_rq, int prio)
if (rt_rq->rt_nr_running) {
- WARN_ON(prio < prev_prio);
+ SCHED_WARN_ON(prio < prev_prio);
/*
* This may have been our highest task, and therefore
@@ -1131,7 +1131,7 @@ dec_rt_group(struct sched_rt_entity *rt_se, struct rt_rq *rt_rq)
if (rt_se_boosted(rt_se))
rt_rq->rt_nr_boosted--;
- WARN_ON(!rt_rq->rt_nr_running && rt_rq->rt_nr_boosted);
+ SCHED_WARN_ON(!rt_rq->rt_nr_running && rt_rq->rt_nr_boosted);
}
#else /* !CONFIG_RT_GROUP_SCHED: */
@@ -1176,7 +1176,7 @@ void inc_rt_tasks(struct sched_rt_entity *rt_se, struct rt_rq *rt_rq)
{
int prio = rt_se_prio(rt_se);
- WARN_ON(!rt_prio(prio));
+ SCHED_WARN_ON(!rt_prio(prio));
rt_rq->rt_nr_running += rt_se_nr_running(rt_se);
rt_rq->rr_nr_running += rt_se_rr_nr_running(rt_se);
@@ -1187,8 +1187,8 @@ void inc_rt_tasks(struct sched_rt_entity *rt_se, struct rt_rq *rt_rq)
static inline
void dec_rt_tasks(struct sched_rt_entity *rt_se, struct rt_rq *rt_rq)
{
- WARN_ON(!rt_prio(rt_se_prio(rt_se)));
- WARN_ON(!rt_rq->rt_nr_running);
+ SCHED_WARN_ON(!rt_prio(rt_se_prio(rt_se)));
+ SCHED_WARN_ON(!rt_rq->rt_nr_running);
rt_rq->rt_nr_running -= rt_se_nr_running(rt_se);
rt_rq->rr_nr_running -= rt_se_rr_nr_running(rt_se);
@@ -1348,7 +1348,7 @@ static void __enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flag
}
if (move_entity(flags)) {
- WARN_ON_ONCE(rt_se->on_list);
+ SCHED_WARN_ON_ONCE(rt_se->on_list);
if (flags & ENQUEUE_HEAD)
list_add(&rt_se->run_list, queue);
else
@@ -1368,7 +1368,7 @@ static void __dequeue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flag
struct rt_prio_array *array = &rt_rq->active;
if (move_entity(flags)) {
- WARN_ON_ONCE(!rt_se->on_list);
+ SCHED_WARN_ON_ONCE(!rt_se->on_list);
__delist_rt_entity(rt_se, array);
}
rt_se->on_rq = 0;
@@ -1690,7 +1690,7 @@ static struct sched_rt_entity *pick_next_rt_entity(struct rt_rq *rt_rq)
BUG_ON(idx >= MAX_RT_PRIO);
queue = array->queue + idx;
- if (WARN_ON_ONCE(list_empty(queue)))
+ if (SCHED_WARN_ON_ONCE(list_empty(queue)))
return NULL;
next = list_entry(queue->next, struct sched_rt_entity, run_list);
@@ -2022,7 +2022,7 @@ static int push_rt_task(struct rq *rq, bool pull)
return 0;
}
- if (WARN_ON(next_task == rq->curr))
+ if (SCHED_WARN_ON(next_task == rq->curr))
return 0;
/* We might release rq lock */
@@ -2322,8 +2322,8 @@ static void pull_rt_task(struct rq *this_rq)
* the to-be-scheduled task?
*/
if (p && (p->prio < this_rq->rt.highest_prio.curr)) {
- WARN_ON(p == src_rq->curr);
- WARN_ON(!task_on_rq_queued(p));
+ SCHED_WARN_ON(p == src_rq->curr);
+ SCHED_WARN_ON(!task_on_rq_queued(p));
/*
* There's a chance that p is higher in priority
@@ -2589,7 +2589,7 @@ static int task_is_throttled_rt(struct task_struct *p, int cpu)
#ifdef CONFIG_RT_GROUP_SCHED // XXX maybe add task_rt_rq(), see also sched_rt_period_rt_rq
rt_rq = task_group(p)->rt_rq[cpu];
- WARN_ON(!rt_group_sched_enabled() && rt_rq->tg != &root_task_group);
+ SCHED_WARN_ON(!rt_group_sched_enabled() && rt_rq->tg != &root_task_group);
#else
rt_rq = &cpu_rq(cpu)->rt;
#endif
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 6/9] sched_ext: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
` (4 preceding siblings ...)
2026-06-11 2:14 ` [PATCH 5/9] sched/rt: " Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 7/9] sched/core_sched: " Rik van Riel
` (3 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/ext.c | 98 +++++++++++++++++++++++-----------------------
1 file changed, 49 insertions(+), 49 deletions(-)
diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
index f412c4bb21c3..149ca9920071 100644
--- a/kernel/sched/ext.c
+++ b/kernel/sched/ext.c
@@ -518,7 +518,7 @@ do { \
*/
#define SCX_CALL_OP_TASK(sch, op, locked_rq, task, args...) \
do { \
- WARN_ON_ONCE(current->scx.kf_tasks[0]); \
+ SCHED_WARN_ON_ONCE(current->scx.kf_tasks[0]); \
current->scx.kf_tasks[0] = task; \
SCX_CALL_OP((sch), op, locked_rq, task, ##args); \
current->scx.kf_tasks[0] = NULL; \
@@ -527,7 +527,7 @@ do { \
#define SCX_CALL_OP_TASK_RET(sch, op, locked_rq, task, args...) \
({ \
__typeof__((sch)->ops.op(task, ##args)) __ret; \
- WARN_ON_ONCE(current->scx.kf_tasks[0]); \
+ SCHED_WARN_ON_ONCE(current->scx.kf_tasks[0]); \
current->scx.kf_tasks[0] = task; \
__ret = SCX_CALL_OP_RET((sch), op, locked_rq, task, ##args); \
current->scx.kf_tasks[0] = NULL; \
@@ -537,7 +537,7 @@ do { \
#define SCX_CALL_OP_2TASKS_RET(sch, op, locked_rq, task0, task1, args...) \
({ \
__typeof__((sch)->ops.op(task0, task1, ##args)) __ret; \
- WARN_ON_ONCE(current->scx.kf_tasks[0]); \
+ SCHED_WARN_ON_ONCE(current->scx.kf_tasks[0]); \
current->scx.kf_tasks[0] = task0; \
current->scx.kf_tasks[1] = task1; \
__ret = SCX_CALL_OP_RET((sch), op, locked_rq, task0, task1, ##args); \
@@ -688,7 +688,7 @@ static bool nldsq_cursor_lost_task(struct scx_dsq_list_node *cursor,
return true;
/* if @p has stayed on @dsq, its rq couldn't have changed */
- if (WARN_ON_ONCE(rq != task_rq(p)))
+ if (SCHED_WARN_ON_ONCE(rq != task_rq(p)))
return true;
return false;
@@ -1225,7 +1225,7 @@ static void schedule_reenq_local(struct rq *rq, u64 reenq_flags)
{
struct scx_sched *root = rcu_dereference_sched(scx_root);
- if (WARN_ON_ONCE(!root))
+ if (SCHED_WARN_ON_ONCE(!root))
return;
schedule_dsq_reenq(root, &rq->scx.local_dsq, reenq_flags, rq);
@@ -1322,7 +1322,7 @@ static void dsq_inc_nr(struct scx_dispatch_q *dsq, struct task_struct *p, u64 en
*/
if (enq_flags & SCX_ENQ_IMMED) {
if (unlikely(dsq->id != SCX_DSQ_LOCAL)) {
- WARN_ON_ONCE(!(enq_flags & SCX_ENQ_GDSQ_FALLBACK));
+ SCHED_WARN_ON_ONCE(!(enq_flags & SCX_ENQ_GDSQ_FALLBACK));
return;
}
p->scx.flags |= SCX_TASK_IMMED;
@@ -1331,7 +1331,7 @@ static void dsq_inc_nr(struct scx_dispatch_q *dsq, struct task_struct *p, u64 en
if (p->scx.flags & SCX_TASK_IMMED) {
struct rq *rq = container_of(dsq, struct rq, scx.local_dsq);
- if (WARN_ON_ONCE(dsq->id != SCX_DSQ_LOCAL))
+ if (SCHED_WARN_ON_ONCE(dsq->id != SCX_DSQ_LOCAL))
return;
rq->scx.nr_immed++;
@@ -1353,8 +1353,8 @@ static void dsq_dec_nr(struct scx_dispatch_q *dsq, struct task_struct *p)
if (p->scx.flags & SCX_TASK_IMMED) {
struct rq *rq = container_of(dsq, struct rq, scx.local_dsq);
- if (WARN_ON_ONCE(dsq->id != SCX_DSQ_LOCAL) ||
- WARN_ON_ONCE(rq->scx.nr_immed <= 0))
+ if (SCHED_WARN_ON_ONCE(dsq->id != SCX_DSQ_LOCAL) ||
+ SCHED_WARN_ON_ONCE(rq->scx.nr_immed <= 0))
return;
rq->scx.nr_immed--;
@@ -1464,8 +1464,8 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
{
bool is_local = dsq->id == SCX_DSQ_LOCAL;
- WARN_ON_ONCE(p->scx.dsq || !list_empty(&p->scx.dsq_list.node));
- WARN_ON_ONCE((p->scx.dsq_flags & SCX_TASK_DSQ_ON_PRIQ) ||
+ SCHED_WARN_ON_ONCE(p->scx.dsq || !list_empty(&p->scx.dsq_list.node));
+ SCHED_WARN_ON_ONCE((p->scx.dsq_flags & SCX_TASK_DSQ_ON_PRIQ) ||
!RB_EMPTY_NODE(&p->scx.dsq_priq));
if (!is_local) {
@@ -1589,7 +1589,7 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
static void task_unlink_from_dsq(struct task_struct *p,
struct scx_dispatch_q *dsq)
{
- WARN_ON_ONCE(list_empty(&p->scx.dsq_list.node));
+ SCHED_WARN_ON_ONCE(list_empty(&p->scx.dsq_list.node));
if (p->scx.dsq_flags & SCX_TASK_DSQ_ON_PRIQ) {
rb_erase(&p->scx.dsq_priq, &dsq->priq);
@@ -1652,7 +1652,7 @@ static void dispatch_dequeue(struct rq *rq, struct task_struct *p)
* holding_cpu which tells dispatch_to_local_dsq() that it lost
* the race.
*/
- WARN_ON_ONCE(!list_empty(&p->scx.dsq_list.node));
+ SCHED_WARN_ON_ONCE(!list_empty(&p->scx.dsq_list.node));
p->scx.holding_cpu = -1;
}
p->scx.dsq = NULL;
@@ -1730,8 +1730,8 @@ static void mark_direct_dispatch(struct scx_sched *sch,
return;
}
- WARN_ON_ONCE(p->scx.ddsp_dsq_id != SCX_DSQ_INVALID);
- WARN_ON_ONCE(p->scx.ddsp_enq_flags);
+ SCHED_WARN_ON_ONCE(p->scx.ddsp_dsq_id != SCX_DSQ_INVALID);
+ SCHED_WARN_ON_ONCE(p->scx.ddsp_enq_flags);
p->scx.ddsp_dsq_id = dsq_id;
p->scx.ddsp_enq_flags = enq_flags;
@@ -1792,13 +1792,13 @@ static void direct_dispatch(struct scx_sched *sch, struct task_struct *p,
atomic_long_set_release(&p->scx.ops_state, SCX_OPSS_NONE);
break;
default:
- WARN_ONCE(true, "sched_ext: %s[%d] has invalid ops state 0x%lx in direct_dispatch()",
+ SCHED_WARN_ONCE(true, "sched_ext: %s[%d] has invalid ops state 0x%lx in direct_dispatch()",
p->comm, p->pid, opss);
atomic_long_set_release(&p->scx.ops_state, SCX_OPSS_NONE);
break;
}
- WARN_ON_ONCE(p->scx.dsq || !list_empty(&p->scx.dsq_list.node));
+ SCHED_WARN_ON_ONCE(p->scx.dsq || !list_empty(&p->scx.dsq_list.node));
list_add_tail(&p->scx.dsq_list.node,
&rq->scx.ddsp_deferred_locals);
schedule_deferred_locked(rq);
@@ -1831,7 +1831,7 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
struct scx_dispatch_q *dsq;
unsigned long qseq;
- WARN_ON_ONCE(!(p->scx.flags & SCX_TASK_QUEUED));
+ SCHED_WARN_ON_ONCE(!(p->scx.flags & SCX_TASK_QUEUED));
/* internal movements - rq migration / RESTORE */
if (sticky_cpu == cpu_of(rq))
@@ -1881,11 +1881,11 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
/* DSQ bypass didn't trigger, enqueue on the BPF scheduler */
qseq = rq->scx.ops_qseq++ << SCX_OPSS_QSEQ_SHIFT;
- WARN_ON_ONCE(atomic_long_read(&p->scx.ops_state) != SCX_OPSS_NONE);
+ SCHED_WARN_ON_ONCE(atomic_long_read(&p->scx.ops_state) != SCX_OPSS_NONE);
atomic_long_set(&p->scx.ops_state, SCX_OPSS_QUEUEING | qseq);
ddsp_taskp = this_cpu_ptr(&direct_dispatch_task);
- WARN_ON_ONCE(*ddsp_taskp);
+ SCHED_WARN_ON_ONCE(*ddsp_taskp);
*ddsp_taskp = p;
SCX_CALL_OP_TASK(sch, enqueue, rq, p, enq_flags);
@@ -1982,7 +1982,7 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_
sticky_cpu = cpu_of(rq);
if (p->scx.flags & SCX_TASK_QUEUED) {
- WARN_ON_ONCE(!task_runnable(p));
+ SCHED_WARN_ON_ONCE(!task_runnable(p));
goto out;
}
@@ -2035,7 +2035,7 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags)
BUG();
case SCX_OPSS_QUEUED:
/* A queued task must always be in BPF scheduler's custody */
- WARN_ON_ONCE(!(p->scx.flags & SCX_TASK_IN_CUSTODY));
+ SCHED_WARN_ON_ONCE(!(p->scx.flags & SCX_TASK_IN_CUSTODY));
if (atomic_long_try_cmpxchg(&p->scx.ops_state, &opss,
SCX_OPSS_NONE))
break;
@@ -2089,7 +2089,7 @@ static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_
deq_flags |= SCX_DEQ_SCHED_CHANGE;
if (!(p->scx.flags & SCX_TASK_QUEUED)) {
- WARN_ON_ONCE(task_runnable(p));
+ SCHED_WARN_ON_ONCE(task_runnable(p));
return true;
}
@@ -2186,7 +2186,7 @@ static void move_local_task_to_local_dsq(struct scx_sched *sch,
lockdep_assert_held(&src_dsq->lock);
lockdep_assert_rq_held(dst_rq);
- WARN_ON_ONCE(p->scx.holding_cpu >= 0);
+ SCHED_WARN_ON_ONCE(p->scx.holding_cpu >= 0);
if (enq_flags & (SCX_ENQ_HEAD | SCX_ENQ_PREEMPT))
list_add(&p->scx.dsq_list.node, &dst_dsq->list);
@@ -2229,8 +2229,8 @@ static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags,
* truncate the upper 32 bit. As we own @rq, we can pass them through
* @rq->scx.extra_enq_flags instead.
*/
- WARN_ON_ONCE(!cpumask_test_cpu(cpu_of(dst_rq), p->cpus_ptr));
- WARN_ON_ONCE(dst_rq->scx.extra_enq_flags);
+ SCHED_WARN_ON_ONCE(!cpumask_test_cpu(cpu_of(dst_rq), p->cpus_ptr));
+ SCHED_WARN_ON_ONCE(dst_rq->scx.extra_enq_flags);
dst_rq->scx.extra_enq_flags = enq_flags;
activate_task(dst_rq, p, 0);
dst_rq->scx.extra_enq_flags = 0;
@@ -2261,7 +2261,7 @@ static bool task_can_run_on_remote_rq(struct scx_sched *sch,
{
s32 cpu = cpu_of(rq);
- WARN_ON_ONCE(task_cpu(p) == cpu);
+ SCHED_WARN_ON_ONCE(task_cpu(p) == cpu);
/*
* If @p has migration disabled, @p->cpus_ptr is updated to contain only
@@ -2341,7 +2341,7 @@ static bool unlink_dsq_and_lock_src_rq(struct task_struct *p,
lockdep_assert_held(&dsq->lock);
- WARN_ON_ONCE(p->scx.holding_cpu >= 0);
+ SCHED_WARN_ON_ONCE(p->scx.holding_cpu >= 0);
task_unlink_from_dsq(p, dsq);
p->scx.holding_cpu = cpu;
@@ -2350,7 +2350,7 @@ static bool unlink_dsq_and_lock_src_rq(struct task_struct *p,
/* task_rq couldn't have changed if we're still the holding cpu */
return likely(p->scx.holding_cpu == cpu) &&
- !WARN_ON_ONCE(src_rq != task_rq(p));
+ !SCHED_WARN_ON_ONCE(src_rq != task_rq(p));
}
static bool consume_remote_task(struct rq *this_rq,
@@ -2560,7 +2560,7 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
/* task_rq couldn't have changed if we're still the holding cpu */
if (likely(p->scx.holding_cpu == raw_smp_processor_id()) &&
- !WARN_ON_ONCE(src_rq != task_rq(p))) {
+ !SCHED_WARN_ON_ONCE(src_rq != task_rq(p))) {
/*
* If @p is staying on the same rq, there's no need to go
* through the full deactivate/activate cycle. Optimize by
@@ -3029,7 +3029,7 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p,
* which should trigger an explicit follow-up scheduling event.
*/
if (next && sched_class_above(&ext_sched_class, next->sched_class)) {
- WARN_ON_ONCE(!(sch->ops.flags & SCX_OPS_ENQ_LAST));
+ SCHED_WARN_ON_ONCE(!(sch->ops.flags & SCX_OPS_ENQ_LAST));
do_enqueue_task(rq, p, SCX_ENQ_LAST, -1);
} else {
do_enqueue_task(rq, p, 0, -1);
@@ -3131,7 +3131,7 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)
keep_prev = rq->scx.flags & SCX_RQ_BAL_KEEP;
if (unlikely(keep_prev &&
prev->sched_class != &ext_sched_class)) {
- WARN_ON_ONCE(scx_enable_state() == SCX_ENABLED);
+ SCHED_WARN_ON_ONCE(scx_enable_state() == SCX_ENABLED);
keep_prev = false;
}
@@ -3262,7 +3262,7 @@ static int select_task_rq_scx(struct task_struct *p, int prev_cpu, int wake_flag
struct task_struct **ddsp_taskp;
ddsp_taskp = this_cpu_ptr(&direct_dispatch_task);
- WARN_ON_ONCE(*ddsp_taskp);
+ SCHED_WARN_ON_ONCE(*ddsp_taskp);
*ddsp_taskp = p;
this_rq()->scx.in_select_cpu = true;
@@ -3510,12 +3510,12 @@ static void scx_set_task_state(struct task_struct *p, u32 state)
warn = prev_state != SCX_TASK_READY;
break;
default:
- WARN_ONCE(1, "sched_ext: Invalid task state %d -> %d for %s[%d]",
+ SCHED_WARN_ONCE(1, "sched_ext: Invalid task state %d -> %d for %s[%d]",
prev_state, state, p->comm, p->pid);
return;
}
- WARN_ONCE(warn, "sched_ext: Invalid task state transition 0x%x -> 0x%x for %s[%d]",
+ SCHED_WARN_ONCE(warn, "sched_ext: Invalid task state transition 0x%x -> 0x%x for %s[%d]",
prev_state, state, p->comm, p->pid);
p->scx.flags &= ~SCX_TASK_STATE_MASK;
@@ -3601,7 +3601,7 @@ static void __scx_enable_task(struct scx_sched *sch, struct task_struct *p)
* transitions are consistent, the flag should always be clear
* here.
*/
- WARN_ON_ONCE(p->scx.flags & SCX_TASK_IN_CUSTODY);
+ SCHED_WARN_ON_ONCE(p->scx.flags & SCX_TASK_IN_CUSTODY);
/*
* Set the weight before calling ops.enable() so that the scheduler
@@ -3632,7 +3632,7 @@ static void scx_disable_task(struct scx_sched *sch, struct task_struct *p)
struct rq *rq = task_rq(p);
lockdep_assert_rq_held(rq);
- WARN_ON_ONCE(scx_get_task_state(p) != SCX_TASK_ENABLED);
+ SCHED_WARN_ON_ONCE(scx_get_task_state(p) != SCX_TASK_ENABLED);
clear_direct_dispatch(p);
@@ -3645,7 +3645,7 @@ static void scx_disable_task(struct scx_sched *sch, struct task_struct *p)
* transitions are consistent, the flag should always be clear
* here.
*/
- WARN_ON_ONCE(p->scx.flags & SCX_TASK_IN_CUSTODY);
+ SCHED_WARN_ON_ONCE(p->scx.flags & SCX_TASK_IN_CUSTODY);
}
static void __scx_disable_and_exit_task(struct scx_sched *sch,
@@ -3670,7 +3670,7 @@ static void __scx_disable_and_exit_task(struct scx_sched *sch,
scx_disable_task(sch, p);
break;
default:
- WARN_ON_ONCE(true);
+ SCHED_WARN_ON_ONCE(true);
return;
}
@@ -3706,7 +3706,7 @@ static void scx_disable_and_exit_task(struct scx_sched *sch,
* it, so undo only init_task.
*/
if (p->scx.flags & SCX_TASK_SUB_INIT) {
- if (!WARN_ON_ONCE(!scx_enabling_sub_sched))
+ if (!SCHED_WARN_ON_ONCE(!scx_enabling_sub_sched))
scx_sub_init_cancel_task(scx_enabling_sub_sched, p);
p->scx.flags &= ~SCX_TASK_SUB_INIT;
}
@@ -3794,7 +3794,7 @@ void scx_cancel_fork(struct task_struct *p)
struct rq_flags rf;
rq = task_rq_lock(p, &rf);
- WARN_ON_ONCE(scx_get_task_state(p) >= SCX_TASK_READY);
+ SCHED_WARN_ON_ONCE(scx_get_task_state(p) >= SCX_TASK_READY);
scx_disable_and_exit_task(scx_task_sched(p), p);
task_rq_unlock(rq, p, &rf);
}
@@ -3941,7 +3941,7 @@ static void process_ddsp_deferred_locals(struct rq *rq)
clear_direct_dispatch(p);
dsq = find_dsq_for_dispatch(sch, rq, dsq_id, task_cpu(p));
- if (!WARN_ON_ONCE(dsq->id != SCX_DSQ_LOCAL))
+ if (!SCHED_WARN_ON_ONCE(dsq->id != SCX_DSQ_LOCAL))
dispatch_to_local_dsq(sch, rq, dsq, p, enq_flags);
}
}
@@ -3996,7 +3996,7 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags)
lockdep_assert_rq_held(rq);
- if (WARN_ON_ONCE(reenq_flags & __SCX_REENQ_TSR_MASK))
+ if (SCHED_WARN_ON_ONCE(reenq_flags & __SCX_REENQ_TSR_MASK))
reenq_flags &= ~__SCX_REENQ_TSR_MASK;
if (rq_is_open(rq, 0))
reenq_flags |= SCX_REENQ_TSR_RQ_OPEN;
@@ -4033,7 +4033,7 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags)
dispatch_dequeue(rq, p);
- if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
+ if (SCHED_WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
p->scx.flags |= reason;
@@ -4154,7 +4154,7 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag
dispatch_dequeue_locked(p, dsq);
raw_spin_unlock(&dsq->lock);
- if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
+ if (SCHED_WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK))
p->scx.flags &= ~SCX_TASK_REENQ_REASON_MASK;
p->scx.flags |= reason;
@@ -4361,7 +4361,7 @@ void scx_cgroup_move_task(struct task_struct *p)
* cgrp_moving_from set.
*/
if (SCX_HAS_OP(sch, cgroup_move) &&
- !WARN_ON_ONCE(!p->scx.cgrp_moving_from))
+ !SCHED_WARN_ON_ONCE(!p->scx.cgrp_moving_from))
SCX_CALL_OP_TASK(sch, cgroup_move, task_rq(p),
p, p->scx.cgrp_moving_from,
tg_cgrp(task_group(p)));
@@ -5711,7 +5711,7 @@ static void scx_sub_disable(struct scx_sched *sch)
* By the time control reaches here, all descendant schedulers
* should already have been disabled.
*/
- WARN_ON_ONCE(!scx_task_on_sched(sch, p));
+ SCHED_WARN_ON_ONCE(!scx_task_on_sched(sch, p));
/*
* If $p is about to be freed, nothing prevents $sch from
@@ -5913,7 +5913,7 @@ static void scx_root_disable(struct scx_sched *sch)
scoped_guard(rq_lock_irqsave, rq) {
update_rq_clock(rq);
if (was_switched_all) {
- if (WARN_ON_ONCE(dl_server_swap_bw(&rq->ext_server,
+ if (SCHED_WARN_ON_ONCE(dl_server_swap_bw(&rq->ext_server,
&rq->fair_server)))
pr_warn("failed to re-attach fair_server on CPU %d\n", cpu);
} else {
@@ -7068,7 +7068,7 @@ static bool assert_task_ready_or_enabled(struct task_struct *p)
case SCX_TASK_ENABLED:
return true;
default:
- WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched",
+ SCHED_WARN_ONCE(true, "sched_ext: Invalid task state %d for %s[%d] during enabling sub sched",
state, p->comm, p->pid);
return false;
}
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 7/9] sched/core_sched: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
` (5 preceding siblings ...)
2026-06-11 2:14 ` [PATCH 6/9] sched_ext: " Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 8/9] sched/deadline: " Rik van Riel
` (2 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/core_sched.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/core_sched.c b/kernel/sched/core_sched.c
index 43e0bde3038e..e39fb8935ef7 100644
--- a/kernel/sched/core_sched.c
+++ b/kernel/sched/core_sched.c
@@ -67,7 +67,7 @@ static unsigned long sched_core_update_cookie(struct task_struct *p,
* a cookie until after we've removed it, we must have core scheduling
* enabled here.
*/
- WARN_ON_ONCE((p->core_cookie || cookie) && !sched_core_enabled(rq));
+ SCHED_WARN_ON_ONCE((p->core_cookie || cookie) && !sched_core_enabled(rq));
if (sched_core_enqueued(p))
sched_core_dequeue(rq, p, DEQUEUE_SAVE);
@@ -249,7 +249,7 @@ void __sched_core_account_forceidle(struct rq *rq)
lockdep_assert_rq_held(rq);
- WARN_ON_ONCE(!rq->core->core_forceidle_count);
+ SCHED_WARN_ON_ONCE(!rq->core->core_forceidle_count);
if (rq->core->core_forceidle_start == 0)
return;
@@ -260,7 +260,7 @@ void __sched_core_account_forceidle(struct rq *rq)
rq->core->core_forceidle_start = now;
- if (WARN_ON_ONCE(!rq->core->core_forceidle_occupation)) {
+ if (SCHED_WARN_ON_ONCE(!rq->core->core_forceidle_occupation)) {
/* can't be forced idle without a running task */
} else if (rq->core->core_forceidle_count > 1 ||
rq->core->core_forceidle_occupation > 1) {
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 8/9] sched/deadline: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
` (6 preceding siblings ...)
2026-06-11 2:14 ` [PATCH 7/9] sched/core_sched: " Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 2:14 ` [PATCH 9/9] sched/rt: " Rik van Riel
2026-06-11 7:43 ` [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Peter Zijlstra
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/cpudeadline.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/cpudeadline.c b/kernel/sched/cpudeadline.c
index 0a2b7e30fd10..271f28b87a4e 100644
--- a/kernel/sched/cpudeadline.c
+++ b/kernel/sched/cpudeadline.c
@@ -149,7 +149,7 @@ int cpudl_find(struct cpudl *cp, struct task_struct *p,
} else {
int best_cpu = cpudl_maximum(cp);
- WARN_ON(best_cpu != -1 && !cpu_present(best_cpu));
+ SCHED_WARN_ON(best_cpu != -1 && !cpu_present(best_cpu));
if (cpumask_test_cpu(best_cpu, &p->cpus_mask) &&
dl_time_before(dl_se->deadline, cp->elements[0].dl)) {
@@ -177,7 +177,7 @@ void cpudl_clear(struct cpudl *cp, int cpu, bool online)
int old_idx, new_cpu;
unsigned long flags;
- WARN_ON(!cpu_present(cpu));
+ SCHED_WARN_ON(!cpu_present(cpu));
raw_spin_lock_irqsave(&cp->lock, flags);
@@ -220,7 +220,7 @@ void cpudl_set(struct cpudl *cp, int cpu, u64 dl)
int old_idx;
unsigned long flags;
- WARN_ON(!cpu_present(cpu));
+ SCHED_WARN_ON(!cpu_present(cpu));
raw_spin_lock_irqsave(&cp->lock, flags);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 9/9] sched/rt: defer WARN console output under rq->lock
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
` (7 preceding siblings ...)
2026-06-11 2:14 ` [PATCH 8/9] sched/deadline: " Rik van Riel
@ 2026-06-11 2:14 ` Rik van Riel
2026-06-11 7:43 ` [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Peter Zijlstra
9 siblings, 0 replies; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 2:14 UTC (permalink / raw)
To: linux-kernel
Cc: kernel-team, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid, Rik van Riel
Convert the WARN*() calls that run under rq->lock or ->pi_lock to the
SCHED_WARN*() variants, so their console output is deferred to irq_work
instead of being emitted synchronously (which can deadlock via
console_unlock() -> up(&console_sem) -> try_to_wake_up() while the lock
is held).
This should prevent a deadlock if these warnings fire with a legacy
or boot console configured.
Signed-off-by: Rik van Riel <riel@surriel.com>
Assisted-by: Claude:claude-opus-4-8
---
kernel/sched/cpupri.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/sched/cpupri.c b/kernel/sched/cpupri.c
index 8f2237e8b484..5082fb228a63 100644
--- a/kernel/sched/cpupri.c
+++ b/kernel/sched/cpupri.c
@@ -149,7 +149,7 @@ int cpupri_find_fitness(struct cpupri *cp, struct task_struct *p,
int task_pri = convert_prio(p->prio);
int idx, cpu;
- WARN_ON_ONCE(task_pri >= CPUPRI_NR_PRIORITIES);
+ SCHED_WARN_ON_ONCE(task_pri >= CPUPRI_NR_PRIORITIES);
for (idx = 0; idx < task_pri; idx++) {
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON)
2026-06-11 2:14 [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Rik van Riel
` (8 preceding siblings ...)
2026-06-11 2:14 ` [PATCH 9/9] sched/rt: " Rik van Riel
@ 2026-06-11 7:43 ` Peter Zijlstra
2026-06-11 16:20 ` Rik van Riel
9 siblings, 1 reply; 15+ messages in thread
From: Peter Zijlstra @ 2026-06-11 7:43 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, kernel-team, mingo, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid
Sorry, no. I've said it before and I'll stick with it. Just no.
printk_deferred() is an abomination, it means that if you mess up the
machine properly you'll *NEVER* see the output.
As per always, printk() is the one that needs fixing, and IIRC they were
very close to getting there.
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON)
2026-06-11 7:43 ` [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON) Peter Zijlstra
@ 2026-06-11 16:20 ` Rik van Riel
2026-06-11 19:19 ` Peter Zijlstra
0 siblings, 1 reply; 15+ messages in thread
From: Rik van Riel @ 2026-06-11 16:20 UTC (permalink / raw)
To: Peter Zijlstra
Cc: linux-kernel, kernel-team, mingo, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid
On Thu, 2026-06-11 at 09:43 +0200, Peter Zijlstra wrote:
>
> Sorry, no. I've said it before and I'll stick with it. Just no.
>
> printk_deferred() is an abomination, it means that if you mess up the
> machine properly you'll *NEVER* see the output.
>
> As per always, printk() is the one that needs fixing, and IIRC they
> were
> very close to getting there.
Printk to certain console types is always deferred,
by default, because trying to synchronously print
everything to a slow serial console can lead to a
system softlockup panic.
In fact, this particular lockup is due to the
printk being passed off to a worker thread, and
the kernel deadlocking when the wakeup code
tries to grab the runqueue lock its CPU already
holds.
You are right that this could be fixed in the
printk code, but the solution there will by
necessity continue to contain some deferring.
I suppose the printk code could use something
like an irq work to wake up the printk worker,
and avoid the scheduler deadlock that way?
I'm not sure that would make things more
reliable, though...
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON)
2026-06-11 16:20 ` Rik van Riel
@ 2026-06-11 19:19 ` Peter Zijlstra
2026-06-12 1:53 ` Rik van Riel
0 siblings, 1 reply; 15+ messages in thread
From: Peter Zijlstra @ 2026-06-11 19:19 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, kernel-team, mingo, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid
On Thu, Jun 11, 2026 at 12:20:27PM -0400, Rik van Riel wrote:
> On Thu, 2026-06-11 at 09:43 +0200, Peter Zijlstra wrote:
> >
> > Sorry, no. I've said it before and I'll stick with it. Just no.
> >
> > printk_deferred() is an abomination, it means that if you mess up the
> > machine properly you'll *NEVER* see the output.
> >
> > As per always, printk() is the one that needs fixing, and IIRC they
> > were
> > very close to getting there.
>
> Printk to certain console types is always deferred,
> by default, because trying to synchronously print
> everything to a slow serial console can lead to a
> system softlockup panic.
>
> In fact, this particular lockup is due to the
> printk being passed off to a worker thread, and
> the kernel deadlocking when the wakeup code
> tries to grab the runqueue lock its CPU already
> holds.
>
> You are right that this could be fixed in the
> printk code, but the solution there will by
> necessity continue to contain some deferring.
>
>
> I suppose the printk code could use something
> like an irq work to wake up the printk worker,
> and avoid the scheduler deadlock that way?
>
> I'm not sure that would make things more
> reliable, though...
The non-atomic consoles will always need a buffer, but atomic consoles
can (and should IMO) push out the messages immediately.
Anyway, the thing that keeps tripping is that console_sem thing, that
should just entirely go away. That thing ends up doing a wakeup from
printk() call context, which obviously doesn't work when inside the
scheduler locks.
So printk should:
- stick msg in buffer (lockless)
- print to atomic consoles (lockless)
- use irq_work to wake console kthreads (lockless)
- each kthread then tries to flush buffer to its own non-atomic console
in non-atomic context.
From what I understand, we're very close to having this work. The only
disagreement between printk people an me is about defaults IIRC. I want
to have the serial console default to atomic, they want it to be an
option. But whatever, as long as I can specify on the kernel cmdline
that my serial should be atomic I'm good.
Myself, I almost exclusively run with earlycon serial and force printk
to be early_printk() (effectively not using printk at all). This works
perfectly fine and is the most reliable thing ever. I can push out
characters to the UART from any context.
And sure, sometimes its gets a little scrambled, but meh.
I have this working with real actual serial, IPMI/serial-over-lan and
AMT/serial-over-lan.
And if a machine doesn't have serial, its a paperweight ;-)
Now, of course I also use trace_printk() a lot, but for those moments
when the machine goes down hard, nothing beats serial.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON)
2026-06-11 19:19 ` Peter Zijlstra
@ 2026-06-12 1:53 ` Rik van Riel
2026-06-12 6:52 ` Peter Zijlstra
0 siblings, 1 reply; 15+ messages in thread
From: Rik van Riel @ 2026-06-12 1:53 UTC (permalink / raw)
To: Peter Zijlstra
Cc: linux-kernel, kernel-team, mingo, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid
On Thu, 2026-06-11 at 21:19 +0200, Peter Zijlstra wrote:
>
> Myself, I almost exclusively run with earlycon serial and force
> printk
> to be early_printk() (effectively not using printk at all). This
> works
> perfectly fine and is the most reliable thing ever. I can push out
> characters to the UART from any context.
>
Great for development, not so good for production.
When something like an OOM kill happens, the amount
of data printed by the kernel can take a long time
to get flushed out a serial port.
In fact, it can take long enough to cause things
like RCU stalls and soft lockups, and break
workloads that way.
Having the serial console deferred allows those
systems to survive.
If the system crashes before the messages were sent
out the serial port, the dmesg buffer will be in
the vmcore, and get extracted by the crash analysis
tooling.
It sounds like the printk people may be right with
making that behavior an option, since different
scenarios require different behavior.
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 0/9] sched: make WARN_ON under rq->lock deadlock-safe (SCHED_WARN_ON)
2026-06-12 1:53 ` Rik van Riel
@ 2026-06-12 6:52 ` Peter Zijlstra
0 siblings, 0 replies; 15+ messages in thread
From: Peter Zijlstra @ 2026-06-12 6:52 UTC (permalink / raw)
To: Rik van Riel
Cc: linux-kernel, kernel-team, mingo, juri.lelli, vincent.guittot,
dietmar.eggemann, vschneid
On Thu, Jun 11, 2026 at 09:53:45PM -0400, Rik van Riel wrote:
> On Thu, 2026-06-11 at 21:19 +0200, Peter Zijlstra wrote:
> >
> > Myself, I almost exclusively run with earlycon serial and force
> > printk
> > to be early_printk() (effectively not using printk at all). This
> > works
> > perfectly fine and is the most reliable thing ever. I can push out
> > characters to the UART from any context.
> >
> Great for development, not so good for production.
>
> When something like an OOM kill happens, the amount
> of data printed by the kernel can take a long time
> to get flushed out a serial port.
>
> In fact, it can take long enough to cause things
> like RCU stalls and soft lockups, and break
> workloads that way.
>
> Having the serial console deferred allows those
> systems to survive.
>
> If the system crashes before the messages were sent
> out the serial port, the dmesg buffer will be in
> the vmcore, and get extracted by the crash analysis
> tooling.
>
> It sounds like the printk people may be right with
> making that behavior an option, since different
> scenarios require different behavior.
I've not hit OOM in a decade or so; I've no idea how much silly nonsense
it spouts. The example the printk people have used for a long while is
booting with a million block devices, apparently that generates insane
amount of boot noise -- another problem I don't have :-)
Anyway, one solution there is to create a threshold and say ERR and
above go directly to atomic console while everything below goes into the
buffer and gets spooled out later on the non-atomic variant.
This way all your WARN/BUG and other useful bits actually hit the wire
when you need them, and the useless verbiage goes into the bin for
later.
Anyway, all this is stuff that seems trivially solvable.
^ permalink raw reply [flat|nested] 15+ messages in thread