A PREEMPT_RT kernel with debug checks enabled printed a sleeping function warning. The warning came from the console printing thread, not from a driver under test, and its call trace ran through the virtual terminal and framebuffer console code. Three parts of that path could have been at fault, and the maintainers who looked at it did not agree at first on which one it was.
The sleeping function warning report
The kernel reports what it is doing through printk(), its counterpart of printf(). The messages go into a buffer and are then written to consoles, which are output devices such as a serial port or the screen. When the console is the screen, two pieces of code draw the text: the virtual terminal code (vt) keeps track of the text and the cursor, and the framebuffer console code (fbcon) draws the characters on the display. On a PREEMPT_RT kernel, a configuration that makes almost all kernel code preemptible so that a high-priority task can run sooner, printing to legacy consoles is done by a separate kernel thread. A kernel thread is a task that runs only kernel code and, like a user process, may wait for events. This one is called the legacy printing thread in this article, and the patch discussed below states that it is enabled only on PREEMPT_RT builds.
In January 2026 Sebastian Andrzej Siewior (Linutronix) posted a report to the kernel mailing list titled “printk’s threaded legacy console + fbcon => schedule where it should not”. The kernel was a PREEMPT_RT build with debug code enabled. The first line of the warning and the functions in its call trace were as follows (addresses and offsets are omitted here).
BUG: sleeping function called from invalid context at kernel/printk/printk.c:3377
[…]
dump_stack_lvl
__might_resched.cold
console_conditional_schedule
fbcon_redraw
fbcon_scroll
con_scroll
lf
vt_console_print
console_flush_one_record
legacy_kthread_func
kthread
ret_from_fork
ret_from_fork_asmThe report lists three held locks: console_lock, console_srcu and printing_lock. Read from the bottom, the legacy printing thread flushes a record, the virtual terminal console callback vt_console_print() scrolls the screen, the framebuffer console redraws it, and console_conditional_schedule() raises the warning.
What the check reports
Some kernel code must not wait for anything. Interrupt handlers and code that holds a spinlock are examples. Such code is said to run in atomic context, and if it called a function that can put the task to sleep, the system could stall or deadlock. To find these mistakes, the option CONFIG_DEBUG_ATOMIC_SLEEP makes functions that may sleep check their own context. Its Kconfig help text says that routines which may sleep are reported when called inside atomic sections, for example while a spinlock is held. When the check fails, the kernel prints BUG: sleeping function called from invalid context with a call trace. This message is the sleeping function warning. It is a report about the context and does not mean that the system stopped.
One common place for the check is cond_resched(), a scheduling point where a running task offers the processor to another task that is waiting. It may only be called where sleeping is allowed. In include/linux/sched.h, the cond_resched() macro calls __might_resched() first and only then _cond_resched(). The check therefore reports a violation even when no reschedule would have happened.
The first lines of a sleeping function warning carry more information than the call trace, and they are the first thing to read. In v6.18 the kernel prints them from __might_resched() in kernel/sched/core.c in this form:
BUG: sleeping function called from invalid context at FILE:LINE
in_atomic(): N, irqs_disabled(): N, non_block: N, pid: N, name: COMM
preempt_count: N, expected: N
RCU nest depth: N, expected: NThe first line names the call site that performed the check. The second line says whether the task is in atomic context and whether interrupts are disabled. The third line compares the current preemption count with the value the call site expects. The fourth line does the same for RCU read-side sections and appears only when CONFIG_PREEMPT_RCU is enabled. After these lines the kernel prints the locks held by the task. Together they show what kind of atomic context the task is in, before any code is read.
raghu@techveda.org:~$ zcat /proc/config.gz | grep -E 'CONFIG_PREEMPT_RT=|CONFIG_DEBUG_ATOMIC_SLEEP='
CONFIG_PREEMPT_RT=y
CONFIG_DEBUG_ATOMIC_SLEEP=yA kernel with both options can produce this report. The warning shows where the rule was broken, but not which owner along the call trace should change.
Three explanations to test
- The printing thread is in a state where it should not call console drivers.
- The lock taken in
vt_console_print()is at fault, because it creates the atomic section. - The scheduling call itself is at fault, and it does not belong on this path.
Each explanation would explain a sleeping function warning of this kind, so the evidence has to separate them. The function appears at seven call sites in v6.18, one in the virtual terminal code and six in the framebuffer console:
raghu@techveda.org:~$ grep -rn console_conditional_schedule linux/drivers/
linux/drivers/tty/vt/vt.c:3239: console_conditional_schedule();
linux/drivers/video/fbdev/core/fbcon.c:1589: console_conditional_schedule();
linux/drivers/video/fbdev/core/fbcon.c:1594: console_conditional_schedule();
linux/drivers/video/fbdev/core/fbcon.c:1630: console_conditional_schedule();
linux/drivers/video/fbdev/core/fbcon.c:1637: console_conditional_schedule();
linux/drivers/video/fbdev/core/fbcon.c:1685: console_conditional_schedule();
linux/drivers/video/fbdev/core/fbcon.c:1691: console_conditional_schedule();The printing thread
The legacy printing thread is an ordinary kernel thread. In v6.18 it takes the console lock and flushes. The source excerpts in this article are from the v6.18 tag, while the report was made against a newer development tree.
static int legacy_kthread_func(void *unused)
{
for (;;) {
wait_event_interruptible(legacy_wait, legacy_kthread_should_wakeup());
if (kthread_should_stop())
break;
console_lock();
__console_flush_and_unlock();
}
return 0;
}The function console_lock() can sleep, and it sets console_may_schedule = 1. A kernel thread in process context may sleep, so the thread is not the defect. The report states what the flag means here:
“legacy_kthread_func() does console_lock() which means console_may_schedule is 1.”
— Sebastian Andrzej Siewior, report of 14 January 2026
The first explanation is eliminated. The flag is correct for the thread, so the question moves to what the thread calls while the flag is set.
The lock
The function vt_console_print() takes a spinlock_t before it draws, and a source comment gives its purpose:
static DEFINE_SPINLOCK(printing_lock);
[…]
/* this protects against concurrent oops only */
if (!spin_trylock(&printing_lock))
return;Petr Mladek, a printk maintainer, treated the lock as the part to change, and his reply included an alternative patch:
“Alternative solution is to get rid of the spin_trylock().”
— Petr Mladek, reply in the same thread
Removing the lock would remove the atomic section. The scheduling call would remain, on a path where, as the next section shows, it has no effect. Siewior argued against keeping code only because it might be needed:
“So why do we keep warts again? Just because it *might* be required?”
— Sebastian Andrzej Siewior, same thread
The scheduling call
In v6.18 the function reads:
void __sched console_conditional_schedule(void)
{
if (console_may_schedule)
cond_resched();
}
EXPORT_SYMBOL(console_conditional_schedule);When printk() prints directly, the console is taken with a trylock and the call does nothing. Mladek confirmed this when he reviewed the later patch:
“printk() does console_trylock() and calls console_unlock() with preemption disabled anyway, see vprintk_emit().”
— Petr Mladek, review of 28 January 2026
On the printk path the call has no effect, and on the legacy thread path it triggers the sleeping function warning. The remaining question was whether any other caller needed it. Steven Rostedt checked the history:
“Egad! That goes all the way back to 2002:”
“Should we just remove it and see what breaks?”
— Steven Rostedt, reply of 20 January 2026
Mladek was cautious because the virtual terminal code also serves ordinary terminal writes. After the patch was posted he accepted the removal on one condition, that it be tested:
“It is possible that some non-printk related operations rely on this.”
“It might actually be a good idea to find it out.”
— Petr Mladek, review of 28 January 2026
He added that he had seen many printk-related soft lockups, but that they were always caused by slow serial consoles and he could not remember any in the virtual terminal code.
Root cause
The cause is the third explanation. The function console_conditional_schedule() decides whether to sleep from a global flag that records how the console lock was taken. It does not look at the context of its caller. In terminal writes the console lock is held in process context and a reschedule is permitted. In vt_console_print(), a console driver callback, the code runs under a spinlock_t, where a voluntary reschedule is not permitted. On the printk() path the flag is clear. On the legacy thread path, which exists only on PREEMPT_RT, the flag is set, so the check reports the call. The patch states it in its commit message:
“vt_console_print() acquires a spinlock_t which does not allow a voluntary schedule.”
“!PREEMPT_RT is not affected because the legacy printing thread is only enabled on PREEMPT_RT builds.”
— Sebastian Andrzej Siewior, patch of 26 January 2026
The commit message gives the reason the call is not needed: the flush loop already yields between lines. The patch carries a Fixes tag for the commit that introduced the legacy printer thread. A later reporter names another commit as the point where the warning first appeared, “printk: Use console_flush_one_record for legacy printer kthread”, which changed the thread to flush one record at a time and release the console lock between records.
The fix and confirmation
The patch removes console_conditional_schedule(), its export and its seven call sites. Helge Deller, the framebuffer maintainer, took it into the fbdev tree:
“I’ve added it to the fbdev git tree to get some testing….”
— Helge Deller, reply of 27 January 2026
The function is no longer present in the v7.0 tag of kernel/printk/printk.c. On 17 February 2026 a second reporter, Nam Cao, hit the same sleeping function warning on a 6.19 kernel in a RISC-V emulator, at kernel/printk/printk.c:3431. Mladek pointed to the pending change, and Nam Cao confirmed the next day that the warning no longer appeared with it applied.
The locking rules for callbacks like vt_console_print() are part of the driver-layer material in TECH VEDA’s Linux device driver course.
How the maintainers reached the answer
The fix in this story was small, but the way the maintainers reached it can be reused. Four points stand out.
- A sleeping function warning names a call site, not a culprit. The trace crossed three owners, and each of them could have been blamed. The warning only says that the rule was broken at one place.
- A check can fire even when the code would never have slept.
cond_resched()runs the sleep check before it decides whether to reschedule, so a call with no practical effect can still be reported. - A flag describes how something was acquired, not where the code runs. The decision to sleep was taken from a global flag that recorded how the console lock was taken. The code reading the flag was in a different context.
- Old code is removed on evidence, not on belief. The maintainers checked the history of the function, its callers, the code that already did the same job, and who could test the removal.
A method you can reuse
The same steps apply to a sleeping function warning and to other warnings that report a context violation, such as a lockdep report or a preemption count mismatch.
- Read the header lines of the sleeping function warning first. Note
in_atomic(),irqs_disabled(), the preemption count against the expected value, the RCU nesting depth and the held locks. These say which kind of atomic context applies. - Turn the call trace into a list of owners. Read it from the bottom. Write down which subsystem or driver owns each frame. In this case the owners were the printk thread, the virtual terminal code and the framebuffer console.
- Write one explanation per owner, with the evidence that would confirm or reject it. Test the explanation that is cheapest to check first. Here the first was settled by reading about eight lines of thread code.
- For the suspect function, ask four questions. Who sets the state it depends on? Which callers are allowed to be in that state? What does the function assume about its caller? Is the function needed at all?
- Check the history and the callers before changing or deleting code. Search for every call site and look at when and why the code was added:
raghu@techveda.org:~$ grep -rn console_conditional_schedule linux/ raghu@techveda.org:~$ git -C linux log --oneline -S console_conditional_schedule -- kernel/printk/printk.c - Fix the cause at the layer that owns it. Silencing the warning with a workaround leaves the faulty assumption in place. In this case the cause was a scheduling call that duplicated what the flush loop already did, so removing it was the complete fix.
- Keep the check enabled where the bug can be seen. The sleeping function warning appeared only on a PREEMPT_RT build with sleep checking enabled. Test builds should include the options that expose such problems:
raghu@techveda.org:~$ grep -E 'CONFIG_(PREEMPT_RT|DEBUG_ATOMIC_SLEEP|PROVE_LOCKING)=' linux/.config
When a warning arrives as a raw trace with addresses, the script scripts/decode_stacktrace.sh in the kernel source tree converts the addresses to function names and source lines. It reads the trace from standard input and needs the vmlinux file of the same build, built with debug information:
raghu@techveda.org:~$ ./linux/scripts/decode_stacktrace.sh linux/vmlinux < oops.txtBest practice from this case
The practical rule behind every sleeping function warning is this: do not decide whether code may sleep from a flag that was set elsewhere. A flag describes how a resource was acquired. It does not describe the context of every function that reads it. Either let each caller state its own context, or remove the operation if the surrounding code already provides it. When you propose removing old code, record four checks in the review: its history, all of its callers, the mechanism that replaces it, and the tree where it will be tested. The maintainers in this story followed a similar order.
Try it yourself
Take a kernel source tree and choose any function that can raise a sleeping function warning, that is, one that calls cond_resched() or might_sleep(). Search for its callers, then write down for each caller whether it can run with a spinlock held or with interrupts disabled. Compare your list with the call sites you find. If one caller cannot sleep, you have found the same kind of defect that this article describes.
Frequently asked questions
What caused the sleeping function warning in the console path?
The function console_conditional_schedule() reschedules whenever the console_may_schedule flag is set. The legacy printing thread sets that flag through console_lock(), and it then reached vt_console_print(), which holds a spinlock_t where a voluntary reschedule is not permitted.
How was it fixed?
Sebastian Andrzej Siewior removed console_conditional_schedule(), its export and its seven call sites. The commit message explains that the console flush loop already yields between lines, so the call was redundant.
Does the warning affect kernels that are not PREEMPT_RT?
The patch states that it does not, because the legacy printing thread is only enabled on PREEMPT_RT builds.
Which kernel option provides the sleep check?
CONFIG_DEBUG_ATOMIC_SLEEP provides the sleep-in-atomic check. The patch describes the affected build as PREEMPT_RT with debug code enabled.
How can I investigate a similar warning on my own system?
Read the header lines of the warning, list the owner of each frame in the call trace, write one explanation per owner with the evidence that would settle it, and check the history and callers of the suspect function before changing it. Keep CONFIG_DEBUG_ATOMIC_SLEEP and CONFIG_PROVE_LOCKING enabled in test builds so that such problems are reported.
Further reading
- Sebastian Andrzej Siewior, “printk’s threaded legacy console + fbcon => schedule where it should not” (the original report and thread)
- The patch “printk, vt, fbcon: Remove console_conditional_schedule()”
- Petr Mladek’s review of the patch
- Steven Rostedt’s reply on the history of the function
- Nam Cao’s later report, “printk, vt: sleep from invalid context bug”
- Kernel documentation: Lock types and their rules
- kernel/sched/core.c at v6.18 (the __might_resched() message format)
- scripts/decode_stacktrace.sh at v6.18
- kernel/printk/printk.c at v6.18




