[PATCH printk 1/3] printk: Introduce console sync mode
From: John Ogness
Date: Fri Jul 10 2026 - 10:47:38 EST
Sometimes it is desirable that console printing occurs synchronously
in the context of the printk() caller. Introduce a "sync mode" for
nbcon consoles that provide safe atomic_write() implementations. A
new console flag CON_SYNC shows if a console is operating in this
mode.
When in sync mode, a console will flush directly within vprintk_emit()
using the same mechanism as emergency printing. If the console
hardware is currently owned by another context, the flushing will
occur when that context releases ownership (just as is the case with
non-panic emergency printing).
If CON_SYNC is set for a legacy console driver or a nbcon console
driver that does not provide a safe atomic_write(), the flag is
cleared upon console registration and a message is logged that the
console does not support sync mode.
Signed-off-by: John Ogness <john.ogness@xxxxxxxxxxxxx>
---
include/linux/console.h | 2 ++
kernel/printk/internal.h | 2 ++
kernel/printk/nbcon.c | 42 ++++++++++++++++++++++++++++++++++------
kernel/printk/printk.c | 25 ++++++++++++++++++++++++
4 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/include/linux/console.h b/include/linux/console.h
index d624200cfc170..0f69e52bc3b6b 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -180,6 +180,7 @@ static inline void con_debug_leave(void) { }
* constraints.
* @CON_NBCON_ATOMIC_UNSAFE: The write_atomic() callback is not safe and is
* therefore only used by nbcon_atomic_flush_unsafe().
+ * @CON_SYNC: Print using write_atomic() from the printk() calling context.
*/
enum cons_flags {
CON_PRINTBUFFER = BIT(0),
@@ -192,6 +193,7 @@ enum cons_flags {
CON_SUSPENDED = BIT(7),
CON_NBCON = BIT(8),
CON_NBCON_ATOMIC_UNSAFE = BIT(9),
+ CON_SYNC = BIT(10),
};
/**
diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h
index 85fbf1801cbe0..1c1e0e97e3a04 100644
--- a/kernel/printk/internal.h
+++ b/kernel/printk/internal.h
@@ -105,6 +105,7 @@ bool nbcon_alloc(struct console *con);
void nbcon_free(struct console *con);
enum nbcon_prio nbcon_get_default_prio(void);
void nbcon_atomic_flush_pending(void);
+void nbcon_sync_flush_pending(void);
bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
int cookie, bool use_atomic);
bool nbcon_kthread_create(struct console *con);
@@ -157,6 +158,7 @@ static inline bool nbcon_alloc(struct console *con) { return false; }
static inline void nbcon_free(struct console *con) { }
static inline enum nbcon_prio nbcon_get_default_prio(void) { return NBCON_PRIO_NONE; }
static inline void nbcon_atomic_flush_pending(void) { }
+static inline void nbcon_sync_flush_pending(void) { }
static inline bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
int cookie, bool use_atomic) { return false; }
static inline void nbcon_kthread_wake(struct console *con) { }
diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c
index 4b03b019cd5ee..77abb98042648 100644
--- a/kernel/printk/nbcon.c
+++ b/kernel/printk/nbcon.c
@@ -1200,7 +1200,10 @@ static bool nbcon_kthread_should_wakeup(struct console *con, struct nbcon_contex
cookie = console_srcu_read_lock();
flags = console_srcu_read_flags(con);
- if (console_is_usable(con, flags, false)) {
+ if (unlikely(flags & CON_SYNC)) {
+ /* Sync consoles never print from the printer thread. */
+ ;
+ } else if (console_is_usable(con, flags, false)) {
/* Bring the sequence in @ctxt up to date */
ctxt->seq = nbcon_seq_read(con);
@@ -1653,8 +1656,9 @@ static void nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq)
* __nbcon_atomic_flush_pending - Flush all nbcon consoles using their
* write_atomic() callback
* @stop_seq: Flush up until this record
+ * @sync_only: Only flush sync consoles (CON_SYNC)
*/
-static void __nbcon_atomic_flush_pending(u64 stop_seq)
+static void __nbcon_atomic_flush_pending(u64 stop_seq, bool sync_only)
{
struct console *con;
int cookie;
@@ -1666,6 +1670,9 @@ static void __nbcon_atomic_flush_pending(u64 stop_seq)
if (!(flags & CON_NBCON))
continue;
+ if (sync_only && !(flags & CON_SYNC))
+ continue;
+
if (!console_is_usable(con, flags, true))
continue;
@@ -1688,7 +1695,22 @@ static void __nbcon_atomic_flush_pending(u64 stop_seq)
*/
void nbcon_atomic_flush_pending(void)
{
- __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb));
+ __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb), false);
+}
+
+/**
+ * nbcon_sync_flush_pending - Flush all nbcon sync consoles (CON_SYNC)
+ * using their write_atomic() callback
+ *
+ * Flush the backlog of all sync consoles up through the currently newest
+ * record. This is the same as nbcon_atomic_flush_pending() except only
+ * for sync (CON_SYNC) consoles.
+ *
+ * See nbcon_atomic_flush_pending() for more details.
+ */
+void nbcon_sync_flush_pending(void)
+{
+ __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb), true);
}
/**
@@ -1701,7 +1723,7 @@ void nbcon_atomic_flush_pending(void)
void nbcon_atomic_flush_unsafe(void)
{
panic_nbcon_allow_unsafe_takeover = true;
- __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb));
+ __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb), false);
panic_nbcon_allow_unsafe_takeover = false;
}
@@ -1905,6 +1927,7 @@ void nbcon_device_release(struct console *con)
{
struct nbcon_context *ctxt = &ACCESS_PRIVATE(con, nbcon_device_ctxt);
struct console_flush_type ft;
+ short flags;
int cookie;
if (!nbcon_context_exit_unsafe(ctxt))
@@ -1919,8 +1942,15 @@ void nbcon_device_release(struct console *con)
* usable throughout flushing.
*/
cookie = console_srcu_read_lock();
- printk_get_console_flush_type(&ft);
- if (console_is_usable(con, console_srcu_read_flags(con), true) &&
+ flags = console_srcu_read_flags(con);
+ if (unlikely(flags & CON_SYNC)) {
+ /* Sync consoles will always perform nbcon_atomic flushing. */
+ memset(&ft, 0, sizeof(ft));
+ ft.nbcon_atomic = true;
+ } else {
+ printk_get_console_flush_type(&ft);
+ }
+ if (console_is_usable(con, flags, true) &&
!ft.nbcon_offload &&
prb_read_valid(prb, nbcon_seq_read(con), NULL)) {
/*
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 2fe9a963c823a..e82e864a4d672 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -453,6 +453,13 @@ bool have_legacy_console;
*/
bool have_nbcon_console;
+/*
+ * Specifies if a console is running in sync mode. If any consoles are running
+ * in sync mode, printk() will immediately flush such consoles using their
+ * write_atomic() callback.
+ */
+bool have_sync_console;
+
/*
* Specifies if a boot console is registered. If boot consoles are present,
* nbcon consoles cannot print simultaneously and must be synchronized by
@@ -2456,6 +2463,8 @@ asmlinkage int vprintk_emit(int facility, int level,
if (ft.nbcon_atomic)
nbcon_atomic_flush_pending();
+ else if (have_sync_console)
+ nbcon_sync_flush_pending();
if (ft.nbcon_offload)
nbcon_kthreads_wake();
@@ -4144,6 +4153,16 @@ void register_console(struct console *newcon)
newcon->dropped = 0;
init_seq = get_init_console_seq(newcon, bootcon_registered);
+ if (newcon->flags & CON_SYNC) {
+ if (!(newcon->flags & CON_NBCON) || (newcon->flags & CON_NBCON_ATOMIC_UNSAFE)) {
+ newcon->flags &= ~CON_SYNC;
+ con_printk(KERN_INFO, newcon, "sync mode unsupported\n");
+ }
+ }
+
+ if (newcon->flags & CON_SYNC)
+ have_sync_console = true;
+
if (newcon->flags & CON_NBCON) {
have_nbcon_console = true;
nbcon_seq_force(newcon, init_seq);
@@ -4231,6 +4250,7 @@ static int unregister_console_locked(struct console *console)
bool found_legacy_con = false;
bool found_nbcon_con = false;
bool found_boot_con = false;
+ bool found_sync_con = false;
unsigned long flags;
struct console *c;
int res;
@@ -4299,6 +4319,9 @@ static int unregister_console_locked(struct console *console)
found_nbcon_con = true;
else
found_legacy_con = true;
+
+ if (c->flags & CON_SYNC)
+ found_sync_con = true;
}
if (!found_boot_con)
have_boot_console = found_boot_con;
@@ -4306,6 +4329,8 @@ static int unregister_console_locked(struct console *console)
have_legacy_console = found_legacy_con;
if (!found_nbcon_con)
have_nbcon_console = found_nbcon_con;
+ if (!found_sync_con)
+ have_sync_console = found_sync_con;
/* @have_nbcon_console must be updated before calling nbcon_free(). */
if (console->flags & CON_NBCON)
--
2.47.3