Implement a new buffering mechanism for pr_cont messages. Old mechanism syntax:
printk(KERN_INFO "text"); printk(KERN_CONT " continued"); printk(KERN_CONT "\n"); New mechanism syntax: pr_cont_t c; pr_cont_begin(&c, KERN_INFO "text"); pr_cont_append(&c, " continued"); pr_cont_end(&c); Signed-off-by: John Ogness <john.ogn...@linutronix.de> --- include/linux/printk.h | 19 ++++++ kernel/printk/printk.c | 137 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/include/linux/printk.h b/include/linux/printk.h index 34c1a7be3e01..4d9ce18e4afa 100644 --- a/include/linux/printk.h +++ b/include/linux/printk.h @@ -380,6 +380,25 @@ extern int kptr_restrict; #define pr_cont(fmt, ...) \ printk(KERN_CONT fmt, ##__VA_ARGS__) +/* opaque handle for continuous printk messages */ +typedef struct { + u8 index; + u8 loglevel; + u16 text_len; +} pr_cont_t; + +/* initialize handle, provide loglevel and initial message text */ +int pr_cont_begin(pr_cont_t *c, const char *fmt, ...); + +/* append message text */ +int pr_cont_append(pr_cont_t *c, const char *fmt, ...); + +/* flush message to kernel buffer */ +void pr_cont_flush(pr_cont_t *c); + +/* flush message to kernel buffer, cleanup handle */ +void pr_cont_end(pr_cont_t *c); + /** * pr_devel - Print a debug-level message conditionally * @fmt: format string diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index ad8d1dfe5fbe..10113e7ea350 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -3495,3 +3495,140 @@ void kmsg_dump_rewind(struct kmsg_dumper *dumper) EXPORT_SYMBOL_GPL(kmsg_dump_rewind); #endif + +#define CONT_LINE_MAX LOG_LINE_MAX +#define CONT_BUF_COUNT 10 +static char cont_buf[CONT_BUF_COUNT][CONT_LINE_MAX]; +static DECLARE_BITMAP(cont_buf_map, CONT_BUF_COUNT); + +static int get_cont_buf(void) +{ + int bit; + + do { + bit = find_first_zero_bit(cont_buf_map, CONT_BUF_COUNT); + if (bit == CONT_BUF_COUNT) + break; + } while (test_and_set_bit(bit, cont_buf_map)); + + return bit; +} + +static void put_cont_buf(int index) +{ + if (WARN_ON(index >= CONT_BUF_COUNT)) + return; + if (WARN_ON(!test_bit(index, cont_buf_map))) + return; + clear_bit(index, cont_buf_map); +} + +/* alloc buffer, get loglevel, setup initial text */ +int pr_cont_begin(pr_cont_t *c, const char *fmt, ...) +{ + int kern_level; + va_list args; + char *text; + + c->index = get_cont_buf(); + if (c->index == CONT_BUF_COUNT) { + /* Fallback to printk parts individually. */ + int text_len; + + va_start(args, fmt); + text_len = vprintk(fmt, args); + va_end(args); + return text_len; + } + + text = &cont_buf[c->index][0]; + + va_start(args, fmt); + c->text_len = vscnprintf(text, CONT_LINE_MAX, fmt, args); + va_end(args); + + c->loglevel = default_message_loglevel; + while (c->text_len > 1 && + (kern_level = printk_get_level(text)) != 0) { + switch (kern_level) { + case '0' ... '7': + c->loglevel = kern_level - '0'; + break; + } + + c->text_len -= 2; + memmove(text, text + 2, c->text_len); + } + + return c->text_len; +} + +/* printk existing buffer, reset buffer */ +void pr_cont_flush(pr_cont_t *c) +{ + char *text; + + if (c->index == CONT_BUF_COUNT || !c->text_len) + return; + + text = &cont_buf[c->index][0]; + + printk("%c%c%s\n", KERN_SOH_ASCII, c->loglevel + '0', text); + + c->text_len = 0; +} + +/* printk existing buffer, free buffer */ +void pr_cont_end(pr_cont_t *c) +{ + if (c->index == CONT_BUF_COUNT) + return; + + pr_cont_flush(c); + put_cont_buf(c->index); + c->index = CONT_BUF_COUNT; +} + +/* append to buffer */ +int pr_cont_append(pr_cont_t *c, const char *fmt, ...) +{ + va_list args_copy; + va_list args; + int text_len; + char *text; + + if (c->index == CONT_BUF_COUNT) { + /* Fallback to printk parts individually. */ + va_start(args, fmt); + text_len = vprintk(fmt, args); + va_end(args); + return text_len; + } + + text = &cont_buf[c->index][0]; + + /* Try to append directly. */ + va_start(args, fmt); + va_copy(args_copy, args); + text_len = vsnprintf(text + c->text_len, CONT_LINE_MAX - c->text_len, fmt, args); + va_end(args); + + if (text_len >= CONT_LINE_MAX - c->text_len) { + /* + * Not enough space remaining. Set the overwritten terminator, + * Flush any existing parts and start with a clean buffer. + * The loglevel is preserved. + */ + + text[c->text_len] = 0; + pr_cont_flush(c); + + va_start(args_copy, fmt); + c->text_len = vscnprintf(text, CONT_LINE_MAX, fmt, args_copy); + va_end(args_copy); + return c->text_len; + } + + c->text_len += text_len; + return text_len; +} -- 2.20.1