[PATCH RFC 3/4] printk: nbcon: move printk_delay to console emiting code

From: Andrew Murray

Date: Sun May 31 2026 - 19:21:25 EST


The printk_delay and boot_delay features are helpful for debugging
as kernel output can be slowed down during boot allowing messages to
be seen before scrolling off the screen, or to correlate timing between
some physical event and console output.

However, since the introduction of nbcon and the legacy printer thread
for PREEMPT_RT kernels, printk records are now emited to the console
asynchronously to the caller of printk. Thus, any printk delay added by
boot_delay/printk_delay continues to slow down the calling process but
may not have any impact to the rate in which records are emited to the
console.

Let's address this by moving the printk delay from the calling code
to the console emiting code instead. Whilst this ensures that delays
are still observed (especially for slower consoles), it doesn't improve
the use-case of using boot_delay/printk_delay to correlate timings
between physical events and console output.

Signed-off-by: Andrew Murray <amurray@xxxxxxxxxxxxxxxxxxxx>
---
include/linux/printk.h | 4 ++++
kernel/printk/nbcon.c | 13 ++++++++++++-
kernel/printk/printk.c | 15 +++++++--------
3 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/include/linux/printk.h b/include/linux/printk.h
index f594c1266bfd411f2238b45374e8a71222f0407c..8ea3ac8201ad541912547305e241e3661f4d8e05 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -209,6 +209,7 @@ extern bool nbcon_device_try_acquire(struct console *con);
extern void nbcon_device_release(struct console *con);
void nbcon_atomic_flush_unsafe(void);
bool pr_flush(int timeout_ms, bool reset_on_progress);
+void printk_delay(bool use_atomic);
#else
static inline __printf(1, 0)
int vprintk(const char *s, va_list args)
@@ -326,6 +327,9 @@ static inline bool pr_flush(int timeout_ms, bool reset_on_progress)
{
return true;
}
+static inline void printk_delay(bool use_atomic)
+{
+}

#endif

diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c
index d7044a7a214bdd4537a5e20d876d99bc3ffe8b3a..a507a2fed5bf4366e24330f763b842a698ecf6f7 100644
--- a/kernel/printk/nbcon.c
+++ b/kernel/printk/nbcon.c
@@ -1267,11 +1267,16 @@ static int nbcon_kthread_func(void *__console)

con_flags = console_srcu_read_flags(con);

+ wctxt.len = 0;
+
if (console_is_usable(con, con_flags, false))
backlog = nbcon_emit_one(&wctxt, false);

console_srcu_read_unlock(cookie);

+ if (backlog && wctxt.len > 0)
+ printk_delay(false);
+
cond_resched();

} while (backlog);
@@ -1525,6 +1530,8 @@ bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
}

progress = nbcon_emit_one(&wctxt, use_atomic);
+ if (progress && wctxt.len > 0)
+ printk_delay(use_atomic);

if (use_atomic) {
start_critical_timings();
@@ -1584,6 +1591,8 @@ static int __nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq)
if (!nbcon_context_try_acquire(ctxt, false))
return -EPERM;

+ wctxt.len = 0;
+
/*
* nbcon_emit_next_record() returns false when
* the console was handed over or taken over.
@@ -1595,7 +1604,9 @@ static int __nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq)
nbcon_context_release(ctxt);
}

- if (!ctxt->backlog) {
+ if (ctxt->backlog && wctxt.len > 0) {
+ printk_delay(true);
+ } else {
/* Are there reserved but not yet finalized records? */
if (nbcon_seq_read(con) < stop_seq)
err = -ENOENT;
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index ca510b9c76458f5c1e059200060d9d6be4c859b3..5154da85537438905231f829ed8f2ea2550daf77 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -2140,18 +2140,17 @@ static inline void late_boot_delay_msec(void)
}
}

-static inline void printk_delay(int level)
+void printk_delay(bool use_atomic)
{
- bool suppress = !is_printk_force_console() &&
- suppress_message_printing(level);
-
- if (likely(!printk_delay_msec) || suppress)
+ if (likely(!printk_delay_msec))
return;

if (system_state < SYSTEM_RUNNING)
early_boot_delay_msec();
- else
+ else if (use_atomic)
late_boot_delay_msec();
+ else
+ msleep(printk_delay_msec);
}

#define CALLER_ID_MASK 0x80000000
@@ -2471,8 +2470,6 @@ asmlinkage int vprintk_emit(int facility, int level,
ft.legacy_direct = false;
}

- printk_delay(level);
-
printed_len = vprintk_store(facility, level, dev_info, fmt, args);

if (ft.nbcon_atomic)
@@ -3171,6 +3168,8 @@ static bool console_emit_next_record(struct console *con, bool *handover, int co
con->dropped = 0;
}

+ printk_delay(true);
+
/* Write everything out to the hardware. */

if (force_legacy_kthread() && !panic_in_progress()) {

--
2.34.1