From a0c1baa05c07962063ebaee3738429efe6e550c9 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 25 Mar 2024 21:00:40 +0000 Subject: [PATCH] printk: nbcon: Show replay message on takeover BugLink: https://bugs.launchpad.net/bugs/2060704 An emergency or panic context can takeover console ownership while the current owner was printing a printk message. The atomic printer will re-print the message that the previous owner was printing. However, this can look confusing to the user and may even seem as though a message was lost. [3430014.1 [3430014.181123] usb 1-2: Product: USB Audio Add a new field @nbcon_prev_seq to struct console to track the sequence number to print that was assigned to the previous console owner. If this matches the sequence number to print that the current owner is assigned, then a takeover must have occurred. In this case, print an additional message to inform the user that the previous message is being printed again. [3430014.1 ** replaying previous printk message ** [3430014.181123] usb 1-2: Product: USB Audio Signed-off-by: John Ogness Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Kevin Becker --- include/linux/console.h | 2 ++ kernel/printk/internal.h | 1 + kernel/printk/nbcon.c | 24 ++++++++++++++++++++++++ kernel/printk/printk.c | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/include/linux/console.h b/include/linux/console.h index abc4e4f2d989..7d8609b437fe 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -325,6 +325,7 @@ struct nbcon_drvdata { * * @nbcon_state: State for nbcon consoles * @nbcon_seq: Sequence number of the next record for nbcon to print + * @nbcon_prev_seq: Seq num the previous nbcon owner was assigned to print * @pbufs: Pointer to nbcon private buffer * @kthread: Printer kthread for this console * @rcuwait: RCU-safe wait object for @kthread waking @@ -441,6 +442,7 @@ struct console { atomic_t __private nbcon_state; atomic_long_t __private nbcon_seq; + atomic_long_t __private nbcon_prev_seq; /** * @nbcon_drvdata: diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h index 8a2b25ff49ea..9d0255ba0ee8 100644 --- a/kernel/printk/internal.h +++ b/kernel/printk/internal.h @@ -222,4 +222,5 @@ bool printk_get_next_message(struct printk_message *pmsg, u64 seq, #ifdef CONFIG_PRINTK void console_prepend_dropped(struct printk_message *pmsg, unsigned long dropped); +void console_prepend_replay(struct printk_message *pmsg); #endif diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 4fe34efd1b3b..619bc48d2304 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -866,6 +866,7 @@ static bool nbcon_emit_next_record(struct nbcon_write_context *wctxt, bool use_a unsigned long con_dropped; struct nbcon_state cur; unsigned long dropped; + unsigned long ulseq; /* * The printk buffers are filled within an unsafe section. This @@ -891,6 +892,28 @@ static bool nbcon_emit_next_record(struct nbcon_write_context *wctxt, bool use_a if (dropped && !is_extended) console_prepend_dropped(&pmsg, dropped); + /* + * If the previous owner was assigned the same record, this context + * has taken over ownership and is replaying the record. Prepend a + * message to let the user know the record is replayed. + */ + ulseq = atomic_long_read(&ACCESS_PRIVATE(con, nbcon_prev_seq)); + if (__ulseq_to_u64seq(prb, ulseq) == pmsg.seq) { + console_prepend_replay(&pmsg); + } else { + /* + * Ensure this context is still the owner before trying to + * update @nbcon_prev_seq. Otherwise the value in @ulseq may + * not be from the previous owner. + */ + nbcon_state_read(con, &cur); + if (!nbcon_context_can_proceed(ctxt, &cur)) + return false; + + atomic_long_try_cmpxchg(&ACCESS_PRIVATE(con, nbcon_prev_seq), &ulseq, + __u64seq_to_ulseq(pmsg.seq)); + } + if (!nbcon_context_exit_unsafe(ctxt)) return false; @@ -1524,6 +1547,7 @@ void nbcon_init(struct console *con) rcuwait_init(&con->rcuwait); init_irq_work(&con->irq_work, nbcon_irq_work); nbcon_seq_force(con, 0); + atomic_long_set(&ACCESS_PRIVATE(con, nbcon_prev_seq), -1UL); nbcon_state_set(con, &state); nbcon_kthread_create(con); } diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index f715800bccd0..28d72f3fda9b 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2886,6 +2886,25 @@ void console_prepend_dropped(struct printk_message *pmsg, unsigned long dropped) __console_prepend_scratch(pmsg, len); } +/* + * Prepend the message in @pmsg->pbufs->outbuf with a "replay message". + * @pmsg->outbuf_len is updated appropriately. + * + * @pmsg is the printk message to prepend. + */ +void console_prepend_replay(struct printk_message *pmsg) +{ + struct printk_buffers *pbufs = pmsg->pbufs; + const size_t scratchbuf_sz = sizeof(pbufs->scratchbuf); + char *scratchbuf = &pbufs->scratchbuf[0]; + size_t len; + + len = scnprintf(scratchbuf, scratchbuf_sz, + "** replaying previous printk message **\n"); + + __console_prepend_scratch(pmsg, len); +} + /* * Read and format the specified record (or a later record if the specified * record is not available).