Hi,
legacy_kthread_func() does console_lock() which means
console_may_schedule is 1.
The other path is from vprintk_emit() where we have
if (ft.legacy_direct) {
preempt_disable();
if (console_trylock_spinning())
console_unlock();
preempt_enable();
}
so all printing happens from console_unlock() where
console_may_schedule is 0. This is a small difference. With the legacy
console enabled I get:
| BUG: sleeping function called from invalid context at kernel/printk/printk.c:3377
| in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 15, name: pr/legacy
| preempt_count: 1, expected: 0
| RCU nest depth: 0, expected: 0
| 3 locks held by pr/legacy/15:
| #0: ffffffffa8aebac0 (console_lock){+.+.}-{0:0}, at: legacy_kthread_func+0x6c/0x130
| #1: ffffffffa8aebb18 (console_srcu){....}-{0:0}, at: console_flush_one_record+0x7e/0x4d0
| #2: ffffffffa8c49818 (printing_lock){+.+.}-{3:3}, at: vt_console_print+0x55/0x490
| Preemption disabled at:
| [<0000000000000000>] 0x0
| CPU: 7 UID: 0 PID: 15 Comm: pr/legacy Not tainted 6.19.0-rc5+ #19 PREEMPT(lazy)
| Hardware name: To Be Filled By O.E.M. To Be Filled By O.E.M./Z68 Pro3-M, BIOS P2.30 06/29/2012
| Call Trace:
| <TASK>
| dump_stack_lvl+0x68/0x90
| __might_resched.cold+0xf0/0x12b
| console_conditional_schedule+0x27/0x30
| fbcon_redraw+0xa0/0x240
| fbcon_scroll+0x164/0x1c0
| con_scroll+0xfa/0x200
| lf+0xa5/0xb0
| vt_console_print+0x313/0x490
| console_flush_one_record+0x2a0/0x4d0
| legacy_kthread_func+0x83/0x130
| kthread+0x118/0x250
| ret_from_fork+0x309/0x3b0
| ret_from_fork_asm+0x1a/0x30
| </TASK>
because vt_console_print() acquires a spin_lock for synchronisation
against another caller while console_conditional_schedule() would like
to schedule.
Most callers of console_unlock() do trylock except for few such as
__pr_flush() which are affected by this the same way as the legacy
printing thread. But we don't have much pr_flush() so this is hidden.
Is there a strict need for fbcon_scroll() to schedule in fbcon_redraw()?
From a quick look it looks that intense callers such the printk flush do
cond_resched() on their own and tty does it, too
| fbcon_scroll+0x164/0x1c0
| con_scroll+0xfa/0x200
| lf+0xa5/0xb0
| do_con_write+0xc68/0x2630
| con_write+0xf/0x40
| do_output_char+0x180/0x1e0
| n_tty_write+0x1ba/0x580
| file_tty_write.isra.0+0x17e/0x2c0
the cond_resched() is in file_tty_write()/ iterate_tty_write().
Therefore I would suggest to simply
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 59b4b5e126ba1..53daf7614b1af 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -3236,7 +3236,6 @@ static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
goto rescan_last_byte;
}
con_flush(vc, &draw);
- console_conditional_schedule();
notify_update(vc);
return n;
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 7be9e865325d9..36dd9d4a46ae0 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -1607,12 +1607,10 @@ static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
start = s;
}
}
- console_conditional_schedule();
s++;
} while (s < le);
if (s > start)
fbcon_putcs(vc, start, s - start, dy, x);
- console_conditional_schedule();
dy++;
}
}
@@ -1648,14 +1646,12 @@ static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info,
}
scr_writew(c, d);
- console_conditional_schedule();
s++;
d++;
} while (s < le);
if (s > start)
par->bitops->bmove(vc, info, line + ycount, x, line, x, 1,
s - start);
- console_conditional_schedule();
if (ycount > 0)
line++;
else {
@@ -1703,13 +1699,11 @@ static void fbcon_redraw(struct vc_data *vc, int line, int count, int offset)
}
}
scr_writew(c, d);
- console_conditional_schedule();
s++;
d++;
} while (s < le);
if (s > start)
fbcon_putcs(vc, start, s - start, line, x);
- console_conditional_schedule();
if (offset > 0)
line++;
else {
diff --git a/include/linux/console.h b/include/linux/console.h
index fc9f5c5c1b04c..ec506d3501965 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -697,7 +697,6 @@ extern int unregister_console(struct console *);
extern void console_lock(void);
extern int console_trylock(void);
extern void console_unlock(void);
-extern void console_conditional_schedule(void);
extern void console_unblank(void);
extern void console_flush_on_panic(enum con_flush_mode mode);
extern struct tty_driver *console_device(int *);
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 1d765ad242b82..52b1fefdff4e0 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -3362,22 +3362,6 @@ void console_unlock(void)
}
EXPORT_SYMBOL(console_unlock);
-/**
- * console_conditional_schedule - yield the CPU if required
- *
- * If the console code is currently allowed to sleep, and
- * if this CPU should yield the CPU to another task, do
- * so here.
- *
- * Must be called within console_lock();.
- */
-void __sched console_conditional_schedule(void)
-{
- if (console_may_schedule)
- cond_resched();
-}
-EXPORT_SYMBOL(console_conditional_schedule);
-
void console_unblank(void)
{
bool found_unblank = false;
Sebastian
On Wed, 14 Jan 2026 15:59:55 +0100
Sebastian Andrzej Siewior <bigeasy@linutronix.de> wrote:
> @@ -3362,22 +3362,6 @@ void console_unlock(void)
> }
> EXPORT_SYMBOL(console_unlock);
>
> -/**
> - * console_conditional_schedule - yield the CPU if required
Egad! That goes all the way back to 2002:
https://git.kernel.org/pub/scm/linux/kernel/git/history/history.git/commit/?id=a880f45a48be2956d2c78a839c472287d54435c1
> - *
> - * If the console code is currently allowed to sleep, and
> - * if this CPU should yield the CPU to another task, do
> - * so here.
> - *
> - * Must be called within console_lock();.
> - */
> -void __sched console_conditional_schedule(void)
> -{
> - if (console_may_schedule)
> - cond_resched();
> -}
> -EXPORT_SYMBOL(console_conditional_schedule);
I'm assuming this likely isn't needed anymore. I don't know of any reason
it needs to stay.
Should we just remove it and see what breaks?
-- Steve
> -
> void console_unblank(void)
> {
> bool found_unblank = false;
On Tue 2026-01-20 11:08:45, Steven Rostedt wrote:
> On Wed, 14 Jan 2026 15:59:55 +0100
> Sebastian Andrzej Siewior <bigeasy@linutronix.de> wrote:
>
> > @@ -3362,22 +3362,6 @@ void console_unlock(void)
> > }
> > EXPORT_SYMBOL(console_unlock);
> >
> > -/**
> > - * console_conditional_schedule - yield the CPU if required
>
> Egad! That goes all the way back to 2002:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/history/history.git/commit/?id=a880f45a48be2956d2c78a839c472287d54435c1
>
> > - *
> > - * If the console code is currently allowed to sleep, and
> > - * if this CPU should yield the CPU to another task, do
> > - * so here.
> > - *
> > - * Must be called within console_lock();.
> > - */
> > -void __sched console_conditional_schedule(void)
> > -{
> > - if (console_may_schedule)
> > - cond_resched();
> > -}
> > -EXPORT_SYMBOL(console_conditional_schedule);
>
> I'm assuming this likely isn't needed anymore. I don't know of any reason
> it needs to stay.
I know that there was a plan to get rid of cond_resched().
But what is the status now, please?
I still see more that 1k cond_resched() calls in the code:
$> git grep cond_resched\(\) | grep "\.c:" | wc -l
1263
And config PREEMPT_VOLUNTARY still talks about the explicit
preemption points.
> Should we just remove it and see what breaks?
Honestly, I do not feel comfortable with removing it. It is true that
it has no effect in the printk() code path. But the vt code is used
also when working on the terminal.
The vt code still uses console_lock() because it was intertwined
with printk console code since very old days. console_lock is a kind
of big kernel lock there.
Alternative solution is to get rid of the spin_trylock(). The only
purpose is to prevent race in console_flush_on_panic(). It used
to be a simple bit operation. The spin_lock() was added just to
get barriers right. But we have a great atomic_t API these days.
IMHO, it is a win-win solution because a preemptive context is
always better.
What about?
From 0fc61b6877e9beb20429effc599bc4bc6ec3a475 Mon Sep 17 00:00:00 2001
From: Petr Mladek <pmladek@suse.com>
Date: Wed, 21 Jan 2026 10:47:15 +0100
Subject: [RFC] tty/vt: Prevent re-entering vt_console_print() in panic()
without spin_lock
The commit b0940003f25dd ("vt: bitlock fix") replaced a simple bit
operation with spin_lock() to get proper memory barriers.
But the code called under this lock calls console_conditional_schedule()
which calls cond_resched() when console_sem() has been acquired
in a preemptive context using console_lock(). Note that the semaphore
can be taken also in an atomic context using console_trylock()
which is used by printk().
One solution would be to remove console_conditional_schedule().
It does not have any effect in the printk() code path anyway.
But the affected VT code is not used just by printk(). And
the cond_resched() calls were likely added for a reason.
Instead, convert the spin_lock back to an atomic operation with
proper barriers. The only purpose of the lock is to prevent
a concurrent access to the guarded code in
console_flush_on_panic() where console_lock() is ignored.
Using a full featured spin_trylock, just to get memory barriers
right, looks like an overkill anyway.
Fixes: b0940003f25dd ("vt: bitlock fix")
Closes: https://lore.kernel.org/all/20260114145955.d924Z-zu@linutronix.de/
Signed-off-by: Petr Mladek <pmladek@suse.com>
---
drivers/tty/vt/vt.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 59b4b5e126ba..5be64d1bba91 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -3353,15 +3353,19 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
{
struct vc_data *vc = vc_cons[fg_console].d;
unsigned char c;
- static DEFINE_SPINLOCK(printing_lock);
+ static atomic_t printing_lock = ATOMIC_INIT(0);
const ushort *start;
ushort start_x, cnt;
int kmsg_console;
WARN_CONSOLE_UNLOCKED();
- /* this protects against concurrent oops only */
- if (!spin_trylock(&printing_lock))
+ /*
+ * Prevent concurrent printing in console_flush_on_panic() where
+ * console_lock is ignored. Easier (serial) console drivers
+ * have bigger chance to get the messages out.
+ */
+ if (atomic_cmpxchg_acquire(&printing_lock, 0, 1) != 0)
return;
kmsg_console = vt_get_kmsg_redirect();
@@ -3422,7 +3426,7 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
notify_update(vc);
quit:
- spin_unlock(&printing_lock);
+ atomic_set_release(&printing_lock, 0);
}
static struct tty_driver *vt_console_device(struct console *c, int *index)
--
2.52.0
Best Regards,
Petr
On 2026-01-21 14:43:45 [+0100], Petr Mladek wrote: > I know that there was a plan to get rid of cond_resched(). > But what is the status now, please? It is slowly moving => https://lore.kernel.org/all/20251219101502.GB1132199@noisy.programming.kicks-ass.net/ > I still see more that 1k cond_resched() calls in the code: > > $> git grep cond_resched\(\) | grep "\.c:" | wc -l > 1263 > > And config PREEMPT_VOLUNTARY still talks about the explicit > preemption points. > > > Should we just remove it and see what breaks? > > Honestly, I do not feel comfortable with removing it. It is true that > it has no effect in the printk() code path. But the vt code is used > also when working on the terminal. > > The vt code still uses console_lock() because it was intertwined > with printk console code since very old days. console_lock is a kind > of big kernel lock there. Do you a have path which loops and would mandate it? I found a few which do not matter and have their own cond_resched() around. So I don't see a reason to keep it. And I found one which breaks things so a removal makes sense. > Alternative solution is to get rid of the spin_trylock(). The only > purpose is to prevent race in console_flush_on_panic(). It used > to be a simple bit operation. The spin_lock() was added just to > get barriers right. But we have a great atomic_t API these days. > > IMHO, it is a win-win solution because a preemptive context is > always better. So why do we keep warts again? Just because it *might* be required? Keeping things preemptible makes sense but this is locking with no annotation what so ever. Again. printk has its cond_resched, the tty has it, too. I'm with Steven on the removal side. Sebastian
On Wed 2026-01-21 14:57:37, Sebastian Andrzej Siewior wrote: > On 2026-01-21 14:43:45 [+0100], Petr Mladek wrote: > > I know that there was a plan to get rid of cond_resched(). > > But what is the status now, please? > > It is slowly moving => https://lore.kernel.org/all/20251219101502.GB1132199@noisy.programming.kicks-ass.net/ Good to know. > > I still see more that 1k cond_resched() calls in the code: > > > > $> git grep cond_resched\(\) | grep "\.c:" | wc -l > > 1263 > > > > And config PREEMPT_VOLUNTARY still talks about the explicit > > preemption points. > > > > > Should we just remove it and see what breaks? > > > > Honestly, I do not feel comfortable with removing it. It is true that > > it has no effect in the printk() code path. But the vt code is used > > also when working on the terminal. > > > > The vt code still uses console_lock() because it was intertwined > > with printk console code since very old days. console_lock is a kind > > of big kernel lock there. > > Do you a have path which loops and would mandate it? I found a few which > do not matter and have their own cond_resched() around. So I don't see a > reason to keep it. And I found one which breaks things so a removal > makes sense. Could anyone from VT guys comment on it, please? > > Alternative solution is to get rid of the spin_trylock(). The only > > purpose is to prevent race in console_flush_on_panic(). It used > > to be a simple bit operation. The spin_lock() was added just to > > get barriers right. But we have a great atomic_t API these days. > > > > IMHO, it is a win-win solution because a preemptive context is > > always better. > > So why do we keep warts again? Just because it *might* be required? > Keeping things preemptible makes sense but this is locking with no > annotation what so ever. Well, the current locking is documented but it creates false positives. The "printing_lock" is taken on a single place using spin_trylock(). Nobody would ever spin on it. So sleeping is perfectly fine. > Again. printk has its cond_resched, the tty has it, too. > I'm with Steven on the removal side. As I said, the cond_resched() does not have any effect from the printk() code path. But the other VT paths might rely on it. If VT-guys are willing to take the risk and remove it then I am fine with it. Best Regards, Petr
On 2026-01-20 11:08:45 [-0500], Steven Rostedt wrote: > > I'm assuming this likely isn't needed anymore. I don't know of any reason > it needs to stay. > > Should we just remove it and see what breaks? I would say so. > -- Steve Sebastian
© 2016 - 2026 Red Hat, Inc.