/* * linux/fs/proc/stlog.c * */ #include #include #include #include #include #include #include #include #include #include #include #include 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);