CONFIG_PRINTK_PERSIST: persist printk buffer across reboots.

Instead of using alloc_bootmem() when log_buf_len= is provided on the
command line, use reserve_bootmem() instead to try to reserve the memory at
a predictable physical address.  If we manage to get such an address, check
whether it has a valid header from last time, and if so, keep the data in
the existing buffer as if it had been printk'd as part of the current
session.  You can then retrieve or clear it with dmesg.  Note: you must
supply log_buf_len= on the kernel command line to activate this feature.

If reserve_bootmem() doesn't work out, we fall back to the old
alloc_bootmem() method.

The nice thing about this feature is it allows us to capture and upload
printk results after a crash and reboot, even if the system had hard crashed
so there was no chance to do something like panic or kexec.  The last few
messages before the crash might give a clue as to the crash.

Note: None of this is any use if your bootloader or BIOS wipes memory
between reboots.  On embedded systems, you have somewhat more control over
this.

Change-Id: Ia62774b435ab889f8e0285a0d30212bc47dd5d32
diff --git a/init/Kconfig b/init/Kconfig
index eb4b337..072d249 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -818,6 +818,18 @@
 	  very difficult to diagnose system problems, saying N here is
 	  strongly discouraged.
 
+config PRINTK_PERSIST
+	default n
+	bool "printk log persists across reboots" if PRINTK
+	help
+	  This option tries to keep the printk memory buffer in a well-known
+	  location in physical memory. It isn't cleared on reboot (unless RAM
+	  is wiped by your boot loader or BIOS) so if your system crashes
+	  or panics, you might get to examine all the log messages next time you
+	  boot. The persisted log messages show up in your 'dmesg' output.
+	  Note: you must supply the log_buf_len= kernel parameter to
+	  activate this feature.
+
 config BUG
 	bool "BUG() support" if EMBEDDED
 	default y
diff --git a/kernel/printk.c b/kernel/printk.c
index 6aaaf6e..e5e11af 100755
--- a/kernel/printk.c
+++ b/kernel/printk.c
@@ -115,7 +115,12 @@
  */
 static unsigned log_start;	/* Index into log_buf: next char to be read by syslog() */
 static unsigned con_start;	/* Index into log_buf: next char to be sent to consoles */
+
+#ifdef CONFIG_PRINTK_PERSIST
+#define log_end logbits->_log_end
+#else
 static unsigned log_end;	/* Index into log_buf: most-recently-written-char + 1 */
+#endif
 
 /*
  *	Array of consoles built from command line options (console=)
@@ -164,9 +169,92 @@
 static int saved_console_loglevel = -1;
 static char __log_buf[__LOG_BUF_LEN];
 static char *log_buf = __log_buf;
+
+#ifndef CONFIG_PRINTK_PERSIST
+
 static int log_buf_len = __LOG_BUF_LEN;
 static unsigned logged_chars; /* Number of chars produced since last read+clear operation */
 
+static __init char *log_buf_alloc(unsigned long size, unsigned *dest_offset)
+{
+	return alloc_bootmem(size);
+}
+
+#else  /* CONFIG_PRINTK_PERSIST */
+
+struct logbits {
+	int magic; /* needed to verify the memory across reboots */
+	int _log_buf_len; /* leading _ so they aren't replaced by #define */
+	unsigned _logged_chars;
+	unsigned _log_end;
+};
+static struct logbits __logbits = {
+	._log_buf_len = __LOG_BUF_LEN,
+};
+static struct logbits *logbits = &__logbits;
+#define log_buf_len logbits->_log_buf_len
+#define logged_chars logbits->_logged_chars
+
+#define PERSIST_SEARCH_START 0
+#define PERSIST_SEARCH_END 0xfe000000
+#define PERSIST_SEARCH_JUMP (16*1024*1024)
+#define PERSIST_MAGIC 0xbabb1e
+
+/*
+ * size is a power of 2 so that the printk offset mask will work.  We'll add
+ * a bit more space to the end of the buffer for our extra data, but that
+ * won't change the offset of the buffers.
+ */
+static __init char *log_buf_alloc(unsigned long size, unsigned *dest_offset)
+{
+	unsigned long where;
+	char *buf;
+	unsigned long full_size = size + sizeof(struct logbits);
+	struct logbits *new_logbits;
+
+	for (where = PERSIST_SEARCH_END - size;
+			where >= PERSIST_SEARCH_START;
+			where -= PERSIST_SEARCH_JUMP) {
+		if (reserve_bootmem(where, full_size, BOOTMEM_EXCLUSIVE))
+			continue;
+
+		buf = phys_to_virt(where);
+		new_logbits = phys_to_virt(where + size);
+		printk(KERN_INFO "printk_persist: memory reserved @ 0x%08lx\n",
+			where);
+		if (new_logbits->magic != PERSIST_MAGIC ||
+				new_logbits->_log_buf_len != size ||
+				new_logbits->_logged_chars > size ||
+				new_logbits->_log_end > size * 2) {
+			printk(KERN_INFO "printk_persist: header invalid, "
+				"cleared.\n");
+			memset(buf, 0, full_size);
+			new_logbits->magic = PERSIST_MAGIC;
+			new_logbits->_log_buf_len = size;
+			new_logbits->_logged_chars = 0;
+			new_logbits->_log_end = 0;
+		} else {
+			printk(KERN_INFO "printk_persist: header valid; "
+				"logged=%d next=%d\n",
+				new_logbits->_logged_chars,
+				new_logbits->_log_end);
+		}
+		*dest_offset = new_logbits->_log_end;
+		new_logbits->_log_end = log_end;
+		new_logbits->_logged_chars += logged_chars;
+		logbits = new_logbits;
+		return buf;
+	}
+	goto error;
+
+error:
+	/* replace the buffer, but don't bother to swap struct logbits */
+	printk(KERN_ERR "printk_persist: failed to reserve bootmem "
+		"area. disabled.\n");
+	return alloc_bootmem(full_size);
+}
+#endif  /* CONFIG_PRINTK_PERSIST */
+
 static int __init log_buf_len_setup(char *str)
 {
 	unsigned size = memparse(str, &str);
@@ -175,10 +263,10 @@
 	if (size)
 		size = roundup_pow_of_two(size);
 	if (size > log_buf_len) {
-		unsigned start, dest_idx, offset;
+		unsigned start, dest_offset = 0, dest_idx, offset;
 		char *new_log_buf;
 
-		new_log_buf = alloc_bootmem(size);
+		new_log_buf = log_buf_alloc(size, &dest_offset);
 		if (!new_log_buf) {
 			printk(KERN_WARNING "log_buf_len: allocation failed\n");
 			goto out;
@@ -189,15 +277,16 @@
 		log_buf = new_log_buf;
 
 		offset = start = min(con_start, log_start);
-		dest_idx = 0;
+		dest_idx = dest_offset;
 		while (start != log_end) {
-			log_buf[dest_idx] = __log_buf[start & (__LOG_BUF_LEN - 1)];
+			log_buf[dest_idx & (size - 1)] =
+				__log_buf[start & (__LOG_BUF_LEN - 1)];
 			start++;
 			dest_idx++;
 		}
-		log_start -= offset;
-		con_start -= offset;
-		log_end -= offset;
+		log_start += dest_offset - offset;
+		con_start += dest_offset - offset;
+		log_end += dest_offset - offset;
 		spin_unlock_irqrestore(&logbuf_lock, flags);
 
 		printk(KERN_NOTICE "log_buf_len: %d\n", log_buf_len);