/* Copyright (c) 2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #define pr_fmt(fmt) "sde_evtlog:[%s] " fmt, __func__ #include #include #include #include #include #include #include "sde_dbg.h" #include "sde_trace.h" #ifdef CONFIG_DRM_SDE_EVTLOG_DEBUG #define SDE_EVTLOG_DEFAULT_ENABLE 1 #else #define SDE_EVTLOG_DEFAULT_ENABLE 0 #endif #define SDE_DBG_DEFAULT_PANIC 1 /* * evtlog will print this number of entries when it is called through * sysfs node or panic. This prevents kernel log from evtlog message * flood. */ #define SDE_EVTLOG_PRINT_ENTRY 256 /* * evtlog keeps this number of entries in memory for debug purpose. This * number must be greater than print entry to prevent out of bound evtlog * entry array access. */ #define SDE_EVTLOG_ENTRY (SDE_EVTLOG_PRINT_ENTRY * 4) #define SDE_EVTLOG_MAX_DATA 15 #define SDE_EVTLOG_BUF_MAX 512 #define SDE_EVTLOG_BUF_ALIGN 32 DEFINE_SPINLOCK(sde_evtloglock); struct tlog { u32 counter; s64 time; const char *name; int line; u32 data[SDE_EVTLOG_MAX_DATA]; u32 data_cnt; int pid; }; static struct sde_dbg_evtlog { struct tlog logs[SDE_EVTLOG_ENTRY]; u32 first; u32 last; u32 curr; struct dentry *evtlog; u32 evtlog_enable; u32 panic_on_err; struct work_struct evtlog_dump_work; bool work_panic; } sde_dbg_evtlog; static inline bool sde_evtlog_is_enabled(u32 flag) { return (flag & sde_dbg_evtlog.evtlog_enable) || (flag == SDE_EVTLOG_ALL && sde_dbg_evtlog.evtlog_enable); } void sde_evtlog(const char *name, int line, int flag, ...) { unsigned long flags; int i, val = 0; va_list args; struct tlog *log; if (!sde_evtlog_is_enabled(flag)) return; spin_lock_irqsave(&sde_evtloglock, flags); log = &sde_dbg_evtlog.logs[sde_dbg_evtlog.curr]; log->time = ktime_to_us(ktime_get()); log->name = name; log->line = line; log->data_cnt = 0; log->pid = current->pid; va_start(args, flag); for (i = 0; i < SDE_EVTLOG_MAX_DATA; i++) { val = va_arg(args, int); if (val == SDE_EVTLOG_DATA_LIMITER) break; log->data[i] = val; } va_end(args); log->data_cnt = i; sde_dbg_evtlog.curr = (sde_dbg_evtlog.curr + 1) % SDE_EVTLOG_ENTRY; sde_dbg_evtlog.last++; trace_sde_evtlog(name, line, i > 0 ? log->data[0] : 0, i > 1 ? log->data[1] : 0); spin_unlock_irqrestore(&sde_evtloglock, flags); } /* always dump the last entries which are not dumped yet */ static bool _sde_evtlog_dump_calc_range(void) { static u32 next; bool need_dump = true; unsigned long flags; struct sde_dbg_evtlog *evtlog = &sde_dbg_evtlog; spin_lock_irqsave(&sde_evtloglock, flags); evtlog->first = next; if (evtlog->last == evtlog->first) { need_dump = false; goto dump_exit; } if (evtlog->last < evtlog->first) { evtlog->first %= SDE_EVTLOG_ENTRY; if (evtlog->last < evtlog->first) evtlog->last += SDE_EVTLOG_ENTRY; } if ((evtlog->last - evtlog->first) > SDE_EVTLOG_PRINT_ENTRY) { pr_warn("evtlog buffer overflow before dump: %d\n", evtlog->last - evtlog->first); evtlog->first = evtlog->last - SDE_EVTLOG_PRINT_ENTRY; } next = evtlog->first + 1; dump_exit: spin_unlock_irqrestore(&sde_evtloglock, flags); return need_dump; } static ssize_t sde_evtlog_dump_entry(char *evtlog_buf, ssize_t evtlog_buf_size) { int i; ssize_t off = 0; struct tlog *log, *prev_log; unsigned long flags; spin_lock_irqsave(&sde_evtloglock, flags); log = &sde_dbg_evtlog.logs[sde_dbg_evtlog.first % SDE_EVTLOG_ENTRY]; prev_log = &sde_dbg_evtlog.logs[(sde_dbg_evtlog.first - 1) % SDE_EVTLOG_ENTRY]; off = snprintf((evtlog_buf + off), (evtlog_buf_size - off), "%s:%-4d", log->name, log->line); if (off < SDE_EVTLOG_BUF_ALIGN) { memset((evtlog_buf + off), 0x20, (SDE_EVTLOG_BUF_ALIGN - off)); off = SDE_EVTLOG_BUF_ALIGN; } off += snprintf((evtlog_buf + off), (evtlog_buf_size - off), "=>[%-8d:%-11llu:%9llu][%-4d]:", sde_dbg_evtlog.first, log->time, (log->time - prev_log->time), log->pid); for (i = 0; i < log->data_cnt; i++) off += snprintf((evtlog_buf + off), (evtlog_buf_size - off), "%x ", log->data[i]); off += snprintf((evtlog_buf + off), (evtlog_buf_size - off), "\n"); spin_unlock_irqrestore(&sde_evtloglock, flags); return off; } static void _sde_evtlog_dump_all(void) { char evtlog_buf[SDE_EVTLOG_BUF_MAX]; while (_sde_evtlog_dump_calc_range()) { sde_evtlog_dump_entry(evtlog_buf, SDE_EVTLOG_BUF_MAX); pr_info("%s", evtlog_buf); } } static void _sde_dump_array(bool dead, const char *name) { _sde_evtlog_dump_all(); if (dead && sde_dbg_evtlog.panic_on_err) panic(name); } static void _sde_dump_work(struct work_struct *work) { _sde_dump_array(sde_dbg_evtlog.work_panic, "evtlog_workitem"); } void sde_dbg_dump(bool queue, const char *name, ...) { int i; bool dead = false; va_list args; char *blk_name = NULL; if (!sde_evtlog_is_enabled(SDE_EVTLOG_DEFAULT)) return; if (queue && work_pending(&sde_dbg_evtlog.evtlog_dump_work)) return; va_start(args, name); for (i = 0; i < SDE_EVTLOG_MAX_DATA; i++) { blk_name = va_arg(args, char*); if (IS_ERR_OR_NULL(blk_name)) break; if (!strcmp(blk_name, "panic")) dead = true; } va_end(args); if (queue) { /* schedule work to dump later */ sde_dbg_evtlog.work_panic = dead; schedule_work(&sde_dbg_evtlog.evtlog_dump_work); } else { _sde_dump_array(dead, name); } } static int sde_evtlog_dump_open(struct inode *inode, struct file *file) { /* non-seekable */ file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); file->private_data = inode->i_private; return 0; } static ssize_t sde_evtlog_dump_read(struct file *file, char __user *buff, size_t count, loff_t *ppos) { ssize_t len = 0; char evtlog_buf[SDE_EVTLOG_BUF_MAX]; if (_sde_evtlog_dump_calc_range()) { len = sde_evtlog_dump_entry(evtlog_buf, SDE_EVTLOG_BUF_MAX); if (copy_to_user(buff, evtlog_buf, len)) return -EFAULT; *ppos += len; } return len; } static ssize_t sde_evtlog_dump_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { _sde_evtlog_dump_all(); if (sde_dbg_evtlog.panic_on_err) panic("sde"); return count; } static const struct file_operations sde_evtlog_fops = { .open = sde_evtlog_dump_open, .read = sde_evtlog_dump_read, .write = sde_evtlog_dump_write, }; int sde_evtlog_init(struct dentry *debugfs_root) { int i; sde_dbg_evtlog.evtlog = debugfs_create_dir("evt_dbg", debugfs_root); if (IS_ERR_OR_NULL(sde_dbg_evtlog.evtlog)) { pr_err("debugfs_create_dir fail, error %ld\n", PTR_ERR(sde_dbg_evtlog.evtlog)); sde_dbg_evtlog.evtlog = NULL; return -ENODEV; } INIT_WORK(&sde_dbg_evtlog.evtlog_dump_work, _sde_dump_work); sde_dbg_evtlog.work_panic = false; for (i = 0; i < SDE_EVTLOG_ENTRY; i++) sde_dbg_evtlog.logs[i].counter = i; debugfs_create_file("dump", 0644, sde_dbg_evtlog.evtlog, NULL, &sde_evtlog_fops); debugfs_create_u32("enable", 0644, sde_dbg_evtlog.evtlog, &sde_dbg_evtlog.evtlog_enable); debugfs_create_u32("panic", 0644, sde_dbg_evtlog.evtlog, &sde_dbg_evtlog.panic_on_err); sde_dbg_evtlog.evtlog_enable = SDE_EVTLOG_DEFAULT_ENABLE; sde_dbg_evtlog.panic_on_err = SDE_DBG_DEFAULT_PANIC; pr_info("evtlog_status: enable:%d, panic:%d\n", sde_dbg_evtlog.evtlog_enable, sde_dbg_evtlog.panic_on_err); return 0; } void sde_evtlog_destroy(void) { debugfs_remove(sde_dbg_evtlog.evtlog); }