printk: implement support for extended console drivers
[deliverable/linux.git] / kernel / printk / printk.c
index 51ce4f1838b34808a8776550d11b9c7cb42db9af..ae980dc3ac1eab4da9772f568f9f9f57776b609d 100644 (file)
@@ -84,6 +84,18 @@ static struct lockdep_map console_lock_dep_map = {
 };
 #endif
 
+/*
+ * Number of registered extended console drivers.
+ *
+ * If extended consoles are present, in-kernel cont reassembly is disabled
+ * and each fragment is stored as a separate log entry with proper
+ * continuation flag so that every emitted message has full metadata.  This
+ * doesn't change the result for regular consoles or /proc/kmsg.  For
+ * /dev/kmsg, as long as the reader concatenates messages according to
+ * consecutive continuation flags, the end result should be the same too.
+ */
+static int nr_ext_console_drivers;
+
 /*
  * Helper macros to handle lockdep when locking/unlocking console_sem. We use
  * macros instead of functions so that _RET_IP_ contains useful information.
@@ -195,7 +207,7 @@ static int console_may_schedule;
  * need to be changed in the future, when the requirements change.
  *
  * /dev/kmsg exports the structured data in the following line format:
- *   "level,sequnum,timestamp;<message text>\n"
+ *   "<level>,<sequnum>,<timestamp>,<contflag>;<message text>\n"
  *
  * The optional key/value pairs are attached as continuation lines starting
  * with a space character and terminated by a newline. All possible
@@ -1417,7 +1429,9 @@ SYSCALL_DEFINE3(syslog, int, type, char __user *, buf, int, len)
  * log_buf[start] to log_buf[end - 1].
  * The console_lock must be held.
  */
-static void call_console_drivers(int level, const char *text, size_t len)
+static void call_console_drivers(int level,
+                                const char *ext_text, size_t ext_len,
+                                const char *text, size_t len)
 {
        struct console *con;
 
@@ -1438,7 +1452,10 @@ static void call_console_drivers(int level, const char *text, size_t len)
                if (!cpu_online(smp_processor_id()) &&
                    !(con->flags & CON_ANYTIME))
                        continue;
-               con->write(con, text, len);
+               if (con->flags & CON_EXTENDED)
+                       con->write(con, ext_text, ext_len);
+               else
+                       con->write(con, text, len);
        }
 }
 
@@ -1581,8 +1598,12 @@ static bool cont_add(int facility, int level, const char *text, size_t len)
        if (cont.len && cont.flushed)
                return false;
 
-       if (cont.len + len > sizeof(cont.buf)) {
-               /* the line gets too long, split it up in separate records */
+       /*
+        * If ext consoles are present, flush and skip in-kernel
+        * continuation.  See nr_ext_console_drivers definition.  Also, if
+        * the line gets too long, split it up in separate records.
+        */
+       if (nr_ext_console_drivers || cont.len + len > sizeof(cont.buf)) {
                cont_flush(LOG_CONT);
                return false;
        }
@@ -1917,9 +1938,19 @@ static struct cont {
        u8 level;
        bool flushed:1;
 } cont;
+static char *log_text(const struct printk_log *msg) { return NULL; }
+static char *log_dict(const struct printk_log *msg) { return NULL; }
 static struct printk_log *log_from_idx(u32 idx) { return NULL; }
 static u32 log_next(u32 idx) { return 0; }
-static void call_console_drivers(int level, const char *text, size_t len) {}
+static ssize_t msg_print_ext_header(char *buf, size_t size,
+                                   struct printk_log *msg, u64 seq,
+                                   enum log_flags prev_flags) { return 0; }
+static ssize_t msg_print_ext_body(char *buf, size_t size,
+                                 char *dict, size_t dict_len,
+                                 char *text, size_t text_len) { return 0; }
+static void call_console_drivers(int level,
+                                const char *ext_text, size_t ext_len,
+                                const char *text, size_t len) {}
 static size_t msg_print_text(const struct printk_log *msg, enum log_flags prev,
                             bool syslog, char *buf, size_t size) { return 0; }
 static size_t cont_print_text(char *text, size_t size) { return 0; }
@@ -2172,7 +2203,7 @@ static void console_cont_flush(char *text, size_t size)
        len = cont_print_text(text, size);
        raw_spin_unlock(&logbuf_lock);
        stop_critical_timings();
-       call_console_drivers(cont.level, text, len);
+       call_console_drivers(cont.level, NULL, 0, text, len);
        start_critical_timings();
        local_irq_restore(flags);
        return;
@@ -2196,6 +2227,7 @@ out:
  */
 void console_unlock(void)
 {
+       static char ext_text[CONSOLE_EXT_LOG_MAX];
        static char text[LOG_LINE_MAX + PREFIX_MAX];
        static u64 seen_seq;
        unsigned long flags;
@@ -2214,6 +2246,7 @@ void console_unlock(void)
 again:
        for (;;) {
                struct printk_log *msg;
+               size_t ext_len = 0;
                size_t len;
                int level;
 
@@ -2259,13 +2292,22 @@ skip:
                level = msg->level;
                len += msg_print_text(msg, console_prev, false,
                                      text + len, sizeof(text) - len);
+               if (nr_ext_console_drivers) {
+                       ext_len = msg_print_ext_header(ext_text,
+                                               sizeof(ext_text),
+                                               msg, console_seq, console_prev);
+                       ext_len += msg_print_ext_body(ext_text + ext_len,
+                                               sizeof(ext_text) - ext_len,
+                                               log_dict(msg), msg->dict_len,
+                                               log_text(msg), msg->text_len);
+               }
                console_idx = log_next(console_idx);
                console_seq++;
                console_prev = msg->flags;
                raw_spin_unlock(&logbuf_lock);
 
                stop_critical_timings();        /* don't trace print latency */
-               call_console_drivers(level, text, len);
+               call_console_drivers(level, ext_text, ext_len, text, len);
                start_critical_timings();
                local_irq_restore(flags);
        }
@@ -2521,6 +2563,11 @@ void register_console(struct console *newcon)
                newcon->next = console_drivers->next;
                console_drivers->next = newcon;
        }
+
+       if (newcon->flags & CON_EXTENDED)
+               if (!nr_ext_console_drivers++)
+                       pr_info("printk: continuation disabled due to ext consoles, expect more fragments in /dev/kmsg\n");
+
        if (newcon->flags & CON_PRINTBUFFER) {
                /*
                 * console_unlock(); will print out the buffered messages
@@ -2593,6 +2640,9 @@ int unregister_console(struct console *console)
                }
        }
 
+       if (!res && (console->flags & CON_EXTENDED))
+               nr_ext_console_drivers--;
+
        /*
         * If this isn't the last console and it has CON_CONSDEV set, we
         * need to set it on the next preferred console.
This page took 0.035588 seconds and 5 git commands to generate.