blob: 72832776659d479faed3d85a3c047372d0f5e9c7 [file] [log] [blame]
/* 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 <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/ktime.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/dma-buf.h>
#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);
}