[RFC PATCH 1/5] printk: implement pr_cont_t

From: John Ogness
Date: Wed Aug 19 2020 - 19:26:42 EST


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.ogness@xxxxxxxxxxxxx>
---
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