genirq: fix regression in irqfixup, irqpoll
[linux-2.6.git] / kernel / printk.c
index 07ad9e7..28a40d8 100644 (file)
@@ -13,7 +13,7 @@
  * Fixed SMP synchronization, 08/08/99, Manfred Spraul
  *     manfred@colorfullife.com
  * Rewrote bits to get rid of console_lock
- *     01Mar01 Andrew Morton <andrewm@uow.edu.au>
+ *     01Mar01 Andrew Morton
  */
 
 #include <linux/kernel.h>
 #include <linux/smp.h>
 #include <linux/security.h>
 #include <linux/bootmem.h>
+#include <linux/memblock.h>
 #include <linux/syscalls.h>
+#include <linux/kexec.h>
+#include <linux/kdb.h>
+#include <linux/ratelimit.h>
+#include <linux/kmsg_dump.h>
+#include <linux/syslog.h>
+#include <linux/cpu.h>
+#include <linux/notifier.h>
+#include <linux/rculist.h>
 
 #include <asm/uaccess.h>
 
@@ -45,7 +54,7 @@ void asmlinkage __attribute__((weak)) early_printk(const char *fmt, ...)
 #define __LOG_BUF_LEN  (1 << CONFIG_LOG_BUF_SHIFT)
 
 /* printk's without a loglevel use this.. */
-#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
+#define DEFAULT_MESSAGE_LOGLEVEL CONFIG_DEFAULT_MESSAGE_LOGLEVEL
 
 /* We show everything that is MORE important than this.. */
 #define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */
@@ -72,8 +81,7 @@ EXPORT_SYMBOL(oops_in_progress);
  * provides serialisation for access to the entire console
  * driver system.
  */
-static DECLARE_MUTEX(console_sem);
-static DECLARE_MUTEX(secondary_console_sem);
+static DEFINE_SEMAPHORE(console_sem);
 struct console *console_drivers;
 EXPORT_SYMBOL_GPL(console_drivers);
 
@@ -90,7 +98,7 @@ static int console_locked, console_suspended;
 /*
  * logbuf_lock protects log_buf, log_start, log_end, con_start and logged_chars
  * It is also used in interesting ways to provide interlocking in
- * release_console_sem().
+ * console_unlock();.
  */
 static DEFINE_SPINLOCK(logbuf_lock);
 
@@ -106,6 +114,11 @@ static unsigned con_start; /* Index into log_buf: next char to be sent to consol
 static unsigned log_end;       /* Index into log_buf: most-recently-written-char + 1 */
 
 /*
+ * If exclusive_console is non-NULL then only this console is to be printed to.
+ */
+static struct console *exclusive_console;
+
+/*
  *     Array of consoles built from command line options (console=)
  */
 struct console_cmdline
@@ -135,57 +148,103 @@ static char __log_buf[__LOG_BUF_LEN];
 static char *log_buf = __log_buf;
 static int log_buf_len = __LOG_BUF_LEN;
 static unsigned logged_chars; /* Number of chars produced since last read+clear operation */
+static int saved_console_loglevel = -1;
 
+#ifdef CONFIG_KEXEC
+/*
+ * This appends the listed symbols to /proc/vmcoreinfo
+ *
+ * /proc/vmcoreinfo is used by various utiilties, like crash and makedumpfile to
+ * obtain access to symbols that are otherwise very difficult to locate.  These
+ * symbols are specifically used so that utilities can access and extract the
+ * dmesg log from a vmcore file after a crash.
+ */
+void log_buf_kexec_setup(void)
+{
+       VMCOREINFO_SYMBOL(log_buf);
+       VMCOREINFO_SYMBOL(log_end);
+       VMCOREINFO_SYMBOL(log_buf_len);
+       VMCOREINFO_SYMBOL(logged_chars);
+}
+#endif
+
+/* requested log_buf_len from kernel cmdline */
+static unsigned long __initdata new_log_buf_len;
+
+/* save requested log_buf_len since it's too early to process it */
 static int __init log_buf_len_setup(char *str)
 {
        unsigned size = memparse(str, &str);
-       unsigned long flags;
 
        if (size)
                size = roundup_pow_of_two(size);
-       if (size > log_buf_len) {
-               unsigned start, dest_idx, offset;
-               char *new_log_buf;
+       if (size > log_buf_len)
+               new_log_buf_len = size;
 
-               new_log_buf = alloc_bootmem(size);
-               if (!new_log_buf) {
-                       printk(KERN_WARNING "log_buf_len: allocation failed\n");
-                       goto out;
-               }
+       return 0;
+}
+early_param("log_buf_len", log_buf_len_setup);
 
-               spin_lock_irqsave(&logbuf_lock, flags);
-               log_buf_len = size;
-               log_buf = new_log_buf;
-
-               offset = start = min(con_start, log_start);
-               dest_idx = 0;
-               while (start != log_end) {
-                       log_buf[dest_idx] = __log_buf[start & (__LOG_BUF_LEN - 1)];
-                       start++;
-                       dest_idx++;
-               }
-               log_start -= offset;
-               con_start -= offset;
-               log_end -= offset;
-               spin_unlock_irqrestore(&logbuf_lock, flags);
+void __init setup_log_buf(int early)
+{
+       unsigned long flags;
+       unsigned start, dest_idx, offset;
+       char *new_log_buf;
+       int free;
+
+       if (!new_log_buf_len)
+               return;
+
+       if (early) {
+               unsigned long mem;
 
-               printk(KERN_NOTICE "log_buf_len: %d\n", log_buf_len);
+               mem = memblock_alloc(new_log_buf_len, PAGE_SIZE);
+               if (mem == MEMBLOCK_ERROR)
+                       return;
+               new_log_buf = __va(mem);
+       } else {
+               new_log_buf = alloc_bootmem_nopanic(new_log_buf_len);
        }
-out:
-       return 1;
-}
 
-__setup("log_buf_len=", log_buf_len_setup);
+       if (unlikely(!new_log_buf)) {
+               pr_err("log_buf_len: %ld bytes not available\n",
+                       new_log_buf_len);
+               return;
+       }
+
+       spin_lock_irqsave(&logbuf_lock, flags);
+       log_buf_len = new_log_buf_len;
+       log_buf = new_log_buf;
+       new_log_buf_len = 0;
+       free = __LOG_BUF_LEN - log_end;
+
+       offset = start = min(con_start, log_start);
+       dest_idx = 0;
+       while (start != log_end) {
+               unsigned log_idx_mask = start & (__LOG_BUF_LEN - 1);
+
+               log_buf[dest_idx] = __log_buf[log_idx_mask];
+               start++;
+               dest_idx++;
+       }
+       log_start -= offset;
+       con_start -= offset;
+       log_end -= offset;
+       spin_unlock_irqrestore(&logbuf_lock, flags);
+
+       pr_info("log_buf_len: %d\n", log_buf_len);
+       pr_info("early log buf free: %d(%d%%)\n",
+               free, (free * 100) / __LOG_BUF_LEN);
+}
 
 #ifdef CONFIG_BOOT_PRINTK_DELAY
 
-static unsigned int boot_delay; /* msecs delay after each printk during bootup */
-static unsigned long long printk_delay_msec; /* per msec, based on boot_delay */
+static int boot_delay; /* msecs delay after each printk during bootup */
+static unsigned long long loops_per_msec;      /* based on boot_delay */
 
 static int __init boot_delay_setup(char *str)
 {
        unsigned long lpj;
-       unsigned long long loops_per_msec;
 
        lpj = preset_lpj ? preset_lpj : 1000000;        /* some guess */
        loops_per_msec = (unsigned long long)lpj / 1000 * HZ;
@@ -194,10 +253,9 @@ static int __init boot_delay_setup(char *str)
        if (boot_delay > 10 * 1000)
                boot_delay = 0;
 
-       printk_delay_msec = loops_per_msec;
-       printk(KERN_DEBUG "boot_delay: %u, preset_lpj: %ld, lpj: %lu, "
-               "HZ: %d, printk_delay_msec: %llu\n",
-               boot_delay, preset_lpj, lpj, HZ, printk_delay_msec);
+       pr_debug("boot_delay: %u, preset_lpj: %ld, lpj: %lu, "
+               "HZ: %d, loops_per_msec: %llu\n",
+               boot_delay, preset_lpj, lpj, HZ, loops_per_msec);
        return 1;
 }
 __setup("boot_delay=", boot_delay_setup);
@@ -210,7 +268,7 @@ static void boot_delay_msec(void)
        if (boot_delay == 0 || system_state != SYSTEM_BOOTING)
                return;
 
-       k = (unsigned long long)printk_delay_msec * boot_delay;
+       k = (unsigned long long)loops_per_msec * boot_delay;
 
        timeout = jiffies + msecs_to_jiffies(boot_delay);
        while (k) {
@@ -232,77 +290,66 @@ static inline void boot_delay_msec(void)
 }
 #endif
 
-/*
- * Return the number of unread characters in the log buffer.
- */
-static int log_buf_get_len(void)
+#ifdef CONFIG_SECURITY_DMESG_RESTRICT
+int dmesg_restrict = 1;
+#else
+int dmesg_restrict;
+#endif
+
+static int syslog_action_restricted(int type)
 {
-       return logged_chars;
+       if (dmesg_restrict)
+               return 1;
+       /* Unless restricted, we allow "read all" and "get buffer size" for everybody */
+       return type != SYSLOG_ACTION_READ_ALL && type != SYSLOG_ACTION_SIZE_BUFFER;
 }
 
-/*
- * Copy a range of characters from the log buffer.
- */
-int log_buf_copy(char *dest, int idx, int len)
+static int check_syslog_permissions(int type, bool from_file)
 {
-       int ret, max;
-       bool took_lock = false;
-
-       if (!oops_in_progress) {
-               spin_lock_irq(&logbuf_lock);
-               took_lock = true;
-       }
-
-       max = log_buf_get_len();
-       if (idx < 0 || idx >= max) {
-               ret = -1;
-       } else {
-               if (len > max)
-                       len = max;
-               ret = len;
-               idx += (log_end - max);
-               while (len-- > 0)
-                       dest[len] = LOG_BUF(idx + len);
+       /*
+        * If this is from /proc/kmsg and we've already opened it, then we've
+        * already done the capabilities checks at open time.
+        */
+       if (from_file && type != SYSLOG_ACTION_OPEN)
+               return 0;
+
+       if (syslog_action_restricted(type)) {
+               if (capable(CAP_SYSLOG))
+                       return 0;
+               /* For historical reasons, accept CAP_SYS_ADMIN too, with a warning */
+               if (capable(CAP_SYS_ADMIN)) {
+                       printk_once(KERN_WARNING "%s (%d): "
+                                "Attempt to access syslog with CAP_SYS_ADMIN "
+                                "but no CAP_SYSLOG (deprecated).\n",
+                                current->comm, task_pid_nr(current));
+                       return 0;
+               }
+               return -EPERM;
        }
-
-       if (took_lock)
-               spin_unlock_irq(&logbuf_lock);
-
-       return ret;
+       return 0;
 }
 
-/*
- * Commands to do_syslog:
- *
- *     0 -- Close the log.  Currently a NOP.
- *     1 -- Open the log. Currently a NOP.
- *     2 -- Read from the log.
- *     3 -- Read all messages remaining in the ring buffer.
- *     4 -- Read and clear all messages remaining in the ring buffer
- *     5 -- Clear ring buffer.
- *     6 -- Disable printk's to console
- *     7 -- Enable printk's to console
- *     8 -- Set level of messages printed to console
- *     9 -- Return number of unread characters in the log buffer
- *     10 -- Return size of the log buffer
- */
-int do_syslog(int type, char __user *buf, int len)
+int do_syslog(int type, char __user *buf, int len, bool from_file)
 {
        unsigned i, j, limit, count;
        int do_clear = 0;
        char c;
-       int error = 0;
+       int error;
+
+       error = check_syslog_permissions(type, from_file);
+       if (error)
+               goto out;
 
        error = security_syslog(type);
        if (error)
                return error;
 
        switch (type) {
-       case 0:         /* Close log */
+       case SYSLOG_ACTION_CLOSE:       /* Close log */
                break;
-       case 1:         /* Open log */
+       case SYSLOG_ACTION_OPEN:        /* Open log */
                break;
-       case 2:         /* Read from log */
+       case SYSLOG_ACTION_READ:        /* Read from log */
                error = -EINVAL;
                if (!buf || len < 0)
                        goto out;
@@ -333,10 +380,12 @@ int do_syslog(int type, char __user *buf, int len)
                if (!error)
                        error = i;
                break;
-       case 4:         /* Read/clear last kernel messages */
+       /* Read/clear last kernel messages */
+       case SYSLOG_ACTION_READ_CLEAR:
                do_clear = 1;
                /* FALL THRU */
-       case 3:         /* Read last kernel messages */
+       /* Read last kernel messages */
+       case SYSLOG_ACTION_READ_ALL:
                error = -EINVAL;
                if (!buf || len < 0)
                        goto out;
@@ -389,28 +438,41 @@ int do_syslog(int type, char __user *buf, int len)
                        }
                }
                break;
-       case 5:         /* Clear ring buffer */
+       /* Clear ring buffer */
+       case SYSLOG_ACTION_CLEAR:
                logged_chars = 0;
                break;
-       case 6:         /* Disable logging to console */
+       /* Disable logging to console */
+       case SYSLOG_ACTION_CONSOLE_OFF:
+               if (saved_console_loglevel == -1)
+                       saved_console_loglevel = console_loglevel;
                console_loglevel = minimum_console_loglevel;
                break;
-       case 7:         /* Enable logging to console */
-               console_loglevel = default_console_loglevel;
+       /* Enable logging to console */
+       case SYSLOG_ACTION_CONSOLE_ON:
+               if (saved_console_loglevel != -1) {
+                       console_loglevel = saved_console_loglevel;
+                       saved_console_loglevel = -1;
+               }
                break;
-       case 8:         /* Set level of messages printed to console */
+       /* Set level of messages printed to console */
+       case SYSLOG_ACTION_CONSOLE_LEVEL:
                error = -EINVAL;
                if (len < 1 || len > 8)
                        goto out;
                if (len < minimum_console_loglevel)
                        len = minimum_console_loglevel;
                console_loglevel = len;
+               /* Implicitly re-enable logging to console */
+               saved_console_loglevel = -1;
                error = 0;
                break;
-       case 9:         /* Number of chars in the log buffer */
+       /* Number of chars in the log buffer */
+       case SYSLOG_ACTION_SIZE_UNREAD:
                error = log_end - log_start;
                break;
-       case 10:        /* Size of the log buffer */
+       /* Size of the log buffer */
+       case SYSLOG_ACTION_SIZE_BUFFER:
                error = log_buf_len;
                break;
        default:
@@ -421,11 +483,27 @@ out:
        return error;
 }
 
-asmlinkage long sys_syslog(int type, char __user *buf, int len)
+SYSCALL_DEFINE3(syslog, int, type, char __user *, buf, int, len)
 {
-       return do_syslog(type, buf, len);
+       return do_syslog(type, buf, len, SYSLOG_FROM_CALL);
 }
 
+#ifdef CONFIG_KGDB_KDB
+/* kdb dmesg command needs access to the syslog buffer.  do_syslog()
+ * uses locks so it cannot be used during debugging.  Just tell kdb
+ * where the start and end of the physical and logical logs are.  This
+ * is equivalent to do_syslog(3).
+ */
+void kdb_syslog_data(char *syslog_data[4])
+{
+       syslog_data[0] = log_buf;
+       syslog_data[1] = log_buf + log_buf_len;
+       syslog_data[2] = log_buf + log_end -
+               (logged_chars < log_buf_len ? logged_chars : log_buf_len);
+       syslog_data[3] = log_buf + log_end;
+}
+#endif /* CONFIG_KGDB_KDB */
+
 /*
  * Call the console drivers on a range of log_buf
  */
@@ -433,7 +511,9 @@ static void __call_console_drivers(unsigned start, unsigned end)
 {
        struct console *con;
 
-       for (con = console_drivers; con; con = con->next) {
+       for_each_console(con) {
+               if (exclusive_console && con != exclusive_console)
+                       continue;
                if ((con->flags & CON_ENABLED) && con->write &&
                                (cpu_online(smp_processor_id()) ||
                                (con->flags & CON_ANYTIME)))
@@ -473,9 +553,74 @@ static void _call_console_drivers(unsigned start,
 }
 
 /*
+ * Parse the syslog header <[0-9]*>. The decimal value represents 32bit, the
+ * lower 3 bit are the log level, the rest are the log facility. In case
+ * userspace passes usual userspace syslog messages to /dev/kmsg or
+ * /dev/ttyprintk, the log prefix might contain the facility. Printk needs
+ * to extract the correct log level for in-kernel processing, and not mangle
+ * the original value.
+ *
+ * If a prefix is found, the length of the prefix is returned. If 'level' is
+ * passed, it will be filled in with the log level without a possible facility
+ * value. If 'special' is passed, the special printk prefix chars are accepted
+ * and returned. If no valid header is found, 0 is returned and the passed
+ * variables are not touched.
+ */
+static size_t log_prefix(const char *p, unsigned int *level, char *special)
+{
+       unsigned int lev = 0;
+       char sp = '\0';
+       size_t len;
+
+       if (p[0] != '<' || !p[1])
+               return 0;
+       if (p[2] == '>') {
+               /* usual single digit level number or special char */
+               switch (p[1]) {
+               case '0' ... '7':
+                       lev = p[1] - '0';
+                       break;
+               case 'c': /* KERN_CONT */
+               case 'd': /* KERN_DEFAULT */
+                       sp = p[1];
+                       break;
+               default:
+                       return 0;
+               }
+               len = 3;
+       } else {
+               /* multi digit including the level and facility number */
+               char *endp = NULL;
+
+               if (p[1] < '0' && p[1] > '9')
+                       return 0;
+
+               lev = (simple_strtoul(&p[1], &endp, 10) & 7);
+               if (endp == NULL || endp[0] != '>')
+                       return 0;
+               len = (endp + 1) - p;
+       }
+
+       /* do not accept special char if not asked for */
+       if (sp && !special)
+               return 0;
+
+       if (special) {
+               *special = sp;
+               /* return special char, do not touch level */
+               if (sp)
+                       return len;
+       }
+
+       if (level)
+               *level = lev;
+       return len;
+}
+
+/*
  * Call the console drivers, asking them to write out
  * log_buf[start] to log_buf[end - 1].
- * The console_sem must be held.
+ * The console_lock must be held.
  */
 static void call_console_drivers(unsigned start, unsigned end)
 {
@@ -487,13 +632,9 @@ static void call_console_drivers(unsigned start, unsigned end)
        cur_index = start;
        start_print = start;
        while (cur_index != end) {
-               if (msg_level < 0 && ((end - cur_index) > 2) &&
-                               LOG_BUF(cur_index + 0) == '<' &&
-                               LOG_BUF(cur_index + 1) >= '0' &&
-                               LOG_BUF(cur_index + 1) <= '7' &&
-                               LOG_BUF(cur_index + 2) == '>') {
-                       msg_level = LOG_BUF(cur_index + 1) - '0';
-                       cur_index += 3;
+               if (msg_level < 0 && ((end - cur_index) > 2)) {
+                       /* strip log prefix */
+                       cur_index += log_prefix(&LOG_BUF(cur_index), &msg_level, NULL);
                        start_print = cur_index;
                }
                while (cur_index != end) {
@@ -550,7 +691,7 @@ static void zap_locks(void)
        /* If a crash is occurring, make sure we can't deadlock */
        spin_lock_init(&logbuf_lock);
        /* And make sure that we print immediately */
-       init_MUTEX(&console_sem);
+       sema_init(&console_sem, 1);
 }
 
 #if defined(CONFIG_PRINTK_TIME)
@@ -565,7 +706,7 @@ static int have_callable_console(void)
 {
        struct console *con;
 
-       for (con = console_drivers; con; con = con->next)
+       for_each_console(con)
                if (con->flags & CON_ANYTIME)
                        return 1;
 
@@ -577,15 +718,12 @@ static int have_callable_console(void)
  * @fmt: format string
  *
  * This is printk().  It can be called from any context.  We want it to work.
- * Be aware of the fact that if oops_in_progress is not set, we might try to
- * wake klogd up which could deadlock on runqueue lock if printk() is called
- * from scheduler code.
  *
- * We try to grab the console_sem.  If we succeed, it's easy - we log the output and
+ * We try to grab the console_lock.  If we succeed, it's easy - we log the output and
  * call the console drivers.  If we fail to get the semaphore we place the output
  * into the log buffer and return.  The current holder of the console_sem will
- * notice the new output in release_console_sem() and will send it to the
- * consoles before releasing the semaphore.
+ * notice the new output in console_unlock(); and will send it to the
+ * consoles before releasing the lock.
  *
  * One effect of this deferred printing is that code which calls printk() and
  * then changes console_loglevel may break. This is because console_loglevel
@@ -593,6 +731,8 @@ static int have_callable_console(void)
  *
  * See also:
  * printf(3)
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
  */
 
 asmlinkage int printk(const char *fmt, ...)
@@ -600,6 +740,14 @@ asmlinkage int printk(const char *fmt, ...)
        va_list args;
        int r;
 
+#ifdef CONFIG_KGDB_KDB
+       if (unlikely(kdb_trap_printk)) {
+               va_start(args, fmt);
+               r = vkdb_printf(fmt, args);
+               va_end(args);
+               return r;
+       }
+#endif
        va_start(args, fmt);
        r = vprintk(fmt, args);
        va_end(args);
@@ -626,18 +774,19 @@ static inline int can_use_console(unsigned int cpu)
 /*
  * Try to get console ownership to actually show the kernel
  * messages from a 'printk'. Return true (and with the
- * console_semaphore held, and 'console_locked' set) if it
+ * console_lock held, and 'console_locked' set) if it
  * is successful, false otherwise.
  *
  * This gets called with the 'logbuf_lock' spinlock held and
  * interrupts disabled. It should return with 'lockbuf_lock'
  * released but interrupts still disabled.
  */
-static int acquire_console_semaphore_for_printk(unsigned int cpu)
+static int console_trylock_for_printk(unsigned int cpu)
+       __releases(&logbuf_lock)
 {
-       int retval = 0;
+       int retval = 0, wake = 0;
 
-       if (!try_acquire_console_sem()) {
+       if (console_trylock()) {
                retval = 1;
 
                /*
@@ -648,20 +797,36 @@ static int acquire_console_semaphore_for_printk(unsigned int cpu)
                 */
                if (!can_use_console(cpu)) {
                        console_locked = 0;
-                       up(&console_sem);
+                       wake = 1;
                        retval = 0;
                }
        }
        printk_cpu = UINT_MAX;
        spin_unlock(&logbuf_lock);
+       if (wake)
+               up(&console_sem);
        return retval;
 }
 static const char recursion_bug_msg [] =
                KERN_CRIT "BUG: recent printk recursion!\n";
 static int recursion_bug;
-       static int new_text_line = 1;
+static int new_text_line = 1;
 static char printk_buf[1024];
 
+int printk_delay_msec __read_mostly;
+
+static inline void printk_delay(void)
+{
+       if (unlikely(printk_delay_msec)) {
+               int m = printk_delay_msec;
+
+               while (m--) {
+                       mdelay(1);
+                       touch_nmi_watchdog();
+               }
+       }
+}
+
 asmlinkage int vprintk(const char *fmt, va_list args)
 {
        int printed_len = 0;
@@ -669,8 +834,11 @@ asmlinkage int vprintk(const char *fmt, va_list args)
        unsigned long flags;
        int this_cpu;
        char *p;
+       size_t plen;
+       char special;
 
        boot_delay_msec();
+       printk_delay();
 
        preempt_disable();
        /* This stops the holder of console_sem just where we want him */
@@ -702,36 +870,58 @@ asmlinkage int vprintk(const char *fmt, va_list args)
        if (recursion_bug) {
                recursion_bug = 0;
                strcpy(printk_buf, recursion_bug_msg);
-               printed_len = sizeof(recursion_bug_msg);
+               printed_len = strlen(recursion_bug_msg);
        }
        /* Emit the output into the temporary buffer */
        printed_len += vscnprintf(printk_buf + printed_len,
                                  sizeof(printk_buf) - printed_len, fmt, args);
 
+       p = printk_buf;
+
+       /* Read log level and handle special printk prefix */
+       plen = log_prefix(p, &current_log_level, &special);
+       if (plen) {
+               p += plen;
+
+               switch (special) {
+               case 'c': /* Strip <c> KERN_CONT, continue line */
+                       plen = 0;
+                       break;
+               case 'd': /* Strip <d> KERN_DEFAULT, start new line */
+                       plen = 0;
+               default:
+                       if (!new_text_line) {
+                               emit_log_char('\n');
+                               new_text_line = 1;
+                       }
+               }
+       }
 
        /*
-        * Copy the output into log_buf.  If the caller didn't provide
-        * appropriate log level tags, we insert them here
+        * Copy the output into log_buf. If the caller didn't provide
+        * the appropriate log prefix, we insert them here
         */
-       for (p = printk_buf; *p; p++) {
+       for (; *p; p++) {
                if (new_text_line) {
-                       /* If a token, set current_log_level and skip over */
-                       if (p[0] == '<' && p[1] >= '0' && p[1] <= '7' &&
-                           p[2] == '>') {
-                               current_log_level = p[1] - '0';
-                               p += 3;
-                               printed_len -= 3;
-                       }
-
-                       /* Always output the token */
-                       emit_log_char('<');
-                       emit_log_char(current_log_level + '0');
-                       emit_log_char('>');
-                       printed_len += 3;
                        new_text_line = 0;
 
+                       if (plen) {
+                               /* Copy original log prefix */
+                               int i;
+
+                               for (i = 0; i < plen; i++)
+                                       emit_log_char(printk_buf[i]);
+                               printed_len += plen;
+                       } else {
+                               /* Add log prefix */
+                               emit_log_char('<');
+                               emit_log_char(current_log_level + '0');
+                               emit_log_char('>');
+                               printed_len += 3;
+                       }
+
                        if (printk_time) {
-                               /* Follow the token with the time */
+                               /* Add the current time stamp */
                                char tbuf[50], *tp;
                                unsigned tlen;
                                unsigned long long t;
@@ -763,12 +953,12 @@ asmlinkage int vprintk(const char *fmt, va_list args)
         * actual magic (print out buffers, wake up klogd,
         * etc). 
         *
-        * The acquire_console_semaphore_for_printk() function
+        * The console_trylock_for_printk() function
         * will release 'logbuf_lock' regardless of whether it
         * actually gets the semaphore or not.
         */
-       if (acquire_console_semaphore_for_printk(this_cpu))
-               release_console_sem();
+       if (console_trylock_for_printk(this_cpu))
+               console_unlock();
 
        lockdep_on();
 out_restore_irqs:
@@ -782,11 +972,6 @@ EXPORT_SYMBOL(vprintk);
 
 #else
 
-asmlinkage long sys_syslog(int type, char __user *buf, int len)
-{
-       return -ENOSYS;
-}
-
 static void call_console_drivers(unsigned start, unsigned end)
 {
 }
@@ -933,88 +1118,144 @@ void suspend_console(void)
 {
        if (!console_suspend_enabled)
                return;
-       printk("Suspending console(s)\n");
-       acquire_console_sem();
+       printk("Suspending console(s) (use no_console_suspend to debug)\n");
+       console_lock();
        console_suspended = 1;
+       up(&console_sem);
 }
 
 void resume_console(void)
 {
        if (!console_suspend_enabled)
                return;
+       down(&console_sem);
        console_suspended = 0;
-       release_console_sem();
+       console_unlock();
 }
 
 /**
- * acquire_console_sem - lock the console system for exclusive use.
+ * console_cpu_notify - print deferred console messages after CPU hotplug
+ * @self: notifier struct
+ * @action: CPU hotplug event
+ * @hcpu: unused
  *
- * Acquires a semaphore which guarantees that the caller has
+ * If printk() is called from a CPU that is not online yet, the messages
+ * will be spooled but will not show up on the console.  This function is
+ * called when a new CPU comes online (or fails to come up), and ensures
+ * that any such output gets printed.
+ */
+static int __cpuinit console_cpu_notify(struct notifier_block *self,
+       unsigned long action, void *hcpu)
+{
+       switch (action) {
+       case CPU_ONLINE:
+       case CPU_DEAD:
+       case CPU_DYING:
+       case CPU_DOWN_FAILED:
+       case CPU_UP_CANCELED:
+               console_lock();
+               console_unlock();
+       }
+       return NOTIFY_OK;
+}
+
+/**
+ * console_lock - lock the console system for exclusive use.
+ *
+ * Acquires a lock which guarantees that the caller has
  * exclusive access to the console system and the console_drivers list.
  *
  * Can sleep, returns nothing.
  */
-void acquire_console_sem(void)
+void console_lock(void)
 {
        BUG_ON(in_interrupt());
-       if (console_suspended) {
-               down(&secondary_console_sem);
-               return;
-       }
        down(&console_sem);
+       if (console_suspended)
+               return;
        console_locked = 1;
        console_may_schedule = 1;
 }
-EXPORT_SYMBOL(acquire_console_sem);
+EXPORT_SYMBOL(console_lock);
 
-int try_acquire_console_sem(void)
+/**
+ * console_trylock - try to lock the console system for exclusive use.
+ *
+ * Tried to acquire a lock which guarantees that the caller has
+ * exclusive access to the console system and the console_drivers list.
+ *
+ * returns 1 on success, and 0 on failure to acquire the lock.
+ */
+int console_trylock(void)
 {
        if (down_trylock(&console_sem))
-               return -1;
+               return 0;
+       if (console_suspended) {
+               up(&console_sem);
+               return 0;
+       }
        console_locked = 1;
        console_may_schedule = 0;
-       return 0;
+       return 1;
 }
-EXPORT_SYMBOL(try_acquire_console_sem);
+EXPORT_SYMBOL(console_trylock);
 
 int is_console_locked(void)
 {
        return console_locked;
 }
 
-void wake_up_klogd(void)
+static DEFINE_PER_CPU(int, printk_pending);
+
+void printk_tick(void)
 {
-       if (!oops_in_progress && waitqueue_active(&log_wait))
+       if (__this_cpu_read(printk_pending)) {
+               __this_cpu_write(printk_pending, 0);
                wake_up_interruptible(&log_wait);
+       }
+}
+
+int printk_needs_cpu(int cpu)
+{
+       if (cpu_is_offline(cpu))
+               printk_tick();
+       return __this_cpu_read(printk_pending);
+}
+
+void wake_up_klogd(void)
+{
+       if (waitqueue_active(&log_wait))
+               this_cpu_write(printk_pending, 1);
 }
 
 /**
- * release_console_sem - unlock the console system
+ * console_unlock - unlock the console system
  *
- * Releases the semaphore which the caller holds on the console system
+ * Releases the console_lock which the caller holds on the console system
  * and the console driver list.
  *
- * While the semaphore was held, console output may have been buffered
- * by printk().  If this is the case, release_console_sem() emits
- * the output prior to releasing the semaphore.
+ * While the console_lock was held, console output may have been buffered
+ * by printk().  If this is the case, console_unlock(); emits
+ * the output prior to releasing the lock.
  *
  * If there is output waiting for klogd, we wake it up.
  *
- * release_console_sem() may be called from any context.
+ * console_unlock(); may be called from any context.
  */
-void release_console_sem(void)
+void console_unlock(void)
 {
        unsigned long flags;
        unsigned _con_start, _log_end;
-       unsigned wake_klogd = 0;
+       unsigned wake_klogd = 0, retry = 0;
 
        if (console_suspended) {
-               up(&secondary_console_sem);
+               up(&console_sem);
                return;
        }
 
        console_may_schedule = 0;
 
+again:
        for ( ; ; ) {
                spin_lock_irqsave(&logbuf_lock, flags);
                wake_klogd |= log_start - log_end;
@@ -1030,12 +1271,32 @@ void release_console_sem(void)
                local_irq_restore(flags);
        }
        console_locked = 0;
+
+       /* Release the exclusive_console once it is used */
+       if (unlikely(exclusive_console))
+               exclusive_console = NULL;
+
+       spin_unlock(&logbuf_lock);
+
        up(&console_sem);
+
+       /*
+        * Someone could have filled up the buffer again, so re-check if there's
+        * something to flush. In case we cannot trylock the console_sem again,
+        * there's a new owner and the console_unlock() from them will do the
+        * flush, no worries.
+        */
+       spin_lock(&logbuf_lock);
+       if (con_start != log_end)
+               retry = 1;
        spin_unlock_irqrestore(&logbuf_lock, flags);
+       if (retry && console_trylock())
+               goto again;
+
        if (wake_klogd)
                wake_up_klogd();
 }
-EXPORT_SYMBOL(release_console_sem);
+EXPORT_SYMBOL(console_unlock);
 
 /**
  * console_conditional_schedule - yield the CPU if required
@@ -1044,7 +1305,7 @@ EXPORT_SYMBOL(release_console_sem);
  * if this CPU should yield the CPU to another task, do
  * so here.
  *
- * Must be called within acquire_console_sem().
+ * Must be called within console_lock();.
  */
 void __sched console_conditional_schedule(void)
 {
@@ -1053,12 +1314,6 @@ void __sched console_conditional_schedule(void)
 }
 EXPORT_SYMBOL(console_conditional_schedule);
 
-void console_print(const char *s)
-{
-       printk(KERN_EMERG "%s", s);
-}
-EXPORT_SYMBOL(console_print);
-
 void console_unblank(void)
 {
        struct console *c;
@@ -1071,14 +1326,14 @@ void console_unblank(void)
                if (down_trylock(&console_sem) != 0)
                        return;
        } else
-               acquire_console_sem();
+               console_lock();
 
        console_locked = 1;
        console_may_schedule = 0;
-       for (c = console_drivers; c != NULL; c = c->next)
+       for_each_console(c)
                if ((c->flags & CON_ENABLED) && c->unblank)
                        c->unblank();
-       release_console_sem();
+       console_unlock();
 }
 
 /*
@@ -1089,15 +1344,15 @@ struct tty_driver *console_device(int *index)
        struct console *c;
        struct tty_driver *driver = NULL;
 
-       acquire_console_sem();
-       for (c = console_drivers; c != NULL; c = c->next) {
+       console_lock();
+       for_each_console(c) {
                if (!c->device)
                        continue;
                driver = c->device(c, index);
                if (driver)
                        break;
        }
-       release_console_sem();
+       console_unlock();
        return driver;
 }
 
@@ -1108,44 +1363,80 @@ struct tty_driver *console_device(int *index)
  */
 void console_stop(struct console *console)
 {
-       acquire_console_sem();
+       console_lock();
        console->flags &= ~CON_ENABLED;
-       release_console_sem();
+       console_unlock();
 }
 EXPORT_SYMBOL(console_stop);
 
 void console_start(struct console *console)
 {
-       acquire_console_sem();
+       console_lock();
        console->flags |= CON_ENABLED;
-       release_console_sem();
+       console_unlock();
 }
 EXPORT_SYMBOL(console_start);
 
+static int __read_mostly keep_bootcon;
+
+static int __init keep_bootcon_setup(char *str)
+{
+       keep_bootcon = 1;
+       printk(KERN_INFO "debug: skip boot console de-registration.\n");
+
+       return 0;
+}
+
+early_param("keep_bootcon", keep_bootcon_setup);
+
 /*
  * The console driver calls this routine during kernel initialization
  * to register the console printing procedure with printk() and to
  * print any messages that were printed by the kernel before the
  * console driver was initialized.
+ *
+ * This can happen pretty early during the boot process (because of
+ * early_printk) - sometimes before setup_arch() completes - be careful
+ * of what kernel features are used - they may not be initialised yet.
+ *
+ * There are two types of consoles - bootconsoles (early_printk) and
+ * "real" consoles (everything which is not a bootconsole) which are
+ * handled differently.
+ *  - Any number of bootconsoles can be registered at any time.
+ *  - As soon as a "real" console is registered, all bootconsoles
+ *    will be unregistered automatically.
+ *  - Once a "real" console is registered, any attempt to register a
+ *    bootconsoles will be rejected
  */
-void register_console(struct console *console)
+void register_console(struct console *newcon)
 {
        int i;
        unsigned long flags;
-       struct console *bootconsole = NULL;
+       struct console *bcon = NULL;
 
-       if (console_drivers) {
-               if (console->flags & CON_BOOT)
-                       return;
-               if (console_drivers->flags & CON_BOOT)
-                       bootconsole = console_drivers;
+       /*
+        * before we register a new CON_BOOT console, make sure we don't
+        * already have a valid console
+        */
+       if (console_drivers && newcon->flags & CON_BOOT) {
+               /* find the last or real console */
+               for_each_console(bcon) {
+                       if (!(bcon->flags & CON_BOOT)) {
+                               printk(KERN_INFO "Too late to register bootconsole %s%d\n",
+                                       newcon->name, newcon->index);
+                               return;
+                       }
+               }
        }
 
-       if (preferred_console < 0 || bootconsole || !console_drivers)
+       if (console_drivers && console_drivers->flags & CON_BOOT)
+               bcon = console_drivers;
+
+       if (preferred_console < 0 || bcon || !console_drivers)
                preferred_console = selected_console;
 
-       if (console->early_setup)
-               console->early_setup();
+       if (newcon->early_setup)
+               newcon->early_setup();
 
        /*
         *      See if we want to use this console driver. If we
@@ -1153,13 +1444,13 @@ void register_console(struct console *console)
         *      that registers here.
         */
        if (preferred_console < 0) {
-               if (console->index < 0)
-                       console->index = 0;
-               if (console->setup == NULL ||
-                   console->setup(console, NULL) == 0) {
-                       console->flags |= CON_ENABLED;
-                       if (console->device) {
-                               console->flags |= CON_CONSDEV;
+               if (newcon->index < 0)
+                       newcon->index = 0;
+               if (newcon->setup == NULL ||
+                   newcon->setup(newcon, NULL) == 0) {
+                       newcon->flags |= CON_ENABLED;
+                       if (newcon->device) {
+                               newcon->flags |= CON_CONSDEV;
                                preferred_console = 0;
                        }
                }
@@ -1171,73 +1462,102 @@ void register_console(struct console *console)
         */
        for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];
                        i++) {
-               if (strcmp(console_cmdline[i].name, console->name) != 0)
+               if (strcmp(console_cmdline[i].name, newcon->name) != 0)
                        continue;
-               if (console->index >= 0 &&
-                   console->index != console_cmdline[i].index)
+               if (newcon->index >= 0 &&
+                   newcon->index != console_cmdline[i].index)
                        continue;
-               if (console->index < 0)
-                       console->index = console_cmdline[i].index;
+               if (newcon->index < 0)
+                       newcon->index = console_cmdline[i].index;
 #ifdef CONFIG_A11Y_BRAILLE_CONSOLE
                if (console_cmdline[i].brl_options) {
-                       console->flags |= CON_BRL;
-                       braille_register_console(console,
+                       newcon->flags |= CON_BRL;
+                       braille_register_console(newcon,
                                        console_cmdline[i].index,
                                        console_cmdline[i].options,
                                        console_cmdline[i].brl_options);
                        return;
                }
 #endif
-               if (console->setup &&
-                   console->setup(console, console_cmdline[i].options) != 0)
+               if (newcon->setup &&
+                   newcon->setup(newcon, console_cmdline[i].options) != 0)
                        break;
-               console->flags |= CON_ENABLED;
-               console->index = console_cmdline[i].index;
+               newcon->flags |= CON_ENABLED;
+               newcon->index = console_cmdline[i].index;
                if (i == selected_console) {
-                       console->flags |= CON_CONSDEV;
+                       newcon->flags |= CON_CONSDEV;
                        preferred_console = selected_console;
                }
                break;
        }
 
-       if (!(console->flags & CON_ENABLED))
+       if (!(newcon->flags & CON_ENABLED))
                return;
 
-       if (bootconsole && (console->flags & CON_CONSDEV)) {
-               printk(KERN_INFO "console handover: boot [%s%d] -> real [%s%d]\n",
-                      bootconsole->name, bootconsole->index,
-                      console->name, console->index);
-               unregister_console(bootconsole);
-               console->flags &= ~CON_PRINTBUFFER;
-       } else {
-               printk(KERN_INFO "console [%s%d] enabled\n",
-                      console->name, console->index);
-       }
+       /*
+        * If we have a bootconsole, and are switching to a real console,
+        * don't print everything out again, since when the boot console, and
+        * the real console are the same physical device, it's annoying to
+        * see the beginning boot messages twice
+        */
+       if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
+               newcon->flags &= ~CON_PRINTBUFFER;
 
        /*
         *      Put this console in the list - keep the
         *      preferred driver at the head of the list.
         */
-       acquire_console_sem();
-       if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
-               console->next = console_drivers;
-               console_drivers = console;
-               if (console->next)
-                       console->next->flags &= ~CON_CONSDEV;
+       console_lock();
+       if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {
+               newcon->next = console_drivers;
+               console_drivers = newcon;
+               if (newcon->next)
+                       newcon->next->flags &= ~CON_CONSDEV;
        } else {
-               console->next = console_drivers->next;
-               console_drivers->next = console;
+               newcon->next = console_drivers->next;
+               console_drivers->next = newcon;
        }
-       if (console->flags & CON_PRINTBUFFER) {
+       if (newcon->flags & CON_PRINTBUFFER) {
                /*
-                * release_console_sem() will print out the buffered messages
+                * console_unlock(); will print out the buffered messages
                 * for us.
                 */
                spin_lock_irqsave(&logbuf_lock, flags);
                con_start = log_start;
                spin_unlock_irqrestore(&logbuf_lock, flags);
+               /*
+                * We're about to replay the log buffer.  Only do this to the
+                * just-registered console to avoid excessive message spam to
+                * the already-registered consoles.
+                */
+               exclusive_console = newcon;
+       }
+       console_unlock();
+       console_sysfs_notify();
+
+       /*
+        * By unregistering the bootconsoles after we enable the real console
+        * we get the "console xxx enabled" message on all the consoles -
+        * boot consoles, real consoles, etc - this is to ensure that end
+        * users know there might be something in the kernel's log buffer that
+        * went to the bootconsole (that they do not see on the real console)
+        */
+       if (bcon &&
+           ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&
+           !keep_bootcon) {
+               /* we need to iterate through twice, to make sure we print
+                * everything out, before we unregister the console(s)
+                */
+               printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",
+                       newcon->name, newcon->index);
+               for_each_console(bcon)
+                       if (bcon->flags & CON_BOOT)
+                               unregister_console(bcon);
+       } else {
+               printk(KERN_INFO "%sconsole [%s%d] enabled\n",
+                       (newcon->flags & CON_BOOT) ? "boot" : "" ,
+                       newcon->name, newcon->index);
        }
-       release_console_sem();
 }
 EXPORT_SYMBOL(register_console);
 
@@ -1251,7 +1571,7 @@ int unregister_console(struct console *console)
                return braille_unregister_console(console);
 #endif
 
-       acquire_console_sem();
+       console_lock();
        if (console_drivers == console) {
                console_drivers=console->next;
                res = 0;
@@ -1273,66 +1593,43 @@ int unregister_console(struct console *console)
        if (console_drivers != NULL && console->flags & CON_CONSDEV)
                console_drivers->flags |= CON_CONSDEV;
 
-       release_console_sem();
+       console_unlock();
+       console_sysfs_notify();
        return res;
 }
 EXPORT_SYMBOL(unregister_console);
 
-static int __init disable_boot_consoles(void)
+static int __init printk_late_init(void)
 {
-       if (console_drivers != NULL) {
-               if (console_drivers->flags & CON_BOOT) {
+       struct console *con;
+
+       for_each_console(con) {
+               if (!keep_bootcon && con->flags & CON_BOOT) {
                        printk(KERN_INFO "turn off boot console %s%d\n",
-                               console_drivers->name, console_drivers->index);
-                       return unregister_console(console_drivers);
+                               con->name, con->index);
+                       unregister_console(con);
                }
        }
+       hotcpu_notifier(console_cpu_notify, 0);
        return 0;
 }
-late_initcall(disable_boot_consoles);
-
-/**
- * tty_write_message - write a message to a certain tty, not just the console.
- * @tty: the destination tty_struct
- * @msg: the message to write
- *
- * This is used for messages that need to be redirected to a specific tty.
- * We don't put it into the syslog queue right now maybe in the future if
- * really needed.
- */
-void tty_write_message(struct tty_struct *tty, char *msg)
-{
-       if (tty && tty->ops->write)
-               tty->ops->write(tty, msg, strlen(msg));
-       return;
-}
+late_initcall(printk_late_init);
 
 #if defined CONFIG_PRINTK
+
 /*
  * printk rate limiting, lifted from the networking subsystem.
  *
- * This enforces a rate limit: not more than one kernel message
- * every printk_ratelimit_jiffies to make a denial-of-service
- * attack impossible.
+ * This enforces a rate limit: not more than 10 kernel messages
+ * every 5s to make a denial-of-service attack impossible.
  */
-int __printk_ratelimit(int ratelimit_jiffies, int ratelimit_burst)
-{
-       return __ratelimit(ratelimit_jiffies, ratelimit_burst);
-}
-EXPORT_SYMBOL(__printk_ratelimit);
+DEFINE_RATELIMIT_STATE(printk_ratelimit_state, 5 * HZ, 10);
 
-/* minimum time in jiffies between messages */
-int printk_ratelimit_jiffies = 5 * HZ;
-
-/* number of messages we send before ratelimiting */
-int printk_ratelimit_burst = 10;
-
-int printk_ratelimit(void)
+int __printk_ratelimit(const char *func)
 {
-       return __printk_ratelimit(printk_ratelimit_jiffies,
-                               printk_ratelimit_burst);
+       return ___ratelimit(&printk_ratelimit_state, func);
 }
-EXPORT_SYMBOL(printk_ratelimit);
+EXPORT_SYMBOL(__printk_ratelimit);
 
 /**
  * printk_timed_ratelimit - caller-controlled printk ratelimiting
@@ -1346,11 +1643,116 @@ EXPORT_SYMBOL(printk_ratelimit);
 bool printk_timed_ratelimit(unsigned long *caller_jiffies,
                        unsigned int interval_msecs)
 {
-       if (*caller_jiffies == 0 || time_after(jiffies, *caller_jiffies)) {
-               *caller_jiffies = jiffies + msecs_to_jiffies(interval_msecs);
+       if (*caller_jiffies == 0
+                       || !time_in_range(jiffies, *caller_jiffies,
+                                       *caller_jiffies
+                                       + msecs_to_jiffies(interval_msecs))) {
+               *caller_jiffies = jiffies;
                return true;
        }
        return false;
 }
 EXPORT_SYMBOL(printk_timed_ratelimit);
+
+static DEFINE_SPINLOCK(dump_list_lock);
+static LIST_HEAD(dump_list);
+
+/**
+ * kmsg_dump_register - register a kernel log dumper.
+ * @dumper: pointer to the kmsg_dumper structure
+ *
+ * Adds a kernel log dumper to the system. The dump callback in the
+ * structure will be called when the kernel oopses or panics and must be
+ * set. Returns zero on success and %-EINVAL or %-EBUSY otherwise.
+ */
+int kmsg_dump_register(struct kmsg_dumper *dumper)
+{
+       unsigned long flags;
+       int err = -EBUSY;
+
+       /* The dump callback needs to be set */
+       if (!dumper->dump)
+               return -EINVAL;
+
+       spin_lock_irqsave(&dump_list_lock, flags);
+       /* Don't allow registering multiple times */
+       if (!dumper->registered) {
+               dumper->registered = 1;
+               list_add_tail_rcu(&dumper->list, &dump_list);
+               err = 0;
+       }
+       spin_unlock_irqrestore(&dump_list_lock, flags);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(kmsg_dump_register);
+
+/**
+ * kmsg_dump_unregister - unregister a kmsg dumper.
+ * @dumper: pointer to the kmsg_dumper structure
+ *
+ * Removes a dump device from the system. Returns zero on success and
+ * %-EINVAL otherwise.
+ */
+int kmsg_dump_unregister(struct kmsg_dumper *dumper)
+{
+       unsigned long flags;
+       int err = -EINVAL;
+
+       spin_lock_irqsave(&dump_list_lock, flags);
+       if (dumper->registered) {
+               dumper->registered = 0;
+               list_del_rcu(&dumper->list);
+               err = 0;
+       }
+       spin_unlock_irqrestore(&dump_list_lock, flags);
+       synchronize_rcu();
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(kmsg_dump_unregister);
+
+/**
+ * kmsg_dump - dump kernel log to kernel message dumpers.
+ * @reason: the reason (oops, panic etc) for dumping
+ *
+ * Iterate through each of the dump devices and call the oops/panic
+ * callbacks with the log buffer.
+ */
+void kmsg_dump(enum kmsg_dump_reason reason)
+{
+       unsigned long end;
+       unsigned chars;
+       struct kmsg_dumper *dumper;
+       const char *s1, *s2;
+       unsigned long l1, l2;
+       unsigned long flags;
+
+       /* Theoretically, the log could move on after we do this, but
+          there's not a lot we can do about that. The new messages
+          will overwrite the start of what we dump. */
+       spin_lock_irqsave(&logbuf_lock, flags);
+       end = log_end & LOG_BUF_MASK;
+       chars = logged_chars;
+       spin_unlock_irqrestore(&logbuf_lock, flags);
+
+       if (chars > end) {
+               s1 = log_buf + log_buf_len - chars + end;
+               l1 = chars - end;
+
+               s2 = log_buf;
+               l2 = end;
+       } else {
+               s1 = "";
+               l1 = 0;
+
+               s2 = log_buf + end - chars;
+               l2 = chars;
+       }
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(dumper, &dump_list, list)
+               dumper->dump(dumper, reason, s1, l1, s2, l2);
+       rcu_read_unlock();
+}
 #endif