Add KERNEL_LOG support
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 993ef99..d25d1f2 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -755,6 +755,11 @@
---help---
Select Y if you want to expose some gpio pins through sysfs.
+config KERNEL_LOG
+ bool "Kernel ram log"
+ ---help---
+ Select Y for support for keeping kernel logs in physical memory
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e123479..52aca8e 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -86,3 +86,4 @@
obj-$(CONFIG_HRES_COUNTER) += hres_counter.o
obj-$(CONFIG_NDUID) += nduid.o
obj-$(CONFIG_USER_PINS) += user-pins.o
+obj-$(CONFIG_KERNEL_LOG) += klog.o
diff --git a/drivers/misc/klog.c b/drivers/misc/klog.c
new file mode 100644
index 0000000..4ef57a5
--- /dev/null
+++ b/drivers/misc/klog.c
@@ -0,0 +1,396 @@
+/*
+ *
+ * Copyright (C) 2008-2009 Palm, Inc.
+ * Copyright (C) 2012 James Sullins
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/miscdevice.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/klog.h>
+#include <asm/memory.h>
+#include <asm/io.h>
+
+#include <linux/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#define MIN(a,b) ((a)<(b) ? (a):(b))
+
+#define KLOG_MAGIC 0x6b6c6f67 // 'klog'
+#define KLOG_VERSION 1
+
+extern int log_buf_get_len(void);
+
+static const char *last_klog_names[] = {
+ "last_klog",
+ "last_klog2",
+ "last_klog3",
+ NULL
+};
+
+struct klog_header {
+ uint32_t magic;
+ uint32_t ver;
+ uint32_t len;
+
+ uint32_t buf_count;
+ uint32_t current_buf;
+
+ uint32_t buf_table[0]; // offsets from start of this header to klog buffers
+};
+
+#define KLOG_BUFFER_MAGIC 0x6b627566 // 'kbuf'
+
+struct klog_buffer_header {
+ uint32_t magic;
+ uint32_t len;
+ uint32_t head;
+ uint32_t tail;
+
+ uint8_t data[0];
+};
+
+static unsigned long klog_phys;
+static unsigned long klog_len;
+
+static int init_done = 0;
+
+static char *klog_buffer;
+
+static struct klog_header *klog;
+static struct klog_buffer_header *klog_buf;
+
+static void klog_copy_logbuf(void);
+int log_buf_copy(char *dest, int idx, int len);
+
+static DEFINE_SPINLOCK(klog_lock);
+
+static inline struct klog_buffer_header *get_kbuf(int num)
+{
+ return (struct klog_buffer_header *)((uint8_t *)klog + klog->buf_table[num]);
+}
+
+static inline struct klog_buffer_header *get_last_kbuf(int num)
+{
+ int lastnum;
+
+ if (num < 1 || num >= klog->buf_count) return NULL;
+
+ lastnum = klog->current_buf - num;
+ if (lastnum <0) lastnum += klog->buf_count;
+
+ return (struct klog_buffer_header *)
+ ((uint8_t *)klog + klog->buf_table[lastnum]);
+}
+
+static inline unsigned get_last_klog_size(int num)
+{
+ struct klog_buffer_header *lkbuf = get_last_kbuf(num);
+
+ if (!lkbuf) return 0;
+
+ if (lkbuf->magic != KLOG_BUFFER_MAGIC) {
+ return 0;
+ }
+ else if (lkbuf->tail == lkbuf->head) {
+ return 0;
+ }
+ else if (lkbuf->head > lkbuf->tail) {
+ return lkbuf->head - lkbuf->tail;
+ }
+ else {
+ return lkbuf->len - 1;
+ }
+}
+
+static inline uint32_t
+inc_pointer(struct klog_buffer_header *klog, uint32_t pointer, uint32_t inc)
+{
+ pointer += inc;
+ if (pointer >= klog->len)
+ pointer -= klog->len;
+
+ return pointer;
+}
+
+static void _klog_write(const char *s, unsigned int count)
+{
+ unsigned int towrite;
+
+ if (klog_buf == NULL)
+ return;
+
+ /* trim the write if it happens to be huge */
+ if (count > klog_buf->len - 1)
+ count = klog_buf->len - 1;
+
+ while (count > 0) {
+ /* write up to the end of the buffer */
+ towrite = MIN(count, klog_buf->len - klog_buf->head);
+
+ /* does this need to increment the tail? */
+ {
+ uint32_t vtail = klog_buf->tail;
+ if (klog_buf->tail <= klog_buf->head)
+ vtail += klog_buf->len;
+
+ if (klog_buf->head + towrite >= vtail)
+ klog_buf->tail = inc_pointer(klog_buf, klog_buf->head, towrite + 1);
+ }
+
+ /* copy */
+ memcpy(klog_buf->data + klog_buf->head, s, towrite);
+ klog_buf->head = inc_pointer(klog_buf, klog_buf->head, towrite);
+ count -= towrite;
+ s += towrite;
+ }
+}
+
+static void klog_copy_logbuf()
+{
+ unsigned int count;
+ unsigned int towrite;
+
+ if (klog_buf == NULL)
+ return;
+
+ count = log_buf_get_len();
+
+ while (count > 0) {
+ /* write up to the end of the buffer */
+ towrite = MIN(count, klog_buf->len - klog_buf->head);
+
+ /* does this need to increment the tail? */
+ {
+ uint32_t vtail = klog_buf->tail;
+ if (klog_buf->tail <= klog_buf->head)
+ vtail += klog_buf->len;
+
+ if (klog_buf->head + towrite >= vtail)
+ klog_buf->tail = inc_pointer(klog_buf, klog_buf->head, towrite + 1);
+ }
+
+ /* copy */
+ log_buf_copy(klog_buf->data + klog_buf->head, 0, towrite);
+ klog_buf->head = inc_pointer(klog_buf, klog_buf->head, towrite);
+ count -= towrite;
+ }
+}
+
+
+void klog_printf(const char *fmt, ...)
+{
+ static char klog_print_buf[1024];
+
+ unsigned long flags;
+ unsigned int len;
+ va_list args;
+
+ spin_lock_irqsave(&klog_lock, flags);
+
+ va_start(args, fmt);
+ len = vscnprintf(klog_print_buf, sizeof(klog_print_buf), fmt, args);
+ va_end(args);
+
+ _klog_write(klog_print_buf, len);
+
+ spin_unlock_irqrestore(&klog_lock, flags);
+}
+
+void klog_write(const char *s, unsigned int count)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&klog_lock, flags);
+
+ _klog_write(s, count);
+
+ spin_unlock_irqrestore(&klog_lock, flags);
+}
+
+static ssize_t last_klog_read(struct file *file, char __user *buf,
+ size_t len, loff_t *offset)
+{
+ loff_t pos = *offset;
+ ssize_t count, lcount, ocount;
+ unsigned lastnum = 0;
+ unsigned lastsize;
+ const char* req_name = NULL;
+ struct klog_buffer_header *lkbuf;
+ char *lbuf;
+ unsigned i;
+
+ if (file && file->f_path.dentry) {
+ req_name = file->f_path.dentry->d_name.name;
+ }
+
+ if (!req_name) return -EFAULT;
+
+ for (i = 0; (i < klog->buf_count - 1 && last_klog_names[i]); i++) {
+ if (!strcmp(req_name, last_klog_names[i])) {
+ lastnum = i + 1;
+ break;
+ }
+ }
+ if (lastnum == 0) return -EFAULT;
+
+ lastsize = get_last_klog_size(lastnum);
+ if (pos >= lastsize) return 0;
+
+ lkbuf = get_last_kbuf(lastnum);
+ lbuf = (char*)lkbuf->data;
+
+ if (lkbuf->head > lkbuf->tail) {
+ count = min(len, (size_t)(lastsize - pos));
+ if (copy_to_user(buf, lbuf + lkbuf->tail + pos, count))
+ return -EFAULT;
+
+ *offset += count;
+ return count;
+ }
+
+ count = min(len, (size_t)(lastsize - pos));
+ ocount = count;
+
+ while (count) {
+ if (pos >= 0 && pos < lkbuf->len - lkbuf->tail) {
+ lcount = min((uint32_t)count, (uint32_t)(lkbuf->len - lkbuf->tail)
+ - (uint32_t)pos);
+ if (copy_to_user(buf, lbuf + lkbuf->tail + pos, lcount))
+ return -EFAULT;
+ } else {
+ lcount = min((uint32_t)count,
+ lastsize - (lkbuf->len - lkbuf->tail));
+ if (copy_to_user(buf, lbuf + pos - (lkbuf->len - lkbuf->tail),
+ lcount))
+ return -EFAULT;
+ }
+ buf += lcount;
+ pos += lcount;
+ count -= lcount;
+ }
+
+ *offset += ocount;
+ return ocount;
+}
+
+static const struct file_operations last_klog_file_ops = {
+ .owner = THIS_MODULE,
+ .read = last_klog_read,
+};
+
+void setup_proc_last_klog(void)
+{
+ struct proc_dir_entry *entry;
+ unsigned i;
+
+ for (i = 0; (i < klog->buf_count - 1 && last_klog_names[i]); i++) {
+ entry = create_proc_entry(last_klog_names[i]
+ , S_IFREG | S_IRUGO, NULL);
+ if (!entry) {
+ printk(KERN_ERR "klog_init: failed to create proc entry '%s'\n",
+ last_klog_names[i]);
+ } else {
+ entry->proc_fops = &last_klog_file_ops;
+ entry->size = get_last_klog_size(i+1);
+ printk(KERN_INFO "klog_init: %s head=%u tail=%u size=%u\n",
+ last_klog_names[i], get_last_kbuf(i+1)->head,
+ get_last_kbuf(i+1)->tail, get_last_klog_size(i+1));
+ }
+ }
+}
+
+static int __init klog_init(void)
+{
+ void *base;
+ unsigned long flags;
+
+ printk(KERN_INFO "klog_init: phys buffer at 0x%lx\n", klog_phys);
+
+ if (klog_phys == 0 || klog_len == 0)
+ return 0;
+
+ if (!request_mem_region(klog_phys, klog_len, "klog"))
+ return 0;
+
+ base = ioremap(klog_phys, klog_len);
+ if (base == 0)
+ return 0;
+
+ /* set up the klog structure */
+ klog_buffer = (char *)base;
+ klog = (struct klog_header *)klog_buffer;
+ printk(KERN_INFO "klog_init: virt address at 0x%p\n", klog);
+
+ /* check to see if it's valid */
+ if (klog->magic != KLOG_MAGIC || klog->ver != KLOG_VERSION) {
+ printk(KERN_ERR "klog_init: valid klog not found\n");
+ return 0;
+ }
+
+ printk(KERN_INFO "klog_init: found valid klog, len %u\n", klog->len);
+
+ klog_buf = get_kbuf(klog->current_buf);
+
+ spin_lock_irqsave(&klog_lock, flags);
+
+ klog_copy_logbuf();
+
+ init_done = 1;
+
+ spin_unlock_irqrestore(&klog_lock, flags);
+
+ printk(KERN_INFO "klog_init: using buffer %u at 0x%p, length %d\n",
+ klog->current_buf, klog_buf, klog_buf->len);
+
+ setup_proc_last_klog();
+
+ return 0;
+}
+
+subsys_initcall(klog_init);
+
+static int __init klog_setup(char *this_opt)
+{
+ klog_phys = simple_strtoul(this_opt, NULL, 0);
+
+ return 1;
+}
+
+__setup("klog=", klog_setup);
+
+static int __init klog_len_setup(char *this_opt)
+{
+ klog_len = simple_strtoul(this_opt, NULL, 0);
+
+ return 1;
+}
+
+__setup("klog_len=", klog_len_setup);
+
+
diff --git a/include/linux/klog.h b/include/linux/klog.h
new file mode 100644
index 0000000..19698ef
--- /dev/null
+++ b/include/linux/klog.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2008-2009 Palm, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __KLOG_H
+#define __KLOG_H
+
+#if CONFIG_KERNEL_LOG
+extern void klog_write(const char *s, unsigned int count);
+extern void klog_printf(const char *fmt, ...);
+#else
+#define klog_write(s, count) ((void)0)
+#define klog_printf(fmt, ...) ((void)0)
+#endif
+
+#endif
+
diff --git a/kernel/printk.c b/kernel/printk.c
index 8e3af82..b2406b3 100644
--- a/kernel/printk.c
+++ b/kernel/printk.c
@@ -57,6 +57,10 @@
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
+#ifdef CONFIG_KERNEL_LOG
+#include <linux/klog.h>
+#endif
+
/* printk's without a loglevel use this.. */
#define DEFAULT_MESSAGE_LOGLEVEL CONFIG_DEFAULT_MESSAGE_LOGLEVEL
@@ -297,10 +301,11 @@
/*
* Return the number of unread characters in the log buffer.
*/
-static int log_buf_get_len(void)
+int log_buf_get_len(void)
{
return logged_chars;
}
+EXPORT_SYMBOL(log_buf_get_len);
/*
* Clears the ring-buffer
@@ -948,6 +953,9 @@
printed_len += vscnprintf(printk_buf + printed_len,
sizeof(printk_buf) - printed_len, fmt, args);
+#ifdef CONFIG_KERNEL_LOG
+ klog_write(printk_buf, printed_len);
+#endif
p = printk_buf;
#ifdef CONFIG_LGE_CRASH_HANDLER