printk: Fix console_sem vs logbuf_lock unlock race
[pandora-kernel.git] / kernel / printk.c
index da8ca81..37dff34 100644 (file)
@@ -31,6 +31,7 @@
 #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>
@@ -167,46 +168,74 @@ void log_buf_kexec_setup(void)
 }
 #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
 
@@ -753,7 +782,7 @@ static inline int can_use_console(unsigned int cpu)
 static int console_trylock_for_printk(unsigned int cpu)
        __releases(&logbuf_lock)
 {
-       int retval = 0;
+       int retval = 0, wake = 0;
 
        if (console_trylock()) {
                retval = 1;
@@ -766,12 +795,14 @@ static int console_trylock_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 [] =
@@ -1213,7 +1244,7 @@ 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(&console_sem);
@@ -1222,6 +1253,7 @@ void console_unlock(void)
 
        console_may_schedule = 0;
 
+again:
        for ( ; ; ) {
                spin_lock_irqsave(&logbuf_lock, flags);
                wake_klogd |= log_start - log_end;
@@ -1242,8 +1274,23 @@ void console_unlock(void)
        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();
 }