Files
kernel-tenderloin-3.0/drivers/misc/klog.c
2012-01-02 20:39:39 -06:00

292 lines
6.3 KiB
C

/*
*
* 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.
*/
#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>
#define MIN(a,b) ((a)<(b) ? (a):(b))
#define KLOG_MAGIC 0x6b6c6f67 // 'klog'
#define KLOG_VERSION 1
extern int log_buf_get_len(void);
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 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_write_char(const char c)
{
uint32_t vtail;
if (klog_buf == NULL)
return;
vtail = klog_buf->tail;
if (klog_buf->tail <= klog_buf->head)
vtail += klog_buf->len;
if (klog_buf->head + 1 >= vtail)
klog_buf->tail = inc_pointer(klog_buf, klog_buf->head, 2);
/* copy */
*(klog_buf->data + klog_buf->head) = c;
klog_buf->head = inc_pointer(klog_buf, klog_buf->head, 1);
}
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);
}
void klog_write_char(const char c)
{
unsigned long flags;
spin_lock_irqsave(&klog_lock, flags);
if (init_done) _klog_write_char(c);
spin_unlock_irqrestore(&klog_lock, flags);
}
static int __init klog_init(void)
{
void *base;
unsigned long flags;
printk("klog_init: entry\n");
printk("klog_init: phys buffer is 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("klog_init: virt address is %p\n", klog);
printk("klog_init: magic 0x%x version 0x%x\n", klog->magic, klog->ver);
/* check to see if it's valid */
if (klog->magic != KLOG_MAGIC || klog->ver != KLOG_VERSION) {
printk("klog_init: didn't find existing klog\n");
return 0;
}
printk("found klog, len %u, using buffer number %d\n", klog->len, klog->current_buf);
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 "welcome to klog, buffer at %p, length %d\n", klog_buf, klog_buf->len);
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);