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).