/* * Copyright (c) 2013 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Debug-SnapShot: Debug Framework for Ramdump based debugging method * The original code is Exynos-Snapshot for Exynos SoC * * Author: Hosung Kim * Author: Changki Kim * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include "debug-snapshot-local.h" #include #include #include #include #include #include #include #include #include #include #include #include struct dbg_snapshot_lastinfo { #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ atomic_t freq_last_idx[DSS_FLAG_END]; #endif char log[DSS_NR_CPUS][SZ_1K]; char *last_p[DSS_NR_CPUS]; }; struct dss_dumper { bool active; u32 items; int init_idx; int cur_idx; u32 cur_cpu; u32 step; }; enum dss_kevent_flag { DSS_FLAG_TASK = 1, DSS_FLAG_WORK, DSS_FLAG_CPUIDLE, DSS_FLAG_SUSPEND, DSS_FLAG_IRQ, DSS_FLAG_IRQ_EXIT, DSS_FLAG_SPINLOCK, DSS_FLAG_IRQ_DISABLE, DSS_FLAG_CLK, DSS_FLAG_FREQ, DSS_FLAG_REG, DSS_FLAG_HRTIMER, DSS_FLAG_REGULATOR, DSS_FLAG_THERMAL, DSS_FLAG_MAILBOX, DSS_FLAG_CLOCKEVENT, DSS_FLAG_PRINTK, DSS_FLAG_PRINTKL, DSS_FLAG_KEVENT, }; struct dbg_snapshot_log_idx { atomic_t task_log_idx[DSS_NR_CPUS]; atomic_t work_log_idx[DSS_NR_CPUS]; atomic_t cpuidle_log_idx[DSS_NR_CPUS]; atomic_t suspend_log_idx; atomic_t irq_log_idx[DSS_NR_CPUS]; #ifdef CONFIG_DEBUG_SNAPSHOT_SPINLOCK atomic_t spinlock_log_idx[DSS_NR_CPUS]; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_DISABLED atomic_t irqs_disabled_log_idx[DSS_NR_CPUS]; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_REG atomic_t reg_log_idx[DSS_NR_CPUS]; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_HRTIMER atomic_t hrtimer_log_idx[DSS_NR_CPUS]; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_CLK atomic_t clk_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_PMU atomic_t pmu_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ atomic_t freq_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_DM atomic_t dm_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR atomic_t regulator_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR atomic_t thermal_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_I2C atomic_t i2c_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_SPI atomic_t spi_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_BINDER atomic_t binder_log_idx; #endif #ifndef CONFIG_DEBUG_SNAPSHOT_MINIMIZED_MODE atomic_t printkl_log_idx; atomic_t printk_log_idx; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_ACPM atomic_t acpm_log_idx; #endif }; int dbg_snapshot_log_size = sizeof(struct dbg_snapshot_log); /* * including or excluding options * if you want to except some interrupt, it should be written in this array */ int dss_irqlog_exlist[DSS_EX_MAX_NUM] = { /* interrupt number ex) 152, 153, 154, */ -1, }; #ifdef CONFIG_DEBUG_SNAPSHOT_REG struct dss_reg_list { size_t addr; size_t size; }; static struct dss_reg_list dss_reg_exlist[] = { /* * if it wants to reduce effect enabled reg feautre to system, * you must add these registers - mct, serial * because they are called very often. * physical address, size ex) {0x10C00000, 0x1000}, */ {DSS_REG_MCT_ADDR, DSS_REG_MCT_SIZE}, {DSS_REG_UART_ADDR, DSS_REG_UART_SIZE}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, }; #endif #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ static char *dss_freq_name[] = { "LITTLE", "BIG", "INT", "MIF", "ISP", "DISP", "INTCAM", "AUD", "IVA", "SCORE", "FSYS0", }; #endif /* Internal interface variable */ static struct dbg_snapshot_log_idx dss_idx; static struct dbg_snapshot_lastinfo dss_lastinfo; void __init dbg_snapshot_init_log_idx(void) { int i; #ifndef CONFIG_DEBUG_SNAPSHOT_MINIMIZED_MODE atomic_set(&(dss_idx.printk_log_idx), -1); atomic_set(&(dss_idx.printkl_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR atomic_set(&(dss_idx.regulator_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_THERMAL atomic_set(&(dss_idx.thermal_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ atomic_set(&(dss_idx.freq_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_DM atomic_set(&(dss_idx.dm_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_CLK atomic_set(&(dss_idx.clk_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_PMU atomic_set(&(dss_idx.pmu_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_ACPM atomic_set(&(dss_idx.acpm_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_I2C atomic_set(&(dss_idx.i2c_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_SPI atomic_set(&(dss_idx.spi_log_idx), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_BINDER atomic_set(&(dss_idx.binder_log_idx), -1); #endif atomic_set(&(dss_idx.suspend_log_idx), -1); for (i = 0; i < DSS_NR_CPUS; i++) { atomic_set(&(dss_idx.task_log_idx[i]), -1); atomic_set(&(dss_idx.work_log_idx[i]), -1); atomic_set(&(dss_idx.cpuidle_log_idx[i]), -1); atomic_set(&(dss_idx.irq_log_idx[i]), -1); #ifdef CONFIG_DEBUG_SNAPSHOT_SPINLOCK atomic_set(&(dss_idx.spinlock_log_idx[i]), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_DISABLED atomic_set(&(dss_idx.irqs_disabled_log_idx[i]), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_REG atomic_set(&(dss_idx.reg_log_idx[i]), -1); #endif #ifdef CONFIG_DEBUG_SNAPSHOT_HRTIMER atomic_set(&(dss_idx.hrtimer_log_idx[i]), -1); #endif } } unsigned long sec_debug_get_kevent_index_addr(int type) { switch (type) { case DSS_KEVENT_TASK: return virt_to_phys(&(dss_idx.task_log_idx[0])); case DSS_KEVENT_WORK: return virt_to_phys(&(dss_idx.work_log_idx[0])); case DSS_KEVENT_IRQ: return virt_to_phys(&(dss_idx.irq_log_idx[0])); case DSS_KEVENT_FREQ: return virt_to_phys(&(dss_idx.freq_log_idx)); case DSS_KEVENT_IDLE: return virt_to_phys(&(dss_idx.cpuidle_log_idx[0])); case DSS_KEVENT_THRM: return virt_to_phys(&(dss_idx.thermal_log_idx)); case DSS_KEVENT_ACPM: return virt_to_phys(&(dss_idx.acpm_log_idx)); default: return 0; } } bool dbg_snapshot_dumper_one(void *v_dumper, char *line, size_t size, size_t *len) { bool ret = false; int idx, array_size; unsigned int cpu, items; unsigned long rem_nsec; u64 ts; struct dss_dumper *dumper = (struct dss_dumper *)v_dumper; if (!line || size < SZ_128 || dumper->cur_cpu >= NR_CPUS) goto out; if (dumper->active) { if (dumper->init_idx == dumper->cur_idx) goto out; } cpu = dumper->cur_cpu; idx = dumper->cur_idx; items = dumper->items; switch(items) { case DSS_FLAG_TASK: { struct task_struct *task; array_size = ARRAY_SIZE(dss_log->task[0]) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.task_log_idx[0]) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->task[cpu][idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); task = dss_log->task[cpu][idx].task; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] task_name:%16s, " "task:0x%16p, stack:0x%16p, exec_start:%16llu\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, task->comm, task, task->stack, task->se.exec_start); break; } case DSS_FLAG_WORK: { char work_fn[KSYM_NAME_LEN] = {0,}; char *task_comm; int en; array_size = ARRAY_SIZE(dss_log->work[0]) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.work_log_idx[0]) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->work[cpu][idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); lookup_symbol_name((unsigned long)dss_log->work[cpu][idx].fn, work_fn); task_comm = dss_log->work[cpu][idx].task_comm; en = dss_log->work[cpu][idx].en; dumper->step = 6; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] task_name:%16s, work_fn:%32s, %3s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, task_comm, work_fn, en == DSS_FLAG_IN ? "IN" : "OUT"); break; } case DSS_FLAG_CPUIDLE: { unsigned int delta; int state, num_cpus, en; char *index; array_size = ARRAY_SIZE(dss_log->cpuidle[0]) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.cpuidle_log_idx[0]) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->cpuidle[cpu][idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); index = dss_log->cpuidle[cpu][idx].modes; en = dss_log->cpuidle[cpu][idx].en; state = dss_log->cpuidle[cpu][idx].state; num_cpus = dss_log->cpuidle[cpu][idx].num_online_cpus; delta = dss_log->cpuidle[cpu][idx].delta; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] cpuidle: %s, " "state:%d, num_online_cpus:%d, stay_time:%8u, %3s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, index, state, num_cpus, delta, en == DSS_FLAG_IN ? "IN" : "OUT"); break; } case DSS_FLAG_SUSPEND: { char suspend_fn[KSYM_NAME_LEN]; int en; array_size = ARRAY_SIZE(dss_log->suspend) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.suspend_log_idx) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->suspend[idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); lookup_symbol_name((unsigned long)dss_log->suspend[idx].fn, suspend_fn); en = dss_log->suspend[idx].en; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] suspend_fn:%s, %3s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, suspend_fn, en == DSS_FLAG_IN ? "IN" : "OUT"); break; } case DSS_FLAG_IRQ: { char irq_fn[KSYM_NAME_LEN]; int en, irq; array_size = ARRAY_SIZE(dss_log->irq[0]) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.irq_log_idx[0]) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->irq[cpu][idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); lookup_symbol_name((unsigned long)dss_log->irq[cpu][idx].fn, irq_fn); irq = dss_log->irq[cpu][idx].irq; en = dss_log->irq[cpu][idx].en; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] irq:%6d, irq_fn:%32s, %3s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, irq, irq_fn, en == DSS_FLAG_IN ? "IN" : "OUT"); break; } #ifdef CONFIG_DEBUG_SNAPSHOT_SPINLOCK case DSS_FLAG_SPINLOCK: { unsigned int jiffies_local; char callstack[CONFIG_DEBUG_SNAPSHOT_CALLSTACK][KSYM_NAME_LEN]; int en, i; u16 next, owner; array_size = ARRAY_SIZE(dss_log->spinlock[0]) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.spinlock_log_idx[0]) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->spinlock[cpu][idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); jiffies_local = dss_log->spinlock[cpu][idx].jiffies; en = dss_log->spinlock[cpu][idx].en; for (i = 0; i < CONFIG_DEBUG_SNAPSHOT_CALLSTACK; i++) lookup_symbol_name((unsigned long)dss_log->spinlock[cpu][idx].caller[i], callstack[i]); next = dss_log->spinlock[cpu][idx].next; owner = dss_log->spinlock[cpu][idx].owner; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] next:%8x, owner:%8x jiffies:%12u, %3s\n" "callstack: %s\n" " %s\n" " %s\n" " %s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, next, owner, jiffies_local, en == DSS_FLAG_IN ? "IN" : "OUT", callstack[0], callstack[1], callstack[2], callstack[3]); break; } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_CLK case DSS_FLAG_CLK: { const char *clk_name; char clk_fn[KSYM_NAME_LEN]; struct clk_hw *clk; int en; array_size = ARRAY_SIZE(dss_log->clk) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.clk_log_idx) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->clk[idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); clk = (struct clk_hw *)dss_log->clk[idx].clk; clk_name = clk_hw_get_name(clk); lookup_symbol_name((unsigned long)dss_log->clk[idx].f_name, clk_fn); en = dss_log->clk[idx].mode; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU] clk_name:%30s, clk_fn:%30s, " ", %s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, clk_name, clk_fn, en == DSS_FLAG_IN ? "IN" : "OUT"); break; } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ case DSS_FLAG_FREQ: { char *freq_name; unsigned int on_cpu; unsigned long old_freq, target_freq; int en; array_size = ARRAY_SIZE(dss_log->freq) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.freq_log_idx) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->freq[idx].time; rem_nsec = do_div(ts, NSEC_PER_SEC); freq_name = dss_log->freq[idx].freq_name; old_freq = dss_log->freq[idx].old_freq; target_freq = dss_log->freq[idx].target_freq; on_cpu = dss_log->freq[idx].cpu; en = dss_log->freq[idx].en; *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] freq_name:%16s, " "old_freq:%16lu, target_freq:%16lu, %3s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, on_cpu, freq_name, old_freq, target_freq, en == DSS_FLAG_IN ? "IN" : "OUT"); break; } #endif #ifndef CONFIG_DEBUG_SNAPSHOT_MINIMIZED_MODE case DSS_FLAG_PRINTK: { char *log; char callstack[CONFIG_DEBUG_SNAPSHOT_CALLSTACK][KSYM_NAME_LEN]; unsigned int cpu; int i; array_size = ARRAY_SIZE(dss_log->printk) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.printk_log_idx) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->printk[idx].time; cpu = dss_log->printk[idx].cpu; rem_nsec = do_div(ts, NSEC_PER_SEC); log = dss_log->printk[idx].log; for (i = 0; i < CONFIG_DEBUG_SNAPSHOT_CALLSTACK; i++) lookup_symbol_name((unsigned long)dss_log->printk[idx].caller[i], callstack[i]); *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] log:%s, callstack:%s, %s, %s, %s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, log, callstack[0], callstack[1], callstack[2], callstack[3]); break; } case DSS_FLAG_PRINTKL: { char callstack[CONFIG_DEBUG_SNAPSHOT_CALLSTACK][KSYM_NAME_LEN]; size_t msg, val; unsigned int cpu; int i; array_size = ARRAY_SIZE(dss_log->printkl) - 1; if (!dumper->active) { idx = (atomic_read(&dss_idx.printkl_log_idx) + 1) & array_size; dumper->init_idx = idx; dumper->active = true; } ts = dss_log->printkl[idx].time; cpu = dss_log->printkl[idx].cpu; rem_nsec = do_div(ts, NSEC_PER_SEC); msg = dss_log->printkl[idx].msg; val = dss_log->printkl[idx].val; for (i = 0; i < CONFIG_DEBUG_SNAPSHOT_CALLSTACK; i++) lookup_symbol_name((unsigned long)dss_log->printkl[idx].caller[i], callstack[i]); *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] msg:%zx, val:%zx, callstack: %s, %s, %s, %s\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, msg, val, callstack[0], callstack[1], callstack[2], callstack[3]); break; } #endif default: snprintf(line, size, "unsupported inforation to dump\n"); goto out; } if (array_size == idx) dumper->cur_idx = 0; else dumper->cur_idx = idx + 1; ret = true; out: return ret; } #ifdef CONFIG_ARM64 static inline unsigned long pure_arch_local_irq_save(void) { unsigned long flags; asm volatile( "mrs %0, daif // arch_local_irq_save\n" "msr daifset, #2" : "=r" (flags) : : "memory"); return flags; } static inline void pure_arch_local_irq_restore(unsigned long flags) { asm volatile( "msr daif, %0 // arch_local_irq_restore" : : "r" (flags) : "memory"); } #else static inline unsigned long arch_local_irq_save(void) { unsigned long flags; asm volatile( " mrs %0, cpsr @ arch_local_irq_save\n" " cpsid i" : "=r" (flags) : : "memory", "cc"); return flags; } static inline void arch_local_irq_restore(unsigned long flags) { asm volatile( " msr cpsr_c, %0 @ local_irq_restore" : : "r" (flags) : "memory", "cc"); } #endif void dbg_snapshot_task(int cpu, void *v_task) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { unsigned long i = atomic_inc_return(&dss_idx.task_log_idx[cpu]) & (ARRAY_SIZE(dss_log->task[0]) - 1); dss_log->task[cpu][i].time = cpu_clock(cpu); dss_log->task[cpu][i].sp = (unsigned long)current_stack_pointer; dss_log->task[cpu][i].task = (struct task_struct *)v_task; dss_log->task[cpu][i].pid = (int)((struct task_struct *)v_task)->pid; strncpy(dss_log->task[cpu][i].task_comm, dss_log->task[cpu][i].task->comm, TASK_COMM_LEN - 1); } } void dbg_snapshot_work(void *worker, void *v_task, void *fn, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.work_log_idx[cpu]) & (ARRAY_SIZE(dss_log->work[0]) - 1); struct task_struct *task = (struct task_struct *)v_task; dss_log->work[cpu][i].time = cpu_clock(cpu); dss_log->work[cpu][i].sp = (unsigned long) current_stack_pointer; dss_log->work[cpu][i].worker = (struct worker *)worker; strncpy(dss_log->work[cpu][i].task_comm, task->comm, TASK_COMM_LEN - 1); dss_log->work[cpu][i].fn = (work_func_t)fn; dss_log->work[cpu][i].en = en; } } void dbg_snapshot_cpuidle(char *modes, unsigned state, int diff, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.cpuidle_log_idx[cpu]) & (ARRAY_SIZE(dss_log->cpuidle[0]) - 1); dss_log->cpuidle[cpu][i].time = cpu_clock(cpu); dss_log->cpuidle[cpu][i].modes = modes; dss_log->cpuidle[cpu][i].state = state; dss_log->cpuidle[cpu][i].sp = (unsigned long) current_stack_pointer; dss_log->cpuidle[cpu][i].num_online_cpus = num_online_cpus(); dss_log->cpuidle[cpu][i].delta = diff; dss_log->cpuidle[cpu][i].en = en; } } void dbg_snapshot_suspend(char *log, void *fn, void *dev, int state, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int len; int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.suspend_log_idx) & (ARRAY_SIZE(dss_log->suspend) - 1); dss_log->suspend[i].time = cpu_clock(cpu); dss_log->suspend[i].sp = (unsigned long) current_stack_pointer; if (log) { len = strlen(log); memcpy(dss_log->suspend[i].log, log, len < DSS_LOG_GEN_LEN ? len : DSS_LOG_GEN_LEN - 1); } else { memset(dss_log->suspend[i].log, 0, DSS_LOG_GEN_LEN - 1); } dss_log->suspend[i].fn = fn; dss_log->suspend[i].dev = (struct device *)dev; dss_log->suspend[i].core = cpu; dss_log->suspend[i].en = en; } } static void dbg_snapshot_print_calltrace(void) { int i; pr_info("\n\n"); for (i = 0; i < DSS_NR_CPUS; i++) { pr_info("CPU ID: %d -----------------------------------------------\n", i); pr_info("%s", dss_lastinfo.log[i]); } } void dbg_snapshot_save_log(int cpu, unsigned long where) { if (dss_lastinfo.last_p[cpu] == NULL) dss_lastinfo.last_p[cpu] = &dss_lastinfo.log[cpu][0]; if (dss_lastinfo.last_p[cpu] > &dss_lastinfo.log[cpu][SZ_1K - SZ_128]) return; *(unsigned long *)&(dss_lastinfo.last_p[cpu]) += sprintf(dss_lastinfo.last_p[cpu], "[<%p>] %pS\n", (void *)where, (void *)where); } static 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; char fn_name[KSYM_NAME_LEN]; idx = atomic_read(&dss_idx.irq_log_idx[cpu]) & (ARRAY_SIZE(dss_log->irq[0]) - 1); dbg_snapshot_get_sec(dss_log->irq[cpu][idx].time, &sec, &msec); lookup_symbol_name((unsigned long)dss_log->irq[cpu][idx].fn, fn_name); pr_info("%-16s: [%4lu] %10lu.%06lu sec, %10s: %24s, %8s: %8d, %10s: %2d, %s\n", ">>> last irq", idx, sec, msec, "handler", fn_name, "irq", dss_log->irq[cpu][idx].irq, "en", dss_log->irq[cpu][idx].en, (dss_log->irq[cpu][idx].en == 1) ? "[Missmatch]" : ""); } static void dbg_snapshot_print_last_task(int cpu) { unsigned long idx, sec, msec; struct task_struct *task; idx = atomic_read(&dss_idx.task_log_idx[cpu]) & (ARRAY_SIZE(dss_log->task[0]) - 1); dbg_snapshot_get_sec(dss_log->task[cpu][idx].time, &sec, &msec); task = dss_log->task[cpu][idx].task; pr_info("%-16s: [%4lu] %10lu.%06lu sec, %10s: %24s, %8s: 0x%-16p, %10s: %16llu\n", ">>> last task", idx, 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; char fn_name[KSYM_NAME_LEN]; idx = atomic_read(&dss_idx.work_log_idx[cpu]) & (ARRAY_SIZE(dss_log->work[0]) - 1); dbg_snapshot_get_sec(dss_log->work[cpu][idx].time, &sec, &msec); lookup_symbol_name((unsigned long)dss_log->work[cpu][idx].fn, fn_name); pr_info("%-16s: [%4lu] %10lu.%06lu sec, %10s: %24s, %8s: %20s, %3s: %3d %s\n", ">>> last work", idx, sec, msec, "task_name", dss_log->work[cpu][idx].task_comm, "work_fn", fn_name, "en", dss_log->work[cpu][idx].en, (dss_log->work[cpu][idx].en == 1) ? "[Missmatch]" : ""); } static void dbg_snapshot_print_last_cpuidle(int cpu) { unsigned long idx, sec, msec; idx = atomic_read(&dss_idx.cpuidle_log_idx[cpu]) & (ARRAY_SIZE(dss_log->cpuidle[0]) - 1); dbg_snapshot_get_sec(dss_log->cpuidle[cpu][idx].time, &sec, &msec); pr_info("%-16s: [%4lu] %10lu.%06lu sec, %10s: %24d, %8s: %4s, %6s: %3d, %12s: %2d, %3s: %3d %s\n", ">>> last cpuidle", idx, sec, msec, "stay time", dss_log->cpuidle[cpu][idx].delta, "modes", dss_log->cpuidle[cpu][idx].modes, "state", dss_log->cpuidle[cpu][idx].state, "online_cpus", dss_log->cpuidle[cpu][idx].num_online_cpus, "en", dss_log->cpuidle[cpu][idx].en, (dss_log->cpuidle[cpu][idx].en == 1) ? "[Missmatch]" : ""); } static void dbg_snapshot_print_lastinfo(void) { int cpu; pr_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); } } #ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR void dbg_snapshot_regulator(unsigned long long timestamp, char* f_name, unsigned int addr, unsigned int volt, unsigned int rvolt, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.regulator_log_idx) & (ARRAY_SIZE(dss_log->regulator) - 1); int size = strlen(f_name); if (size >= SZ_16) size = SZ_16 - 1; dss_log->regulator[i].time = cpu_clock(cpu); dss_log->regulator[i].cpu = cpu; dss_log->regulator[i].acpm_time = timestamp; strncpy(dss_log->regulator[i].name, f_name, size); dss_log->regulator[i].reg = addr; dss_log->regulator[i].en = en; dss_log->regulator[i].voltage = volt; dss_log->regulator[i].raw_volt = rvolt; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_THERMAL void dbg_snapshot_thermal(void *data, unsigned int temp, char *name, unsigned int max_cooling) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.thermal_log_idx) & (ARRAY_SIZE(dss_log->thermal) - 1); dss_log->thermal[i].time = cpu_clock(cpu); dss_log->thermal[i].cpu = cpu; dss_log->thermal[i].data = (struct exynos_tmu_platform_data *)data; dss_log->thermal[i].temp = temp; dss_log->thermal[i].cooling_device = name; dss_log->thermal[i].cooling_state = max_cooling; } } #endif void dbg_snapshot_irq(int irq, void *fn, void *val, unsigned long long start_time, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; unsigned long flags; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; flags = pure_arch_local_irq_save(); { int cpu = raw_smp_processor_id(); unsigned long long time, latency; unsigned long i; time = cpu_clock(cpu); if (start_time == 0) start_time = time; latency = time - start_time; i = atomic_inc_return(&dss_idx.irq_log_idx[cpu]) & (ARRAY_SIZE(dss_log->irq[0]) - 1); dss_log->irq[cpu][i].time = time; dss_log->irq[cpu][i].sp = (unsigned long) current_stack_pointer; dss_log->irq[cpu][i].irq = irq; dss_log->irq[cpu][i].fn = (void *)fn; dss_log->irq[cpu][i].desc = (struct irq_desc *)val; dss_log->irq[cpu][i].latency = latency; dss_log->irq[cpu][i].en = en; } pure_arch_local_irq_restore(flags); } #ifdef CONFIG_DEBUG_SNAPSHOT_SPINLOCK void dbg_snapshot_spinlock(void *v_lock, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned index = atomic_inc_return(&dss_idx.spinlock_log_idx[cpu]); unsigned long j, i = index & (ARRAY_SIZE(dss_log->spinlock[0]) - 1); raw_spinlock_t *lock = (raw_spinlock_t *)v_lock; #ifdef CONFIG_ARM_ARCH_TIMER dss_log->spinlock[cpu][i].time = cpu_clock(cpu); #else dss_log->spinlock[cpu][i].time = index; #endif dss_log->spinlock[cpu][i].sp = (unsigned long) current_stack_pointer; dss_log->spinlock[cpu][i].jiffies = jiffies_64; #ifdef CONFIG_DEBUG_SPINLOCK dss_log->spinlock[cpu][i].lock = lock; dss_log->spinlock[cpu][i].next = lock->raw_lock.next; dss_log->spinlock[cpu][i].owner = lock->raw_lock.owner; #endif dss_log->spinlock[cpu][i].en = en; for (j = 0; j < dss_desc.callstack; j++) { dss_log->spinlock[cpu][i].caller[j] = (void *)((size_t)return_address(j + 1)); } } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_DISABLED void dbg_snapshot_irqs_disabled(unsigned long flags) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; int cpu = raw_smp_processor_id(); if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; if (unlikely(flags)) { unsigned j, local_flags = pure_arch_local_irq_save(); /* If flags has one, it shows interrupt enable status */ atomic_set(&dss_idx.irqs_disabled_log_idx[cpu], -1); dss_log->irqs_disabled[cpu][0].time = 0; dss_log->irqs_disabled[cpu][0].index = 0; dss_log->irqs_disabled[cpu][0].task = NULL; dss_log->irqs_disabled[cpu][0].task_comm = NULL; for (j = 0; j < dss_desc.callstack; j++) { dss_log->irqs_disabled[cpu][0].caller[j] = NULL; } pure_arch_local_irq_restore(local_flags); } else { unsigned index = atomic_inc_return(&dss_idx.irqs_disabled_log_idx[cpu]); unsigned long j, i = index % ARRAY_SIZE(dss_log->irqs_disabled[0]); dss_log->irqs_disabled[cpu][0].time = jiffies_64; dss_log->irqs_disabled[cpu][i].index = index; dss_log->irqs_disabled[cpu][i].task = get_current(); dss_log->irqs_disabled[cpu][i].task_comm = get_current()->comm; for (j = 0; j < dss_desc.callstack; j++) { dss_log->irqs_disabled[cpu][i].caller[j] = (void *)((size_t)return_address(j + 1)); } } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_CLK void dbg_snapshot_clk(void *clock, const char *func_name, unsigned long arg, int mode) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.clk_log_idx) & (ARRAY_SIZE(dss_log->clk) - 1); dss_log->clk[i].time = cpu_clock(cpu); dss_log->clk[i].mode = mode; dss_log->clk[i].arg = arg; dss_log->clk[i].clk = (struct clk_hw *)clock; dss_log->clk[i].f_name = func_name; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_PMU void dbg_snapshot_pmu(int id, const char *func_name, int mode) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.pmu_log_idx) & (ARRAY_SIZE(dss_log->pmu) - 1); dss_log->pmu[i].time = cpu_clock(cpu); dss_log->pmu[i].mode = mode; dss_log->pmu[i].id = id; dss_log->pmu[i].f_name = func_name; } } #endif static struct notifier_block **dss_should_check_nl[] = { (struct notifier_block **)(&panic_notifier_list.head), (struct notifier_block **)(&reboot_notifier_list.head), (struct notifier_block **)(&restart_handler_list.head), #ifdef CONFIG_PM_SLEEP (struct notifier_block **)(&pm_chain_head.head), #endif #ifdef CONFIG_EXYNOS_ITMON (struct notifier_block **)(&itmon_notifier_list.head), #endif }; void dbg_snapshot_print_notifier_call(void **nl, unsigned long func, int en) { struct notifier_block **nl_org = (struct notifier_block **)nl; char notifier_name[KSYM_NAME_LEN]; char notifier_func_name[KSYM_NAME_LEN]; int i; for (i = 0; i < ARRAY_SIZE(dss_should_check_nl); i++) { if (nl_org == dss_should_check_nl[i]) { lookup_symbol_name((unsigned long)nl_org, notifier_name); lookup_symbol_name((unsigned long)func, notifier_func_name); pr_info("debug-snapshot: %s -> %s call %s\n", notifier_name, notifier_func_name, en == DSS_FLAG_IN ? "+" : "-"); break; } } } #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ static void dbg_snapshot_print_freqinfo(void) { unsigned long idx, sec, msec; char *freq_name; unsigned int i; unsigned long old_freq, target_freq; pr_info("\n\n"); for (i = 0; i < DSS_FLAG_END; i++) { idx = atomic_read(&dss_lastinfo.freq_last_idx[i]) & (ARRAY_SIZE(dss_log->freq) - 1); freq_name = dss_log->freq[idx].freq_name; if ((!freq_name) || strncmp(freq_name, dss_freq_name[i], strlen(dss_freq_name[i]))) { pr_info("%10s: no infomation\n", dss_freq_name[i]); continue; } dbg_snapshot_get_sec(dss_log->freq[idx].time, &sec, &msec); old_freq = dss_log->freq[idx].old_freq; target_freq = dss_log->freq[idx].target_freq; pr_info("%10s: [%4lu] %10lu.%06lu sec, %12s: %6luMhz, %12s: %6luMhz, %3s: %3d %s\n", freq_name, idx, sec, msec, "old_freq", old_freq/1000, "target_freq", target_freq/1000, "en", dss_log->freq[idx].en, (dss_log->freq[idx].en == 1) ? "[Missmatch]" : ""); } } void dbg_snapshot_freq(int type, unsigned long old_freq, unsigned long target_freq, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.freq_log_idx) & (ARRAY_SIZE(dss_log->freq) - 1); if (atomic_read(&dss_idx.freq_log_idx) > atomic_read(&dss_lastinfo.freq_last_idx[type])) atomic_set(&dss_lastinfo.freq_last_idx[type], atomic_read(&dss_idx.freq_log_idx)); dss_log->freq[i].time = cpu_clock(cpu); dss_log->freq[i].cpu = cpu; dss_log->freq[i].freq_name = dss_freq_name[type]; dss_log->freq[i].freq_type = type; dss_log->freq[i].old_freq = old_freq; dss_log->freq[i].target_freq = target_freq; dss_log->freq[i].en = en; } } #endif #ifndef arch_irq_stat #define arch_irq_stat() 0 #endif static void dbg_snapshot_print_irq(void) { int i, j; u64 sum = 0; for_each_possible_cpu(i) { sum += kstat_cpu_irqs_sum(i); sum += arch_irq_stat_cpu(i); } sum += arch_irq_stat(); pr_info("\n\n"); pr_info("------------------------------------------------------------------\n"); pr_info("\n"); pr_info("sum irq : %llu", (unsigned long long)sum); pr_info("------------------------------------------------------------------\n"); for_each_irq_nr(j) { unsigned int irq_stat = kstat_irqs(j); if (irq_stat) { struct irq_desc *desc = irq_to_desc(j); const char *name; name = desc->action ? (desc->action->name ? desc->action->name : "???") : "???"; pr_info("irq-%-4d : %8u %s\n", j, irq_stat, name); } } } void dbg_snapshot_print_panic_report(void) { pr_info("============================================================\n"); pr_info("Panic Report\n"); pr_info("============================================================\n"); dbg_snapshot_print_lastinfo(); #ifdef CONFIG_DEBUG_SNAPSHOT_FREQ dbg_snapshot_print_freqinfo(); #endif dbg_snapshot_print_calltrace(); dbg_snapshot_print_irq(); pr_info("============================================================\n"); } #ifdef CONFIG_DEBUG_SNAPSHOT_DM void dbg_snapshot_dm(int type, unsigned long min, unsigned long max, s32 wait_t, s32 t) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.dm_log_idx) & (ARRAY_SIZE(dss_log->dm) - 1); dss_log->dm[i].time = cpu_clock(cpu); dss_log->dm[i].cpu = cpu; dss_log->dm[i].dm_num = type; dss_log->dm[i].min_freq = min; dss_log->dm[i].max_freq = max; dss_log->dm[i].wait_dmt = wait_t; dss_log->dm[i].do_dmt = t; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_HRTIMER void dbg_snapshot_hrtimer(void *timer, s64 *now, void *fn, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.hrtimer_log_idx[cpu]) & (ARRAY_SIZE(dss_log->hrtimers[0]) - 1); dss_log->hrtimers[cpu][i].time = cpu_clock(cpu); dss_log->hrtimers[cpu][i].now = *now; dss_log->hrtimers[cpu][i].timer = (struct hrtimer *)timer; dss_log->hrtimers[cpu][i].fn = fn; dss_log->hrtimers[cpu][i].en = en; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_I2C void dbg_snapshot_i2c(struct i2c_adapter *adap, struct i2c_msg *msgs, int num, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.i2c_log_idx) & (ARRAY_SIZE(dss_log->i2c) - 1); dss_log->i2c[i].time = cpu_clock(cpu); dss_log->i2c[i].cpu = cpu; dss_log->i2c[i].adap = adap; dss_log->i2c[i].msgs = msgs; dss_log->i2c[i].num = num; dss_log->i2c[i].en = en; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_SPI void dbg_snapshot_spi(struct spi_controller *ctlr, struct spi_message *cur_msg, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.spi_log_idx) & (ARRAY_SIZE(dss_log->spi) - 1); dss_log->spi[i].time = cpu_clock(cpu); dss_log->spi[i].cpu = cpu; dss_log->spi[i].ctlr = ctlr; dss_log->spi[i].cur_msg = cur_msg; dss_log->spi[i].en = en; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_BINDER void dbg_snapshot_binder(struct trace_binder_transaction_base *base, struct trace_binder_transaction *transaction, struct trace_binder_transaction_error *error) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; int cpu; unsigned long i; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; if (base == NULL) return; cpu = raw_smp_processor_id(); i = atomic_inc_return(&dss_idx.binder_log_idx) & (ARRAY_SIZE(dss_log->binder) - 1); dss_log->binder[i].time = cpu_clock(cpu); dss_log->binder[i].cpu = cpu; dss_log->binder[i].base = *base; if (transaction) { dss_log->binder[i].transaction = *transaction; } else { dss_log->binder[i].transaction.to_node_id = 0; dss_log->binder[i].transaction.reply = 0; dss_log->binder[i].transaction.flags = 0; dss_log->binder[i].transaction.code = 0; } if (error) { dss_log->binder[i].error = *error; } else { dss_log->binder[i].error.return_error = 0; dss_log->binder[i].error.return_error_param = 0; dss_log->binder[i].error.return_error_line = 0; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_ACPM void dbg_snapshot_acpm(unsigned long long timestamp, const char *log, unsigned int data) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long i = atomic_inc_return(&dss_idx.acpm_log_idx) & (ARRAY_SIZE(dss_log->acpm) - 1); int len = strlen(log); if (len >= 8) len = 8; dss_log->acpm[i].time = cpu_clock(cpu); dss_log->acpm[i].acpm_time = timestamp; strncpy(dss_log->acpm[i].log, log, len); dss_log->acpm[i].log[len] = '\0'; dss_log->acpm[i].data = data; } } #endif #ifdef CONFIG_DEBUG_SNAPSHOT_REG static phys_addr_t virt_to_phys_high(size_t vaddr) { phys_addr_t paddr = 0; pgd_t *pgd; pmd_t *pmd; pte_t *pte; if (virt_addr_valid((void *) vaddr)) { paddr = virt_to_phys((void *) vaddr); goto out; } pgd = pgd_offset_k(vaddr); if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd))) goto out; if (pgd_val(*pgd) & 2) { paddr = pgd_val(*pgd) & SECTION_MASK; goto out; } pmd = pmd_offset((pud_t *)pgd, vaddr); if (pmd_none_or_clear_bad(pmd)) goto out; pte = pte_offset_kernel(pmd, vaddr); if (pte_none(*pte)) goto out; paddr = pte_val(*pte) & PAGE_MASK; out: return paddr | (vaddr & UL(SZ_4K - 1)); } void dbg_snapshot_reg(unsigned int read, size_t val, size_t reg, int en) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; int cpu = raw_smp_processor_id(); unsigned long i, j; size_t phys_reg, start_addr, end_addr; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; if (dss_reg_exlist[0].addr == 0) return; phys_reg = virt_to_phys_high(reg); if (unlikely(!phys_reg)) return; for (j = 0; j < ARRAY_SIZE(dss_reg_exlist); j++) { if (dss_reg_exlist[j].addr == 0) break; start_addr = dss_reg_exlist[j].addr; end_addr = start_addr + dss_reg_exlist[j].size; if (start_addr <= phys_reg && phys_reg <= end_addr) return; } i = atomic_inc_return(&dss_idx.reg_log_idx[cpu]) & (ARRAY_SIZE(dss_log->reg[0]) - 1); dss_log->reg[cpu][i].time = cpu_clock(cpu); dss_log->reg[cpu][i].read = read; dss_log->reg[cpu][i].val = val; dss_log->reg[cpu][i].reg = phys_reg; dss_log->reg[cpu][i].en = en; for (j = 0; j < dss_desc.callstack; j++) { dss_log->reg[cpu][i].caller[j] = (void *)((size_t)return_address(j + 1)); } } #endif #ifndef CONFIG_DEBUG_SNAPSHOT_MINIMIZED_MODE void dbg_snapshot_printk(const char *fmt, ...) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); va_list args; int ret; unsigned long j, i = atomic_inc_return(&dss_idx.printk_log_idx) & (ARRAY_SIZE(dss_log->printk) - 1); va_start(args, fmt); ret = vsnprintf(dss_log->printk[i].log, sizeof(dss_log->printk[i].log), fmt, args); va_end(args); dss_log->printk[i].time = cpu_clock(cpu); dss_log->printk[i].cpu = cpu; for (j = 0; j < dss_desc.callstack; j++) { dss_log->printk[i].caller[j] = (void *)((size_t)return_address(j)); } } } void dbg_snapshot_printkl(size_t msg, size_t val) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return; { int cpu = raw_smp_processor_id(); unsigned long j, i = atomic_inc_return(&dss_idx.printkl_log_idx) & (ARRAY_SIZE(dss_log->printkl) - 1); dss_log->printkl[i].time = cpu_clock(cpu); dss_log->printkl[i].cpu = cpu; dss_log->printkl[i].msg = msg; dss_log->printkl[i].val = val; for (j = 0; j < dss_desc.callstack; j++) { dss_log->printkl[i].caller[j] = (void *)((size_t)return_address(j)); } } } #endif #if defined(CONFIG_DEBUG_SNAPSHOT_THERMAL) && defined(CONFIG_SEC_PM_DEBUG) #include static int exynos_ss_thermal_show(struct seq_file *m, void *unused) { struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; unsigned long idx, size; unsigned long rem_nsec; u64 ts; int i; if (unlikely(!dss_base.enabled || !item->entry.enabled)) return 0; seq_puts(m, "time\t\t\ttemperature\tcooling_device\t\tmax_frequency\n"); size = ARRAY_SIZE(dss_log->thermal); idx = atomic_read(&dss_idx.thermal_log_idx); for (i = 0; i < size; i++, idx--) { idx &= size - 1; ts = dss_log->thermal[idx].time; if (!ts) break; rem_nsec = do_div(ts, NSEC_PER_SEC); seq_printf(m, "[%8lu.%06lu]\t%u\t\t%-16s\t%u\n", (unsigned long)ts, rem_nsec / NSEC_PER_USEC, dss_log->thermal[idx].temp, dss_log->thermal[idx].cooling_device, dss_log->thermal[idx].cooling_state); } return 0; } static int exynos_ss_thermal_open(struct inode *inode, struct file *file) { return single_open(file, exynos_ss_thermal_show, NULL); } static const struct file_operations thermal_fops = { .owner = THIS_MODULE, .open = exynos_ss_thermal_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static struct dentry *debugfs_ess_root; static int __init exynos_ss_debugfs_init(void) { debugfs_ess_root = debugfs_create_dir("exynos-ss", NULL); if (!debugfs_ess_root) { pr_err("Failed to create exynos-ss debugfs\n"); return 0; } debugfs_create_file("thermal", 0444, debugfs_ess_root, NULL, &thermal_fops); return 0; } late_initcall(exynos_ss_debugfs_init); #endif /* CONFIG_DEBUG_SNAPSHOT_THERMAL && CONFIG_SEC_PM_DEBUG */ #if defined(CONFIG_HARDLOCKUP_DETECTOR_OTHER_CPU) \ && defined(CONFIG_SEC_DEBUG) #define for_each_generated_irq_in_snapshot(idx, i, max, base, cpu) \ for (i = 0, idx = base; i < max; ++i, idx = (base - i) & (ARRAY_SIZE(dss_log->irq[0]) - 1)) \ if (dss_log->irq[cpu][idx].en == DSS_FLAG_IN) static inline void dbg_snapshot_get_busiest_irq(struct hardlockup_info *hl_info, unsigned long start_idx, int cpu) { #define MAX_BUF 5 int i, j, idx, max_count = 20; int buf_count = 0; int max_irq_idx = 0; struct irq_info_buf { unsigned int occurrences; int irq; void *fn; unsigned long long total_duration; unsigned long long last_time; }; struct irq_info_buf i_buf[MAX_BUF] = {{0,},}; for_each_generated_irq_in_snapshot(idx, i, max_count, start_idx, cpu) { for (j = 0; j < buf_count; j++) { if (i_buf[j].irq == dss_log->irq[cpu][idx].irq) { i_buf[j].total_duration += (i_buf[j].last_time - dss_log->irq[cpu][idx].time); i_buf[j].last_time = dss_log->irq[cpu][idx].time; i_buf[j].occurrences++; break; } } if (j == buf_count && buf_count < MAX_BUF) { i_buf[buf_count].irq = dss_log->irq[cpu][idx].irq; i_buf[buf_count].fn = dss_log->irq[cpu][idx].fn; i_buf[buf_count].occurrences = 0; i_buf[buf_count].total_duration = 0; i_buf[buf_count].last_time = dss_log->irq[cpu][idx].time; buf_count++; } else if (buf_count == MAX_BUF) { pr_info("Buffer overflow. Various irqs were generated!!\n"); } } for (i = 1; i < buf_count; i++) { if (i_buf[max_irq_idx].occurrences < i_buf[i].occurrences) max_irq_idx = i; } hl_info->irq_info.irq = i_buf[max_irq_idx].irq; hl_info->irq_info.fn = i_buf[max_irq_idx].fn; hl_info->irq_info.avg_period = i_buf[max_irq_idx].total_duration / i_buf[max_irq_idx].occurrences; } void dbg_snapshot_get_hardlockup_info(unsigned int cpu, void *info) { struct hardlockup_info *hl_info = info; unsigned long cpuidle_idx, irq_idx, task_idx; unsigned long long cpuidle_delay_time, irq_delay_time, task_delay_time; unsigned long long curr, thresh; thresh = get_hardlockup_thresh(); curr = local_clock(); cpuidle_idx = atomic_read(&dss_idx.cpuidle_log_idx[cpu]) & (ARRAY_SIZE(dss_log->cpuidle[0]) - 1); cpuidle_delay_time = curr - dss_log->cpuidle[cpu][cpuidle_idx].time; if (dss_log->cpuidle[cpu][cpuidle_idx].en == DSS_FLAG_IN && cpuidle_delay_time > thresh) { hl_info->delay_time = cpuidle_delay_time; hl_info->cpuidle_info.mode = dss_log->cpuidle[cpu][cpuidle_idx].modes; hl_info->hl_type = HL_IDLE_STUCK; return; } irq_idx = atomic_read(&dss_idx.irq_log_idx[cpu]) & (ARRAY_SIZE(dss_log->irq[0]) - 1); irq_delay_time = curr - dss_log->irq[cpu][irq_idx].time; if (dss_log->irq[cpu][irq_idx].en == DSS_FLAG_IN && irq_delay_time > thresh) { hl_info->delay_time = irq_delay_time; if (dss_log->irq[cpu][irq_idx].irq < 0) { // smc calls have negative irq number hl_info->smc_info.cmd = dss_log->irq[cpu][irq_idx].irq; hl_info->hl_type = HL_SMC_CALL_STUCK; return; } else { hl_info->irq_info.irq = dss_log->irq[cpu][irq_idx].irq; hl_info->irq_info.fn = dss_log->irq[cpu][irq_idx].fn; hl_info->hl_type = HL_IRQ_STUCK; return; } } task_idx = atomic_read(&dss_idx.task_log_idx[cpu]) & (ARRAY_SIZE(dss_log->task[0]) - 1); task_delay_time = curr - dss_log->task[cpu][task_idx].time; if (task_delay_time > thresh) { hl_info->delay_time = task_delay_time; if (irq_delay_time > thresh) { strncpy(hl_info->task_info.task_comm, dss_log->task[cpu][task_idx].task_comm, TASK_COMM_LEN - 1); hl_info->task_info.task_comm[TASK_COMM_LEN - 1] = '\0'; hl_info->hl_type = HL_TASK_STUCK; return; } else { dbg_snapshot_get_busiest_irq(hl_info, irq_idx, cpu); hl_info->hl_type = HL_IRQ_STORM; return; } } hl_info->hl_type = HL_UNKNOWN_STUCK; } void dbg_snapshot_get_softlockup_info(unsigned int cpu, void *info) { struct softlockup_info *sl_info = info; unsigned long task_idx; unsigned long long task_delay_time; unsigned long long curr, thresh; thresh = get_dss_softlockup_thresh(); curr = local_clock(); task_idx = atomic_read(&dss_idx.task_log_idx[cpu]) & (ARRAY_SIZE(dss_log->task[0]) - 1); task_delay_time = curr - dss_log->task[cpu][task_idx].time; sl_info->delay_time = task_delay_time; strncpy(sl_info->task_info.task_comm, dss_log->task[cpu][task_idx].task_comm, TASK_COMM_LEN - 1); sl_info->task_info.task_comm[TASK_COMM_LEN - 1] = '\0'; if (task_delay_time > thresh) sl_info->sl_type = SL_TASK_STUCK; else sl_info->sl_type = SL_UNKNOWN_STUCK; } #endif