[PATCH POC 4/4] printk: Correctly replay buffer log for a new console

From: Petr Mladek
Date: Fri Jul 15 2016 - 12:59:54 EST


There are several problems with replying the log buffer
when a new console is added.

Only the end of partially flushed line is printed on
the new console. But this end of the line is not longer
printed on the other consoles. In fact, any new line
are printed only on the new console until the replay
is finished.

This patch tries to fix all the above mentioned problems.
The replay finishes when the original sequence number
is reached. Only the already printed piece of the
continuous line is printed. The rest will be printed
when all consoles are enabled again.

Signed-off-by: Petr Mladek <pmladek@xxxxxxxx>
---
kernel/printk/printk.c | 68 ++++++++++++++++++++++++++++++++++++++------------
1 file changed, 52 insertions(+), 16 deletions(-)

diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index a920cc36c24e..d117e7b1244d 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -134,9 +134,11 @@ static int __down_trylock_console_sem(unsigned long ip)
static int console_locked, console_suspended;

/*
- * If exclusive_console is non-NULL then only this console is to be printed to.
+ * If exclusive_console is non-NULL then only this console is to be printed to
+ * and only until the given sequence number.
*/
static struct console *exclusive_console;
+static u64 exclusive_console_seq_stop;

/*
* Array of consoles built from command line options (console=)
@@ -2177,14 +2179,15 @@ static inline int can_use_console(void)
return cpu_online(raw_smp_processor_id()) || have_callable_console();
}

-static void console_cont_flush(char *text, size_t size)
+static void console_cont_flush(struct cont *cont,char *text, size_t size)
{
unsigned long flags;
+ u8 level;
size_t len;

raw_spin_lock_irqsave(&logbuf_lock, flags);

- if (!cont.msg->text_len)
+ if (!cont->msg->text_len)
goto out;

/*
@@ -2194,10 +2197,11 @@ static void console_cont_flush(char *text, size_t size)
if (console_seq < log_next_seq)
goto out;

- len = cont_print_text(&cont, text, size);
+ len = cont_print_text(cont, text, size);
+ level = cont->msg->level;
raw_spin_unlock(&logbuf_lock);
stop_critical_timings();
- call_console_drivers(cont.msg->level, NULL, 0, text, len);
+ call_console_drivers(level, NULL, 0, text, len);
start_critical_timings();
local_irq_restore(flags);
return;
@@ -2282,18 +2286,19 @@ again:
len = 0;
}

+ if (exclusive_console &&
+ console_seq == exclusive_console_seq_stop)
+ break;
+
if (console_seq == log_next_seq)
break;

msg = log_from_idx(console_idx);
level = msg->level;

- /*
- * The line might be already partially printed from the cont
- * buffer. In this case, flush the rest using a fake
- * cont buffer.
- */
- if (cont_console_len && cont_console_seq == console_seq) {
+ if (cont_console_len &&
+ cont_console_seq == console_seq &&
+ !exclusive_console) {
struct cont fake_cont;

fake_cont.owner = NULL;
@@ -2317,7 +2322,6 @@ again:
log_text(msg), msg->text_len);
}
}
-
console_idx = log_next(console_idx);
console_seq++;
console_prev = msg->flags;
@@ -2334,12 +2338,43 @@ again:

raw_spin_unlock(&logbuf_lock);

- /* flush buffered message fragment immediately to console */
- console_cont_flush(text, sizeof(text));
+ if (unlikely(exclusive_console)) {
+ /* Replay also the partially flushed cont buffer */
+ if (cont_console_seq) {
+ struct printk_log fake_msg;
+ struct printk_log *msg;
+ struct cont fake_cont;
+ size_t len;
+ int level;
+
+ raw_spin_lock(&logbuf_lock);
+ if (console_seq == log_next_seq) {
+ memcpy(&fake_msg, &cont_msg, sizeof(fake_msg));
+ fake_cont.buf = cont_buf;
+ } else {
+ msg = log_from_idx(console_idx);
+ memcpy(&fake_msg, msg, sizeof(fake_msg));
+ fake_cont.buf = log_text(msg);
+ }
+ fake_cont.msg = &fake_msg;
+ fake_msg.text_len = cont_console_len;
+ cont_console_len = 0;
+
+ len = cont_print_text(&fake_cont, text, sizeof(text));
+ level = fake_msg.level;
+
+ raw_spin_unlock(&logbuf_lock);
+
+ stop_critical_timings(); /* don't trace print latency */
+ call_console_drivers(level, NULL, 0, text, len);
+ start_critical_timings();
+ }

- /* Release the exclusive_console once it is used */
- if (unlikely(exclusive_console))
exclusive_console = NULL;
+ } else {
+ /* Flush buffered message fragment immediately to console */
+ console_cont_flush(&cont, text, sizeof(text));
+ }

console_locked = 0;
up_console_sem();
@@ -2626,6 +2661,7 @@ void register_console(struct console *newcon)
* the already-registered consoles.
*/
exclusive_console = newcon;
+ exclusive_console_seq_stop = console_seq;
}
console_unlock();
console_sysfs_notify();
--
1.8.5.6