kernel_samsung_a53x/drivers/soc/samsung/debug/debug-snapshot-log.c
2024-06-15 16:02:09 -03:00

1046 lines
34 KiB
C
Executable file

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/ktime.h>
#include <linux/clk-provider.h>
#include <linux/sched/clock.h>
#include <linux/ftrace.h>
#include <linux/kernel_stat.h>
#include <linux/irqnr.h>
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <asm/stacktrace.h>
#include <soc/samsung/debug-snapshot.h>
#include "debug-snapshot-local.h"
#include <trace/events/power.h>
#include <trace/events/timer.h>
#include <trace/events/workqueue.h>
#include <trace/events/irq.h>
#include <trace/events/sched.h>
#include <trace/events/rwmmio.h>
struct dbg_snapshot_log_item dss_log_items[] = {
[DSS_LOG_TASK_ID] = {DSS_LOG_TASK, {0, 0, 0, false}, 0, false},
[DSS_LOG_WORK_ID] = {DSS_LOG_WORK, {0, 0, 0, false}, 0, false},
[DSS_LOG_CPUIDLE_ID] = {DSS_LOG_CPUIDLE, {0, 0, 0, false}, 0, false},
[DSS_LOG_SUSPEND_ID] = {DSS_LOG_SUSPEND, {0, 0, 0, false}, 0, false},
[DSS_LOG_IRQ_ID] = {DSS_LOG_IRQ, {0, 0, 0, false}, 0, false},
[DSS_LOG_HRTIMER_ID] = {DSS_LOG_HRTIMER, {0, 0, 0, false}, 0, false},
[DSS_LOG_REG_ID] = {DSS_LOG_REG, {0, 0, 0, false}, 0, false},
[DSS_LOG_CLK_ID] = {DSS_LOG_CLK, {0, 0, 0, false}, 0, false},
[DSS_LOG_PMU_ID] = {DSS_LOG_PMU, {0, 0, 0, false}, 0, false},
[DSS_LOG_FREQ_ID] = {DSS_LOG_FREQ, {0, 0, 0, false}, 0, false},
[DSS_LOG_DM_ID] = {DSS_LOG_DM, {0, 0, 0, false}, 0, false},
[DSS_LOG_REGULATOR_ID] = {DSS_LOG_REGULATOR, {0, 0, 0, false}, 0, false},
[DSS_LOG_THERMAL_ID] = {DSS_LOG_THERMAL, {0, 0, 0, false}, 0, false},
[DSS_LOG_ACPM_ID] = {DSS_LOG_ACPM, {0, 0, 0, false}, 0, false},
[DSS_LOG_PRINTK_ID] = {DSS_LOG_PRINTK, {0, 0, 0, false}, 0, false},
};
struct dbg_snapshot_log_misc dss_log_misc;
static char dss_freq_name[SZ_32][SZ_8];
static unsigned int dss_freq_size = 0;
struct dbg_snapshot_log_item *dbg_snapshot_get_log_item(char *name)
{
struct dbg_snapshot_log_item *log_item;
int i;
if (!name)
return NULL;
for (i = 0; i < ARRAY_SIZE(dss_log_items); i++) {
log_item = &dss_log_items[i];
if (log_item->name && !strcmp(log_item->name, name)) {
return log_item;
}
}
return NULL;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_get_log_item);
#define dss_get_log(item) \
long dss_get_sizeof_##item##_log(void) { \
return sizeof(struct item##_log); \
} \
EXPORT_SYMBOL_GPL(dss_get_sizeof_##item##_log); \
long dss_get_len_##item##_log(void) { \
return (long)dbg_snapshot_get_log_item(#item"_log")->log_num; \
} \
EXPORT_SYMBOL_GPL(dss_get_len_##item##_log); \
long dss_get_last_##item##_log_idx(void) { \
return (atomic_read(&dss_log_misc.item##_log_idx) - 1) % \
dss_get_len_##item##_log(); \
} \
EXPORT_SYMBOL_GPL(dss_get_last_##item##_log_idx); \
long dss_get_first_##item##_log_idx(void) { \
int v = atomic_read(&dss_log_misc.item##_log_idx); \
return v < dss_get_len_##item##_log() ? 0 : \
v % dss_get_len_##item##_log(); \
} \
EXPORT_SYMBOL_GPL(dss_get_first_##item##_log_idx); \
struct item##_log *dss_get_last_##item##_log(void) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
return &entry[dss_get_last_##item##_log_idx()]; \
} \
EXPORT_SYMBOL_GPL(dss_get_last_##item##_log); \
unsigned long dss_get_last_paddr_##item##_log(void) { \
long last_idx = dss_get_last_##item##_log_idx(); \
struct dbg_snapshot_log_item *log_item = \
dbg_snapshot_get_log_item(#item"_log"); \
return log_item ? log_item->entry.paddr + \
(last_idx * sizeof(struct item##_log)) : 0; \
} \
EXPORT_SYMBOL_GPL(dss_get_last_paddr_##item##_log); \
struct item##_log *dss_get_first_##item##_log(void) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
return &entry[dss_get_first_##item##_log_idx()]; \
} \
EXPORT_SYMBOL_GPL(dss_get_first_##item##_log); \
struct item##_log *dss_get_##item##_log_by_idx(int idx) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
if (idx < 0 || idx >= dss_get_len_##item##_log()) \
return NULL; \
return &entry[idx]; \
} \
EXPORT_SYMBOL_GPL(dss_get_##item##_log_by_idx); \
struct item##_log *dss_get_##item##_log_iter(int idx) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
if (!(idx % dss_get_len_##item##_log())) \
idx = 0; \
else if (idx < 0) \
idx = dss_get_len_##item##_log() - \
(abs(idx) % dss_get_len_##item##_log()); \
else \
idx = idx % dss_get_len_##item##_log(); \
return &entry[idx]; \
} \
EXPORT_SYMBOL_GPL(dss_get_##item##_log_iter); \
unsigned long dss_get_vaddr_##item##_log(void) { \
return (unsigned long) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
} \
EXPORT_SYMBOL_GPL(dss_get_vaddr_##item##_log)
#define dss_get_log_by_cpu(item) \
long dss_get_sizeof_##item##_log(void) { \
return sizeof(struct item##_log); \
} \
EXPORT_SYMBOL_GPL(dss_get_sizeof_##item##_log); \
long dss_get_len_##item##_log(void) { \
return (long)dbg_snapshot_get_log_item(#item"_log")->arr_num; \
} \
EXPORT_SYMBOL_GPL(dss_get_len_##item##_log); \
long dss_get_len_##item##_log_by_cpu(int cpu) { \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return -EINVAL; \
return (long)dbg_snapshot_get_log_item(#item"_log")->log_num; \
} \
EXPORT_SYMBOL_GPL(dss_get_len_##item##_log_by_cpu); \
long dss_get_last_##item##_log_idx(int cpu) { \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return -EINVAL; \
return (atomic_read(&dss_log_misc.item##_log_idx[cpu]) - 1) % \
dss_get_len_##item##_log_by_cpu(cpu); \
} \
EXPORT_SYMBOL_GPL(dss_get_last_##item##_log_idx); \
long dss_get_first_##item##_log_idx(int cpu) { \
int v = atomic_read(&dss_log_misc.item##_log_idx[cpu]); \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return -EINVAL; \
return v < dss_get_len_##item##_log_by_cpu(cpu) ? 0 : \
v % dss_get_len_##item##_log_by_cpu(cpu); \
} \
EXPORT_SYMBOL_GPL(dss_get_first_##item##_log_idx); \
struct item##_log *dss_get_last_##item##_log(int cpu) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return NULL; \
entry = &entry[dss_get_len_##item##_log_by_cpu(cpu) * cpu]; \
return &entry[dss_get_last_##item##_log_idx(cpu)]; \
} \
EXPORT_SYMBOL_GPL(dss_get_last_##item##_log); \
struct item##_log *dss_get_first_##item##_log(int cpu) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return NULL; \
entry = &entry[dss_get_len_##item##_log_by_cpu(cpu) * cpu]; \
return &entry[dss_get_first_##item##_log_idx(cpu)]; \
} \
EXPORT_SYMBOL_GPL(dss_get_first_##item##_log); \
unsigned long dss_get_last_paddr_##item##_log(int cpu) { \
long last_idx = dss_get_last_##item##_log_idx(cpu); \
struct dbg_snapshot_log_item *log_item = \
dbg_snapshot_get_log_item(#item"_log"); \
last_idx += cpu * dss_get_len_##item##_log_by_cpu(cpu); \
return log_item ? log_item->entry.paddr + \
(last_idx * dss_get_sizeof_##item##_log()) : 0; \
} \
EXPORT_SYMBOL_GPL(dss_get_last_paddr_##item##_log); \
struct item##_log *dss_get_##item##_log_by_idx(int cpu, int idx) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return NULL; \
if (idx < 0 || idx >= dss_get_len_##item##_log_by_cpu(cpu)) \
return NULL; \
entry = &entry[dss_get_len_##item##_log_by_cpu(cpu) * cpu]; \
return &entry[idx]; \
} \
EXPORT_SYMBOL_GPL(dss_get_##item##_log_by_idx); \
struct item##_log *dss_get_##item##_log_by_cpu_iter(int cpu, int idx) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return NULL; \
entry = &entry[dss_get_len_##item##_log_by_cpu(cpu) * cpu]; \
if (!(idx % dss_get_len_##item##_log_by_cpu(cpu))) \
idx = 0; \
else if (idx < 0) \
idx = dss_get_len_##item##_log_by_cpu(cpu) - \
(abs(idx) % dss_get_len_##item##_log_by_cpu(cpu)); \
else \
idx = idx % dss_get_len_##item##_log_by_cpu(cpu); \
return &entry[idx]; \
} \
EXPORT_SYMBOL_GPL(dss_get_##item##_log_by_cpu_iter); \
unsigned long dss_get_vaddr_##item##_log_by_cpu(int cpu) { \
struct item##_log *entry = (struct item##_log *) \
dbg_snapshot_get_log_item(#item"_log")->entry.vaddr; \
if (cpu < 0 || cpu >= dss_get_len_##item##_log()) \
return 0; \
entry = &entry[dss_get_len_##item##_log_by_cpu(cpu) * cpu]; \
return (unsigned long)entry; \
} \
EXPORT_SYMBOL_GPL(dss_get_vaddr_##item##_log_by_cpu)
#ifdef CONFIG_DEBUG_SNAPSHOT_API
dss_get_log_by_cpu(task);
dss_get_log_by_cpu(work);
dss_get_log_by_cpu(cpuidle);
dss_get_log_by_cpu(freq);
dss_get_log(suspend);
dss_get_log_by_cpu(irq);
dss_get_log_by_cpu(hrtimer);
dss_get_log_by_cpu(reg);
dss_get_log(clk);
dss_get_log(pmu);
dss_get_log(dm);
dss_get_log(regulator);
dss_get_log(acpm);
dss_get_log(print);
#endif
dss_get_log(thermal);
static inline bool dbg_snapshot_is_log_item_enabled(int id)
{
bool item_enabled = dbg_snapshot_get_item_enable(DSS_ITEM_KEVENTS);
bool log_enabled = dss_log_items[id].entry.enabled;
return dbg_snapshot_get_enable() && item_enabled && log_enabled;
}
void *dbg_snapshot_log_get_item_by_index(int index)
{
if (index < 0 || index > ARRAY_SIZE(dss_log_items))
return NULL;
return &dss_log_items[index];
}
EXPORT_SYMBOL_GPL(dbg_snapshot_log_get_item_by_index);
int dbg_snapshot_log_get_num_items(void)
{
return ARRAY_SIZE(dss_log_items);
}
EXPORT_SYMBOL_GPL(dbg_snapshot_log_get_num_items);
int dbg_snapshot_get_freq_idx(const char *name)
{
unsigned int i;
for (i = 0; i < dss_freq_size; i++) {
if (!strncmp(dss_freq_name[i], name, strlen(name)))
return i;
}
return -EFAULT;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_get_freq_idx);
char *dbg_snapshot_get_freq_name(int idx, char *dest, size_t dest_size)
{
if (idx >= dss_freq_size || sizeof(dss_freq_name[0]) > dest_size)
return NULL;
strncpy(dest, dss_freq_name[idx], sizeof(dss_freq_name[0]));
return dest;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_get_freq_name);
void dbg_snapshot_log_output(void)
{
unsigned long i;
pr_info("debug-snapshot-log physical / virtual memory layout:\n");
for (i = 0; i < ARRAY_SIZE(dss_log_items); i++) {
if (dss_log_items[i].entry.enabled)
pr_info("%-12s: phys:0x%zx / virt:0x%zx / size:0x%zx / en:%d\n",
dss_log_items[i].name,
dss_log_items[i].entry.paddr,
dss_log_items[i].entry.vaddr,
dss_log_items[i].entry.size,
dss_log_items[i].entry.enabled);
}
}
void dbg_snapshot_set_policy_log_item(const char *name, bool en)
{
struct dbg_snapshot_log_item *log_item;
int i;
if (!name)
return;
for (i = 0; i < (int)ARRAY_SIZE(dss_log_items); i++) {
log_item = &dss_log_items[i];
if (log_item->name && !strcmp(log_item->name, name)) {
log_item->policy = en;
pr_info("log item policy - %s is %sabled\n", name, en ? "en" : "dis");
break;
}
}
}
static bool dbg_snapshot_get_enable_log_item(const char *name)
{
struct dbg_snapshot_log_item *log_item;
int i;
if (!name)
return false;
for (i = 0; i < ARRAY_SIZE(dss_log_items); i++) {
log_item = &dss_log_items[i];
if (log_item->name &&
!strncmp(log_item->name, name, strlen(name))) {
return log_item->entry.enabled;
}
}
return false;
}
static void dbg_snapshot_task(int cpu, struct task_struct *task)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_TASK_ID];
struct task_log *entry = (struct task_log *)log_item->entry.vaddr;
idx = (atomic_fetch_inc(&dss_log_misc.task_log_idx[cpu]) % log_item->log_num) +
(cpu * log_item->log_num);
entry[idx].time = local_clock();
entry[idx].task = task;
entry[idx].pid = task_pid_nr(task);
strncpy(entry[idx].task_comm, task->comm, TASK_COMM_LEN - 1);
}
static void dbg_snapshot_sched_switch(void *ignore, bool preempt,
struct task_struct *prev,
struct task_struct *next)
{
dbg_snapshot_task(raw_smp_processor_id(), next);
}
static void dbg_snapshot_work(work_func_t fn, int en)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_WORK_ID];
struct work_log *entry = (struct work_log *)log_item->entry.vaddr;
int cpu = raw_smp_processor_id();
idx = (atomic_fetch_inc(&dss_log_misc.work_log_idx[cpu]) % log_item->log_num) +
(cpu * log_item->log_num);
entry[idx].time = local_clock();
entry[idx].fn = fn;
entry[idx].en = en;
}
static void dbg_snapshot_wq_start(void *ignore, struct work_struct *work)
{
dbg_snapshot_work(work->func, DSS_FLAG_IN);
}
static void dbg_snapshot_wq_end(void *ignore, struct work_struct *work,
work_func_t func)
{
dbg_snapshot_work(func, DSS_FLAG_OUT);
}
void dbg_snapshot_cpuidle(char *modes, unsigned int state, s64 diff, int en)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_CPUIDLE_ID];
struct cpuidle_log *entry = (struct cpuidle_log *)log_item->entry.vaddr;
int cpu = raw_smp_processor_id();
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_CPUIDLE_ID))
return;
idx = (atomic_fetch_inc(&dss_log_misc.cpuidle_log_idx[cpu]) % log_item->log_num) +
(cpu * log_item->log_num);
entry[idx].time = cpu_clock(cpu);
entry[idx].modes = modes;
entry[idx].state = state;
entry[idx].num_online_cpus = num_online_cpus();
entry[idx].delta = (int)diff;
entry[idx].en = en;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_cpuidle);
static void dbg_snapshot_irq(int irq, void *fn, int en)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_IRQ_ID];
struct irq_log *entry = (struct irq_log *)log_item->entry.vaddr;
unsigned long flags = arch_local_irq_save();
int cpu = raw_smp_processor_id();
idx = (atomic_fetch_inc(&dss_log_misc.irq_log_idx[cpu]) % log_item->log_num) +
(cpu * log_item->log_num);
entry[idx].time = local_clock();
entry[idx].irq = irq;
entry[idx].fn = fn;
entry[idx].desc = irq_to_desc(irq);
entry[idx].en = en;
#if IS_ENABLED(CONFIG_SEC_DEBUG)
entry[idx].latency = READ_ONCE(current_thread_info()->preempt_count);
#endif
arch_local_irq_restore(flags);
}
static void dbg_snapshot_irq_entry(void *ignore, int irq,
struct irqaction *action)
{
dbg_snapshot_irq(irq, action->handler, DSS_FLAG_IN);
}
static void dbg_snapshot_irq_exit(void *ignore, int irq,
struct irqaction *action, int ret)
{
dbg_snapshot_irq(irq, action->handler, DSS_FLAG_OUT);
}
static void dbg_snapshot_hrtimer(struct hrtimer *timer, s64 now,
void *fn, int en)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_HRTIMER_ID];
struct hrtimer_log *entry = (struct hrtimer_log *)log_item->entry.vaddr;
int cpu = raw_smp_processor_id();
idx = (atomic_fetch_inc(&dss_log_misc.hrtimer_log_idx[cpu]) % log_item->log_num) +
(cpu * log_item->log_num);
entry[idx].time = local_clock();
entry[idx].now = now;
entry[idx].timer = timer;
entry[idx].fn = fn;
entry[idx].en = en;
}
static void dbg_snapshot_hrtimer_entry(void *ignore, struct hrtimer *timer,
ktime_t *now)
{
dbg_snapshot_hrtimer(timer, *now, timer->function, DSS_FLAG_IN);
}
static void dbg_snapshot_hrtimer_exit(void *ignore, struct hrtimer *timer)
{
dbg_snapshot_hrtimer(timer, 0, timer->function, DSS_FLAG_OUT);
}
__maybe_unused static void dbg_snapshot_reg(unsigned char io_type,
unsigned int data_type,
unsigned long long val, void *addr,
unsigned long fn)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_REG_ID];
struct reg_log *entry = (struct reg_log *)log_item->entry.vaddr;
int cpu = raw_smp_processor_id();
idx = (atomic_fetch_inc(&dss_log_misc.reg_log_idx[cpu]) % log_item->log_num) +
(cpu * log_item->log_num);
entry[idx].time = local_clock();
entry[idx].io_type = io_type;
entry[idx].data_type = data_type;
entry[idx].val = val;
entry[idx].addr = addr;
entry[idx].caller = (void *)fn;
}
__maybe_unused static void dbg_snapshot_reg_write(void *ignore,
unsigned long fn,
u64 val, u8 width,
volatile void __iomem *addr)
{
dbg_snapshot_reg('w', width, val, (void *)addr, fn);
}
__maybe_unused static void dbg_snapshot_reg_read(void *ignore,
unsigned long fn, u8 width,
const volatile void __iomem *addr)
{
dbg_snapshot_reg('r', width, 0, (void *)addr, fn);
}
__maybe_unused static void dbg_snapshot_reg_post_read(void *ignore,
unsigned long fn, u64 val, u8 width,
const volatile void __iomem *addr)
{
dbg_snapshot_reg('R', width, val, (void *)addr, fn);
}
static void dbg_snapshot_suspend(const char *log, struct device *dev,
int event, int en)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_SUSPEND_ID];
struct suspend_log *entry = (struct suspend_log *)log_item->entry.vaddr;
idx = atomic_fetch_inc(&dss_log_misc.suspend_log_idx) % log_item->log_num;
entry[idx].time = local_clock();
entry[idx].log = log ? log : NULL;
entry[idx].event = event;
entry[idx].dev = dev ? dev_name(dev) : "";
entry[idx].core = raw_smp_processor_id();
entry[idx].en = en;
}
static void dbg_snapshot_suspend_resume(void *ignore, const char *action,
int event, bool start)
{
dbg_snapshot_suspend(action, NULL, event,
start ? DSS_FLAG_IN : DSS_FLAG_OUT);
}
static void dbg_snapshot_dev_pm_cb_start(void *ignore, struct device *dev,
const char *info, int event)
{
dbg_snapshot_suspend(info, dev, event, DSS_FLAG_IN);
}
static void dbg_snapshot_dev_pm_cb_end(void *ignore, struct device *dev,
int error)
{
dbg_snapshot_suspend(NULL, dev, error, DSS_FLAG_OUT);
}
void dbg_snapshot_regulator(unsigned long long timestamp, char* f_name,
unsigned int addr, unsigned int volt,
unsigned int rvolt, int en)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_REGULATOR_ID];
struct regulator_log *entry = (struct regulator_log *)log_item->entry.vaddr;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_REGULATOR_ID))
return;
idx = atomic_fetch_inc(&dss_log_misc.regulator_log_idx) % log_item->log_num;
entry[idx].time = local_clock();
entry[idx].cpu = raw_smp_processor_id();
entry[idx].acpm_time = timestamp;
strncpy(entry[idx].name, f_name, min_t(size_t, strlen(f_name), SZ_16 - 1));
entry[idx].reg = addr;
entry[idx].en = en;
entry[idx].voltage = volt;
entry[idx].raw_volt = rvolt;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_regulator);
void dbg_snapshot_thermal(void *data, unsigned int temp, char *name,
unsigned long long max_cooling)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_THERMAL_ID];
struct thermal_log *entry = (struct thermal_log *)log_item->entry.vaddr;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_THERMAL_ID))
return;
idx = atomic_fetch_inc(&dss_log_misc.thermal_log_idx) % log_item->log_num;
entry[idx].time = local_clock();
entry[idx].cpu = raw_smp_processor_id();
entry[idx].data = (struct exynos_tmu_data *)data;
entry[idx].temp = temp;
entry[idx].cooling_device = name;
entry[idx].cooling_state = max_cooling;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_thermal);
void dbg_snapshot_clk(void *clock, const char *func_name, unsigned long arg, int mode)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_CLK_ID];
struct clk_log *entry = (struct clk_log *)log_item->entry.vaddr;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_CLK_ID))
return;
idx = atomic_fetch_inc(&dss_log_misc.clk_log_idx) % log_item->log_num;
entry[idx].time = local_clock();
entry[idx].mode = mode;
entry[idx].arg = arg;
entry[idx].clk = (struct clk_hw *)clock;
entry[idx].f_name = func_name;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_clk);
void dbg_snapshot_pmu(int id, const char *func_name, int mode)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_PMU_ID];
struct pmu_log *entry = (struct pmu_log *)log_item->entry.vaddr;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_PMU_ID))
return;
idx = atomic_fetch_inc(&dss_log_misc.pmu_log_idx) % log_item->log_num;
entry[idx].time = local_clock();
entry[idx].mode = mode;
entry[idx].id = id;
entry[idx].f_name = func_name;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_pmu);
void dbg_snapshot_freq(int type, unsigned int old_freq,
unsigned int target_freq, int en)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_FREQ_ID];
struct freq_log *entry = (struct freq_log *)log_item->entry.vaddr;
if (unlikely(type < 0 || type > dss_freq_size))
return;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_FREQ_ID))
return;
idx = (atomic_fetch_inc(&dss_log_misc.freq_log_idx[type]) % log_item->log_num) +
(type * log_item->log_num);
entry[idx].time = local_clock();
entry[idx].cpu = raw_smp_processor_id();
entry[idx].old_freq = old_freq;
entry[idx].target_freq = target_freq;
entry[idx].en = en;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_freq);
void dbg_snapshot_dm(int type, unsigned int min, unsigned int max,
s32 wait_t, s32 t)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_DM_ID];
struct dm_log *entry = (struct dm_log *)log_item->entry.vaddr;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_DM_ID))
return;
idx = atomic_fetch_inc(&dss_log_misc.dm_log_idx) % log_item->log_num;
entry[idx].time = local_clock();
entry[idx].cpu = raw_smp_processor_id();
entry[idx].dm_num = type;
entry[idx].min_freq = min;
entry[idx].max_freq = max;
entry[idx].wait_dmt = wait_t;
entry[idx].do_dmt = t;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_dm);
void dbg_snapshot_acpm(unsigned long long timestamp, const char *log,
unsigned int data)
{
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_ACPM_ID];
struct acpm_log *entry = (struct acpm_log *)log_item->entry.vaddr;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_ACPM_ID))
return;
idx = atomic_fetch_inc(&dss_log_misc.acpm_log_idx) % log_item->log_num;
entry[idx].time = local_clock();
entry[idx].acpm_time = timestamp;
strncpy(entry[idx].log, log, min_t(size_t, strlen(log), 8));
entry[idx].data = data;
}
EXPORT_SYMBOL_GPL(dbg_snapshot_acpm);
void dbg_snapshot_printk(const char *fmt, ...)
{
va_list args;
unsigned long idx;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_PRINTK_ID];
struct print_log *entry = (struct print_log *)log_item->entry.vaddr;
if (!dbg_snapshot_is_log_item_enabled(DSS_LOG_PRINTK_ID))
return;
idx = atomic_fetch_inc(&dss_log_misc.print_log_idx) % log_item->log_num;
va_start(args, fmt);
vsnprintf(entry[idx].log, sizeof(entry[idx].log), fmt, args);
va_end(args);
entry[idx].time = local_clock();
entry[idx].cpu = raw_smp_processor_id();
}
EXPORT_SYMBOL_GPL(dbg_snapshot_printk);
static inline void dbg_snapshot_get_sec(unsigned long long ts,
unsigned long *sec,
unsigned long *msec)
{
*sec = ts / NSEC_PER_SEC;
*msec = (ts % NSEC_PER_SEC) / USEC_PER_MSEC;
}
static void dbg_snapshot_print_last_irq(int cpu)
{
unsigned long idx, sec, msec;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_IRQ_ID];
struct irq_log *entry = (struct irq_log *)log_item->entry.vaddr;
if (!log_item->entry.enabled)
return;
idx = ((atomic_read(&dss_log_misc.irq_log_idx[cpu]) - 1) % log_item->log_num) +
(cpu * log_item->log_num);
dbg_snapshot_get_sec(entry[idx].time, &sec, &msec);
pr_info("%-12s: [%4ld] %10lu.%06lu sec, %10s: %pS, %8s: %8d, %10s: %2d, %s\n",
">>> irq", idx % log_item->log_num, sec, msec,
"handler", entry[idx].fn,
"irq", entry[idx].irq,
"en", entry[idx].en,
(entry[idx].en == 1) ? "[Mismatch]" : "");
}
static void dbg_snapshot_print_last_task(int cpu)
{
unsigned long idx, sec, msec;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_TASK_ID];
struct task_log *entry = (struct task_log *)log_item->entry.vaddr;
struct task_struct *task;
if (!log_item->entry.enabled)
return;
idx = ((atomic_read(&dss_log_misc.task_log_idx[cpu]) - 1) % log_item->log_num) +
(cpu * log_item->log_num);
dbg_snapshot_get_sec(entry[idx].time, &sec, &msec);
task = entry[idx].task;
pr_info("%-12s: [%4lu] %10lu.%06lu sec, %10s: %-16s, %8s: 0x%-16px, %10s: %16llu\n",
">>> task", idx % log_item->log_num, sec, msec,
"task_comm", (task) ? task->comm : "NULL",
"task", task,
"exec_start", (task) ? task->se.exec_start : 0);
}
static void dbg_snapshot_print_last_work(int cpu)
{
unsigned long idx, sec, msec;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_WORK_ID];
struct work_log *entry = (struct work_log *)log_item->entry.vaddr;
if (!log_item->entry.enabled)
return;
idx = ((atomic_read(&dss_log_misc.work_log_idx[cpu]) - 1) % log_item->log_num) +
(cpu * log_item->log_num);
dbg_snapshot_get_sec(entry[idx].time, &sec, &msec);
pr_info("%-12s: [%4lu] %10lu.%06lu sec, %10s: %pS, %3s: %3d %s\n",
">>> work", idx % log_item->log_num, sec, msec,
"work_fn", entry[idx].fn,
"en", entry[idx].en,
(entry[idx].en == 1) ? "[Mismatch]" : "");
}
static void dbg_snapshot_print_last_cpuidle(int cpu)
{
unsigned long idx, sec, msec;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_CPUIDLE_ID];
struct cpuidle_log *entry = (struct cpuidle_log *)log_item->entry.vaddr;
if (!log_item->entry.enabled)
return;
idx = ((atomic_read(&dss_log_misc.cpuidle_log_idx[cpu]) - 1) % log_item->log_num) +
(cpu * log_item->log_num);
dbg_snapshot_get_sec(entry[idx].time, &sec, &msec);
pr_info("%-12s: [%4lu] %10lu.%06lu sec, %10s: %s, %6s: %2d, %10s: %10d"
", %12s: %2d, %3s: %3d %s\n",
">>> cpuidle", idx % log_item->log_num, sec, msec,
"modes", entry[idx].modes,
"state", entry[idx].state,
"stay time", entry[idx].delta,
"online_cpus", entry[idx].num_online_cpus,
"en", entry[idx].en,
(entry[idx].en == 1) ? "[Mismatch]" : "");
}
static void dbg_snapshot_print_lastinfo(void)
{
int cpu;
pr_info("<last info>\n");
for (cpu = 0; cpu < DSS_NR_CPUS; cpu++) {
pr_info("CPU ID: %d ----------------------------------\n", cpu);
dbg_snapshot_print_last_task(cpu);
dbg_snapshot_print_last_work(cpu);
dbg_snapshot_print_last_irq(cpu);
dbg_snapshot_print_last_cpuidle(cpu);
}
}
static void dbg_snapshot_print_freqinfo(void)
{
unsigned int i;
struct dbg_snapshot_log_item *log_item = &dss_log_items[DSS_LOG_FREQ_ID];
struct freq_log *entry = (struct freq_log *)log_item->entry.vaddr;
if (!log_item->entry.enabled)
return;
pr_info("\n<last freq info>\n");
for (i = 0; i < dss_freq_size; i++) {
unsigned long idx, sec, msec;
unsigned long old_freq, target_freq;
if (!atomic_read(&dss_log_misc.freq_log_idx[i])) {
pr_info("%10s: no infomation\n", dss_freq_name[i]);
continue;
}
idx = ((atomic_read(&dss_log_misc.freq_log_idx[i]) - 1) % log_item->log_num) +
(i * log_item->log_num);
dbg_snapshot_get_sec(entry[idx].time, &sec, &msec);
old_freq = entry[idx].old_freq;
target_freq = entry[idx].target_freq;
pr_info("%10s[%4lu]: %10lu.%06lu sec, %12s: %5luMhz, %12s: %5luMhz, %3s: %d %s\n",
dss_freq_name[i], idx % log_item->log_num, sec, msec,
"old_freq", old_freq / 1000,
"target_freq", target_freq / 1000,
"en", entry[idx].en,
(entry[idx].en == 1) ? "[Mismatch]" : "");
}
}
#ifndef arch_irq_stat
#define arch_irq_stat() 0
#endif
static void dbg_snapshot_print_irq(void)
{
int i, cpu;
u64 sum = 0;
for_each_possible_cpu(cpu)
sum += kstat_cpu_irqs_sum(cpu);
sum += arch_irq_stat();
pr_info("<irq info>\n");
pr_info("----------------------------------------------------------\n");
pr_info("sum irq : %llu", sum);
pr_info("----------------------------------------------------------\n");
for_each_irq_nr(i) {
struct irq_desc *desc = irq_to_desc(i);
unsigned int irq_stat = 0;
const char *name;
if (!desc)
continue;
for_each_possible_cpu(cpu)
irq_stat += *per_cpu_ptr(desc->kstat_irqs, cpu);
if (!irq_stat)
continue;
if (desc->action && desc->action->name)
name = desc->action->name;
else
name = "???";
pr_info("irq-%-4d(hwirq-%-3d) : %8u %s\n",
i, (int)desc->irq_data.hwirq, irq_stat, name);
}
}
void dbg_snapshot_print_log_report(void)
{
if (unlikely(!dbg_snapshot_get_enable()))
return;
pr_info("==========================================================\n");
pr_info("Panic Report\n");
pr_info("==========================================================\n");
dbg_snapshot_print_lastinfo();
dbg_snapshot_print_freqinfo();
dbg_snapshot_print_irq();
pr_info("==========================================================\n");
}
#define _log_item_set_field(log_item, item, dss_log_ptr, log_name) \
log_item->entry.vaddr = (size_t)(dss_log_ptr->log_name); \
log_item->entry.paddr = item->entry.paddr + \
(size_t)(dss_log_ptr->log_name) - (size_t)(dss_log_ptr); \
log_item->entry.size = sizeof(dss_log_ptr->log_name); \
\
if (!log_item->entry.paddr || !log_item->entry.vaddr || \
!log_item->entry.size || !log_item->policy) \
log_item->entry.enabled = false; \
else \
log_item->entry.enabled = true
#define log_item_set_array_field(dss_log_type, id, log_name) \
{ \
struct dbg_snapshot_log_item *log_item; \
struct dbg_snapshot_item *item = dbg_snapshot_get_item(DSS_ITEM_KEVENTS); \
dss_log_type *dss_log_ptr = (dss_log_type *)item->entry.vaddr; \
\
log_item = &dss_log_items[DSS_LOG_##id##_ID]; \
_log_item_set_field(log_item, item, dss_log_ptr, log_name); \
log_item->arr_num = (int)ARRAY_SIZE(dss_log_ptr->log_name); \
log_item->log_num = (int)ARRAY_SIZE(dss_log_ptr->log_name[0]); \
}
#define log_item_set_field(dss_log_type, id, log_name) \
{ \
struct dbg_snapshot_log_item *log_item; \
struct dbg_snapshot_item *item = dbg_snapshot_get_item(DSS_ITEM_KEVENTS); \
dss_log_type *dss_log_ptr = (dss_log_type *)item->entry.vaddr; \
\
log_item = &dss_log_items[DSS_LOG_##id##_ID]; \
_log_item_set_field(log_item, item, dss_log_ptr, log_name); \
log_item->log_num = (int)ARRAY_SIZE(dss_log_ptr->log_name); \
}
static void dbg_snapshot_set_log_item_field(void)
{
switch (dss_kevents.type) {
case eDSS_LOG_TYPE_DEFAULT:
log_item_set_array_field(struct dbg_snapshot_log, TASK, task);
log_item_set_array_field(struct dbg_snapshot_log, WORK, work);
log_item_set_array_field(struct dbg_snapshot_log, CPUIDLE, cpuidle);
log_item_set_field(struct dbg_snapshot_log, SUSPEND, suspend);
log_item_set_array_field(struct dbg_snapshot_log, IRQ, irq);
log_item_set_field(struct dbg_snapshot_log, CLK, clk);
log_item_set_field(struct dbg_snapshot_log, PMU, pmu);
log_item_set_array_field(struct dbg_snapshot_log, FREQ, freq);
log_item_set_field(struct dbg_snapshot_log, DM, dm);
log_item_set_array_field(struct dbg_snapshot_log, HRTIMER, hrtimer);
log_item_set_array_field(struct dbg_snapshot_log, REG, reg);
log_item_set_field(struct dbg_snapshot_log, REGULATOR, regulator);
log_item_set_field(struct dbg_snapshot_log, THERMAL, thermal);
log_item_set_field(struct dbg_snapshot_log, ACPM, acpm);
log_item_set_field(struct dbg_snapshot_log, PRINTK, print);
break;
case eDSS_LOG_TYPE_MINIMIZED:
log_item_set_array_field(struct dbg_snapshot_log_minimized, TASK, task);
log_item_set_array_field(struct dbg_snapshot_log_minimized, WORK, work);
log_item_set_array_field(struct dbg_snapshot_log_minimized, CPUIDLE, cpuidle);
log_item_set_array_field(struct dbg_snapshot_log_minimized, IRQ, irq);
log_item_set_array_field(struct dbg_snapshot_log_minimized, FREQ, freq);
break;
default:
break;
}
}
static bool is_valid_dss_log_type(enum dbg_snapshot_log_type type)
{
if (type == eDSS_LOG_TYPE_DEFAULT || type == eDSS_LOG_TYPE_MINIMIZED)
return true;
return false;
}
void dbg_snapshot_init_log(void)
{
struct device_node *np = dss_desc.dev->of_node;
struct property *prop;
const char *str;
unsigned int i = 0;
if (!dbg_snapshot_get_item_enable(DSS_ITEM_KEVENTS)) {
dev_err(dss_desc.dev, "DSS logging is disabled.\n");
return;
}
if (!is_valid_dss_log_type(dss_kevents.type)) {
dev_err(dss_desc.dev, "DSS logging type is invalid(%d)\n", dss_kevents.type);
return;
}
dbg_snapshot_set_log_item_field();
if (dbg_snapshot_get_enable_log_item(DSS_LOG_TASK))
register_trace_sched_switch(dbg_snapshot_sched_switch, NULL);
if (dbg_snapshot_get_enable_log_item(DSS_LOG_WORK)) {
register_trace_workqueue_execute_start(dbg_snapshot_wq_start, NULL);
register_trace_workqueue_execute_end(dbg_snapshot_wq_end, NULL);
}
if (dbg_snapshot_get_enable_log_item(DSS_LOG_IRQ)) {
register_trace_irq_handler_entry(dbg_snapshot_irq_entry, NULL);
register_trace_irq_handler_exit(dbg_snapshot_irq_exit, NULL);
}
if (dbg_snapshot_get_enable_log_item(DSS_LOG_HRTIMER)) {
register_trace_hrtimer_expire_entry(dbg_snapshot_hrtimer_entry, NULL);
register_trace_hrtimer_expire_exit(dbg_snapshot_hrtimer_exit, NULL);
}
if (dbg_snapshot_get_enable_log_item(DSS_LOG_SUSPEND)) {
register_trace_suspend_resume(dbg_snapshot_suspend_resume, NULL);
register_trace_device_pm_callback_start(dbg_snapshot_dev_pm_cb_start, NULL);
register_trace_device_pm_callback_end(dbg_snapshot_dev_pm_cb_end, NULL);
}
if (dbg_snapshot_get_enable_log_item(DSS_LOG_REG)) {
register_rwmmio_write(dbg_snapshot_reg_write, NULL);
register_rwmmio_read(dbg_snapshot_reg_read, NULL);
register_rwmmio_post_read(dbg_snapshot_reg_post_read, NULL);
}
dss_freq_size = of_property_count_strings(np, "freq_names");
of_property_for_each_string(np, "freq_names", prop, str) {
strncpy(dss_freq_name[i++], str, strlen(str));
}
dbg_snapshot_log_output();
}