android_kernel_samsung_hero.../fs/proc/stlog.c

737 lines
16 KiB
C
Raw Normal View History

2016-08-17 10:41:52 +02:00
/*
* linux/fs/proc/stlog.c
*
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/kernel.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/proc_fs.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/stlog.h>
extern wait_queue_head_t ringbuf_wait;
/*
* Compatibility Issue :
* dumpstate process of Android M tries to open stlog node with O_NONBLOCK.
* But on Android L, it tries to open stlog node without O_NONBLOCK.
* In order to resolve this issue, stlog_open() always works as NONBLOCK mode.
* If you want runtime debugging, please use stlog_open_pipe().
*/
static int stlog_open(struct inode * inode, struct file * file)
{
//Open as non-blocking mode for printing once.
file->f_flags |= O_NONBLOCK;
return do_stlog(STLOG_ACTION_OPEN, NULL, 0, STLOG_FROM_PROC);
}
static int stlog_open_pipe(struct inode * inode, struct file * file)
{
//Open as blocking mode for runtime debugging
file->f_flags &= ~(O_NONBLOCK);
return do_stlog(STLOG_ACTION_OPEN, NULL, 0, STLOG_FROM_PROC);
}
static int stlog_release(struct inode * inode, struct file * file)
{
(void) do_stlog(STLOG_ACTION_CLOSE, NULL, 0, STLOG_FROM_PROC);
return 0;
}
static ssize_t stlog_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
//Blocking mode for runtime debugging
if (!(file->f_flags & O_NONBLOCK))
return do_stlog(STLOG_ACTION_READ, buf, count, STLOG_FROM_PROC);
//Non-blocking mode, print once, consume all the buffers
return do_stlog(STLOG_ACTION_READ_ALL, buf, count, STLOG_FROM_PROC);
}
static ssize_t stlog_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
return do_stlog_write(STLOG_ACTION_WRITE, buf, count, STLOG_FROM_READER);
}
loff_t stlog_llseek(struct file *file, loff_t offset, int whence)
{
return (loff_t)do_stlog(STLOG_ACTION_SIZE_BUFFER, 0, 0, STLOG_FROM_READER);
}
static const struct file_operations stlog_operations = {
.read = stlog_read,
.write = stlog_write,
.open = stlog_open,
.release = stlog_release,
.llseek = stlog_llseek,
};
static const struct file_operations stlog_pipe_operations = {
.read = stlog_read,
.open = stlog_open_pipe,
.release = stlog_release,
.llseek = stlog_llseek,
};
static const char DEF_STLOG_VER_STR[] = "1.0.2\n";
static ssize_t stlog_ver_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
ssize_t ret = 0;
loff_t off = *ppos;
ssize_t len = strlen(DEF_STLOG_VER_STR);
if (off >= len)
return 0;
len -= off;
if (count < len)
return -ENOMEM;
ret = copy_to_user(buf, &DEF_STLOG_VER_STR[off], len);
if (ret < 0)
return ret;
len -= ret;
*ppos += len;
return len;
}
static const struct file_operations stlog_ver_operations = {
.read = stlog_ver_read,
};
static int __init stlog_init(void)
{
proc_create("stlog", S_IRUGO, NULL, &stlog_operations);
proc_create("stlog_pipe", S_IRUGO, NULL, &stlog_pipe_operations);
proc_create("stlog_version", S_IRUGO, NULL, &stlog_ver_operations);
return 0;
}
module_init(stlog_init);
//#define CONFIG_STLOG_CPU_ID
#define CONFIG_STLOG_PID
#define CONFIG_STLOG_TIME
#ifdef CONFIG_STLOG
#define CONFIG_RINGBUF_SHIFT 15 /*32KB*/
#else
#define CONFIG_RINGBUF_SHIFT 10 /*1KB*/
#endif
//#define DEBUG_STLOG
#if defined(CONFIG_STLOG_CPU_ID)
static bool stlog_cpu_id = 1;
#else
static bool stlog_cpu_id;
#endif
module_param_named(cpu, stlog_cpu_id, bool, S_IRUGO | S_IWUSR);
#if defined(CONFIG_STLOG_PID)
static bool stlog_pid = 1;
#else
static bool stlog_pid;
#endif
module_param_named(pid, stlog_pid, bool, S_IRUGO | S_IWUSR);
enum ringbuf_flags {
RINGBUF_NOCONS = 1,
RINGBUF_NEWLINE = 2,
RINGBUF_PREFIX = 4,
RINGBUF_CONT = 8,
};
struct ringbuf {
u64 ts_nsec;
u16 len;
u16 text_len;
#ifdef CONFIG_STLOG_CPU_ID
u8 cpu_id;
#endif
#ifdef CONFIG_STLOG_PID
char comm[TASK_COMM_LEN];
pid_t pid;
#endif
u8 flags:5;
u8 level:3;
};
/*
* The ringbuf_lock protects smsg buffer, indices, counters.
*/
static DEFINE_RAW_SPINLOCK(ringbuf_lock);
DECLARE_WAIT_QUEUE_HEAD(ringbuf_wait);
/* the next stlog record to read by /proc/stlog */
static u64 stlog_seq;
static u32 stlog_idx;
static enum ringbuf_flags ringbuf_prev;
static u64 ringbuf_first_seq;
static u32 ringbuf_first_idx;
static u64 ringbuf_next_seq;
static u32 ringbuf_next_idx;
static u64 stlog_clear_seq;
static u32 stlog_clear_idx;
#define S_PREFIX_MAX 32
#define RINGBUF_LINE_MAX 1024 - S_PREFIX_MAX
/* record buffer */
#define RINGBUF_ALIGN __alignof__(struct ringbuf)
#define __RINGBUF_LEN (1 << CONFIG_RINGBUF_SHIFT)
static char __ringbuf_buf[__RINGBUF_LEN] __aligned(RINGBUF_ALIGN);
static char *ringbuf_buf = __ringbuf_buf;
static u32 ringbuf_buf_len = __RINGBUF_LEN;
/* cpu currently holding logbuf_lock */
static volatile unsigned int ringbuf_cpu = UINT_MAX;
/* human readable text of the record */
static char *ringbuf_text(const struct ringbuf *msg)
{
return (char *)msg + sizeof(struct ringbuf);
}
static struct ringbuf *ringbuf_from_idx(u32 idx)
{
struct ringbuf *msg = (struct ringbuf *)(ringbuf_buf + idx);
if (!msg->len)
return (struct ringbuf *)ringbuf_buf;
return msg;
}
static u32 ringbuf_next(u32 idx)
{
struct ringbuf *msg = (struct ringbuf *)(ringbuf_buf + idx);
if (!msg->len) {
msg = (struct ringbuf *)ringbuf_buf;
return msg->len;
}
return idx + msg->len;
}
static void ringbuf_store(enum ringbuf_flags flags, const char *text, u16 text_len,
u8 cpu_id, struct task_struct *owner)
{
struct ringbuf *msg;
u32 size, pad_len;
#ifdef DEBUG_STLOG
printk("[STLOG] %s stlog_seq %llu stlog_idx %lu ringbuf_first_seq %llu ringbuf_first_idx %lu ringbuf_next_seq %llu ringbuf_next_idx %lu \n",
__func__,stlog_seq,stlog_idx,ringbuf_first_seq,ringbuf_first_idx,ringbuf_next_seq,ringbuf_next_idx);
#endif
/* number of '\0' padding bytes to next message */
size = sizeof(struct ringbuf) + text_len;
pad_len = (-size) & (RINGBUF_ALIGN - 1);
size += pad_len;
while (ringbuf_first_seq < ringbuf_next_seq) {
u32 free;
if (ringbuf_next_idx > ringbuf_first_idx)
free = max(ringbuf_buf_len - ringbuf_next_idx, ringbuf_first_idx);
else
free = ringbuf_first_idx - ringbuf_next_idx;
if (free > size + sizeof(struct ringbuf))
break;
/* drop old messages until we have enough space */
ringbuf_first_idx = ringbuf_next(ringbuf_first_idx);
ringbuf_first_seq++;
}
if (ringbuf_next_idx + size + sizeof(struct ringbuf) >= ringbuf_buf_len) {
memset(ringbuf_buf + ringbuf_next_idx, 0, sizeof(struct ringbuf));
ringbuf_next_idx = 0;
}
/* fill message */
msg = (struct ringbuf *)(ringbuf_buf + ringbuf_next_idx);
memcpy(ringbuf_text(msg), text, text_len);
msg->text_len = text_len;
msg->flags = flags & 0x1f;
#ifdef CONFIG_STLOG_CPU_ID
msg->cpu_id = cpu_id;
#endif
#ifdef CONFIG_STLOG_PID
msg->pid = owner->pid;
memcpy(msg->comm, owner->comm, TASK_COMM_LEN);
#endif
msg->ts_nsec = local_clock();
msg->len = sizeof(struct ringbuf) + text_len + pad_len;
/* insert message */
ringbuf_next_idx += msg->len;
ringbuf_next_seq++;
wake_up_interruptible(&ringbuf_wait);
}
#if defined(CONFIG_STLOG_TIME)
static bool stlog_time = 1;
#else
static bool stlog_time;
#endif
module_param_named(time, stlog_time, bool, S_IRUGO | S_IWUSR);
static size_t stlog_print_time(u64 ts, char *buf)
{
unsigned long rem_nsec;
if (!stlog_time)
return 0;
rem_nsec = do_div(ts, 1000000000);
if (!buf)
return snprintf(NULL, 0, "[%5lu.000000] ", (unsigned long)ts);
return sprintf(buf, "[%5lu.%06lu] ",
(unsigned long)ts, rem_nsec / 1000);
}
#ifdef CONFIG_STLOG_PID
static size_t stlog_print_pid(const struct ringbuf *msg, char *buf)
{
if (!stlog_pid)
return 0;
if (!buf)
return snprintf(NULL, 0, "[%15s, %d] ", msg->comm, msg->pid);
return sprintf(buf, "[%15s, %d] ", msg->comm, msg->pid);
}
#else
static size_t stlog_print_pid(const struct ringbuf *msg, char *buf)
{
return 0;
}
#endif
#ifdef CONFIG_STLOG_CPU_ID
static size_t stlog_print_cpuid(const struct ringbuf *msg, char *buf)
{
if (!stlog_cpu_id)
return 0;
if (!buf)
return snprintf(NULL, 0, "C%d ", msg->cpu_id);
return sprintf(buf, "C%d ", msg->cpu_id);
}
#else
static size_t stlog_print_cpuid(const struct ringbuf *msg, char *buf)
{
return 0;
}
#endif
static size_t stlog_print_prefix(const struct ringbuf *msg, bool ringbuf, char *buf)
{
size_t len = 0;
len += stlog_print_time(msg->ts_nsec, buf ? buf + len : NULL);
len += stlog_print_cpuid(msg, buf ? buf + len : NULL);
len += stlog_print_pid(msg, buf ? buf + len : NULL);
return len;
}
static size_t stlog_print_text(const struct ringbuf *msg, enum ringbuf_flags prev,
bool ringbuf, char *buf, size_t size)
{
const char *text = ringbuf_text(msg);
size_t text_size = msg->text_len;
bool prefix = true;
bool newline = true;
size_t len = 0;
do {
const char *next = memchr(text, '\n', text_size);
size_t text_len;
if (next) {
text_len = next - text;
next++;
text_size -= next - text;
} else {
text_len = text_size;
}
if (buf) {
if (stlog_print_prefix(msg, ringbuf, NULL) + text_len + 1 >= size - len)
break;
if (prefix)
len += stlog_print_prefix(msg, ringbuf, buf + len);
memcpy(buf + len, text, text_len);
len += text_len;
if (next || newline)
buf[len++] = '\n';
} else {
/* buffer size only calculation */
if (prefix)
len += stlog_print_prefix(msg, ringbuf, NULL);
len += text_len;
if (next || newline)
len++;
}
prefix = true;
text = next;
} while (text);
return len;
}
static int stlog_print(char __user *buf, int size)
{
char *text;
struct ringbuf *msg;
int len = 0;
#ifdef DEBUG_STLOG
printk("[STLOG] %s stlog_seq %llu stlog_idx %lu ringbuf_first_seq %llu ringbuf_first_idx %lu ringbuf_next_seq %llu ringbuf_next_idx %lu \n",
__func__,stlog_seq,stlog_idx,ringbuf_first_seq,ringbuf_first_idx,ringbuf_next_seq,ringbuf_next_idx);
#endif
text = kmalloc(RINGBUF_LINE_MAX + S_PREFIX_MAX, GFP_KERNEL);
if (!text)
return -ENOMEM;
while (size > 0) {
size_t n;
raw_spin_lock_irq(&ringbuf_lock);
if (stlog_seq < ringbuf_first_seq) {
/* messages are gone, move to first one */
stlog_seq = ringbuf_first_seq;
stlog_idx = ringbuf_first_idx;
ringbuf_prev = 0;
}
if (stlog_seq == ringbuf_next_seq) {
raw_spin_unlock_irq(&ringbuf_lock);
break;
}
msg = ringbuf_from_idx(stlog_idx);
n = stlog_print_text(msg, ringbuf_prev, false, text, RINGBUF_LINE_MAX + S_PREFIX_MAX);
if (n <= size) {
/* message fits into buffer, move forward */
stlog_idx = ringbuf_next(stlog_idx);
stlog_seq++;
ringbuf_prev = msg->flags;
} else if (!len){
n = size;
} else
n = 0;
raw_spin_unlock_irq(&ringbuf_lock);
if (!n)
break;
if (copy_to_user(buf, text, n)) {
if (!len)
len = -EFAULT;
break;
}
len += n;
size -= n;
buf += n;
}
kfree(text);
return len;
}
static int stlog_print_all(char __user *buf, int size, bool clear)
{
char *text;
int len = 0;
text = kmalloc(RINGBUF_LINE_MAX + S_PREFIX_MAX, GFP_KERNEL);
if (!text)
return -ENOMEM;
raw_spin_lock_irq(&ringbuf_lock);
if (buf) {
u64 next_seq;
u64 seq;
u32 idx;
enum ringbuf_flags prev;
if (stlog_clear_seq < ringbuf_first_seq) {
/* messages are gone, move to first available one */
stlog_clear_seq = ringbuf_first_seq;
stlog_clear_idx = ringbuf_first_idx;
}
seq = stlog_clear_seq;
idx = stlog_clear_idx;
prev = 0;
while (seq < ringbuf_next_seq) {
struct ringbuf *msg = ringbuf_from_idx(idx);
len += stlog_print_text(msg, prev, false, NULL, 0);
prev = msg->flags;
idx = ringbuf_next(idx);
seq++;
}
/* move first record forward until length fits into the buffer */
seq = stlog_clear_seq;
idx = stlog_clear_idx;
prev = 0;
while (len > size && seq < ringbuf_next_seq) {
struct ringbuf *msg = ringbuf_from_idx(idx);
len -= stlog_print_text(msg, prev, false, NULL, 0);
prev = msg->flags;
idx = ringbuf_next(idx);
seq++;
}
/* last message fitting into this dump */
next_seq = ringbuf_next_seq;
len = 0;
prev = 0;
while (len >= 0 && seq < next_seq) {
struct ringbuf *msg = ringbuf_from_idx(idx);
int textlen;
textlen = stlog_print_text(msg, prev, false, text,
RINGBUF_LINE_MAX + S_PREFIX_MAX);
if (textlen < 0) {
len = textlen;
break;
}
idx = ringbuf_next(idx);
seq++;
prev = msg->flags;
raw_spin_unlock_irq(&ringbuf_lock);
if (copy_to_user(buf + len, text, textlen))
len = -EFAULT;
else{
#ifdef DEBUG_STLOG
printk("[STLOG] %s seq %llu text %s \n",__func__,seq,text);
#endif
len += textlen;
}
raw_spin_lock_irq(&ringbuf_lock);
if (seq < ringbuf_first_seq) {
/* messages are gone, move to next one */
seq = ringbuf_first_seq;
idx = ringbuf_first_idx;
prev = 0;
}
}
}
if (clear) {
stlog_clear_seq = ringbuf_next_seq;
stlog_clear_idx = ringbuf_next_idx;
}
raw_spin_unlock_irq(&ringbuf_lock);
kfree(text);
return len;
}
int do_stlog(int type, char __user *buf, int len, bool from_file)
{
int error=0;
switch (type) {
case STLOG_ACTION_CLOSE: /* Close log */
break;
case STLOG_ACTION_OPEN: /* Open log */
#ifdef DEBUG_STLOG
printk("[STLOG] %s OPEN stlog_seq %llu stlog_idx %lu ringbuf_first_seq %llu ringbuf_first_idx %lu ringbuf_next_seq %llu ringbuf_next_idx %lu \n",
__func__,stlog_seq,stlog_idx,ringbuf_first_seq,ringbuf_first_idx,ringbuf_next_seq,ringbuf_next_idx);
#endif
break;
case STLOG_ACTION_READ: /* cat -f /proc/stlog */
#ifdef DEBUG_STLOG
printk("[STLOG] %s READ stlog_seq %llu stlog_idx %lu ringbuf_first_seq %llu ringbuf_first_idx %lu ringbuf_next_seq %llu ringbuf_next_idx %lu \n",
__func__,stlog_seq,stlog_idx,ringbuf_first_seq,ringbuf_first_idx,ringbuf_next_seq,ringbuf_next_idx);
#endif
error = -EINVAL;
if (!buf || len < 0)
goto out;
if (!len)
goto out;
if (!access_ok(VERIFY_WRITE, buf, len)) {
error = -EFAULT;
goto out;
}
error = wait_event_interruptible(ringbuf_wait,
stlog_seq != ringbuf_next_seq);
if (error)
goto out;
error = stlog_print(buf, len);
break;
case STLOG_ACTION_READ_ALL: /* cat /proc/stlog */ /* dumpstate */
#ifdef DEBUG_STLOG
printk("[STLOG] %s READ_ALL stlog_seq %llu stlog_idx %lu ringbuf_first_seq %llu ringbuf_first_idx %lu ringbuf_next_seq %llu ringbuf_next_idx %lu \n",
__func__,stlog_seq,stlog_idx,ringbuf_first_seq,ringbuf_first_idx,ringbuf_next_seq,ringbuf_next_idx);
#endif
error = -EINVAL;
if (!buf || len < 0)
goto out;
error = 0;
if (!len)
goto out;
if (!access_ok(VERIFY_WRITE, buf, len)) {
error = -EFAULT;
goto out;
}
if(stlog_clear_seq==ringbuf_next_seq){
stlog_clear_seq=ringbuf_first_seq;
stlog_clear_idx=ringbuf_first_idx;
error=0;
goto out;
}
error = stlog_print_all(buf, len, true);
break;
/* Size of the log buffer */
case STLOG_ACTION_SIZE_BUFFER:
#ifdef DEBUG_STLOG
printk("[STLOG] %s SIZE_BUFFER %lu\n",__func__,ringbuf_buf_len);
#endif
error = ringbuf_buf_len;
break;
default:
error = -EINVAL;
break;
}
out:
return error;
}
int do_stlog_write(int type, const char __user *buf, int len, bool from_file)
{
int error=0;
char *kern_buf=0;
char *line=0;
if (!buf || len < 0)
goto out;
if (!len)
goto out;
if (len > RINGBUF_LINE_MAX)
return -EINVAL;
kern_buf = kmalloc(len+1, GFP_KERNEL);
if (kern_buf == NULL)
return -ENOMEM;
line = kern_buf;
if (copy_from_user(line, buf, len)) {
error = -EFAULT;
goto out;
}
line[len] = '\0';
error = stlog("%s", line);
if ((line[len-1] == '\n') && (error == (len-1)))
error++;
out:
kfree(kern_buf);
return error;
}
asmlinkage int vstlog(const char *fmt, va_list args)
{
static char textbuf[RINGBUF_LINE_MAX];
char *text = textbuf;
size_t text_len;
enum ringbuf_flags lflags = 0;
unsigned long flags;
int this_cpu;
int printed_len = 0;
bool stored = false;
local_irq_save(flags);
this_cpu = smp_processor_id();
raw_spin_lock(&ringbuf_lock);
ringbuf_cpu = this_cpu;
text_len = vscnprintf(text, sizeof(textbuf), fmt, args);
/* mark and strip a trailing newline */
if (text_len && text[text_len-1] == '\n') {
text_len--;
lflags |= RINGBUF_NEWLINE;
}
if (!stored)
ringbuf_store(lflags,text, text_len, ringbuf_cpu, current);
printed_len += text_len;
raw_spin_unlock(&ringbuf_lock);
local_irq_restore(flags);
return printed_len;
}
EXPORT_SYMBOL(vstlog);
/**
* stlog - print a storage message
* @fmt: format string
*/
asmlinkage int stlog(const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = vstlog(fmt, args);
va_end(args);
return r;
}
EXPORT_SYMBOL(stlog);