| /* |
| * 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 <hosung0.kim@samsung.com> |
| * Author: Changki Kim <changki.kim@samsung.com> |
| * |
| * 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 <linux/kernel.h> |
| #include <linux/io.h> |
| #include <linux/notifier.h> |
| #include <linux/delay.h> |
| #include <linux/kallsyms.h> |
| #include <linux/input.h> |
| #include <linux/smc.h> |
| #include <linux/bitops.h> |
| #include <linux/sched/clock.h> |
| #include <linux/sched/debug.h> |
| #include <linux/nmi.h> |
| #include <linux/init_task.h> |
| #include <linux/ftrace.h> |
| #include <linux/sec_debug.h> |
| |
| #include <asm/cputype.h> |
| #include <asm/smp_plat.h> |
| #include <asm/core_regs.h> |
| #include <asm/cacheflush.h> |
| |
| #include "debug-snapshot-local.h" |
| |
| DEFINE_PER_CPU(struct pt_regs *, dss_core_reg); |
| DEFINE_PER_CPU(struct dbg_snapshot_mmu_reg *, dss_mmu_reg); |
| |
| struct dbg_snapshot_allcorelockup_param { |
| unsigned long last_pc_addr; |
| unsigned long spin_pc_addr; |
| } dss_allcorelockup_param; |
| |
| void dbg_snapshot_hook_hardlockup_entry(void *v_regs) |
| { |
| int cpu = raw_smp_processor_id(); |
| unsigned int val; |
| |
| if (!dss_base.enabled) |
| return; |
| |
| if (!dss_desc.hardlockup_core_mask) { |
| if (dss_desc.multistage_wdt_irq && |
| !dss_desc.allcorelockup_detected) { |
| /* 1st FIQ trigger */ |
| val = readl(dbg_snapshot_get_base_vaddr() + |
| DSS_OFFSET_CORE_LAST_PC + (DSS_NR_CPUS * sizeof(unsigned long))); |
| if (val == DSS_SIGN_LOCKUP || val == (DSS_SIGN_LOCKUP + 1)) { |
| dss_desc.allcorelockup_detected = true; |
| dss_desc.hardlockup_core_mask = GENMASK(DSS_NR_CPUS - 1, 0); |
| } else { |
| return; |
| } |
| } |
| } |
| |
| /* re-check the cpu number which is lockup */ |
| if (dss_desc.hardlockup_core_mask & BIT(cpu)) { |
| int ret; |
| unsigned long last_pc; |
| struct pt_regs *regs; |
| unsigned long timeout = USEC_PER_SEC * 2; |
| |
| do { |
| /* |
| * If one cpu is occurred to lockup, |
| * others are going to output its own information |
| * without side-effect. |
| */ |
| ret = do_raw_spin_trylock(&dss_desc.nmi_lock); |
| if (!ret) |
| udelay(1); |
| } while (!ret && timeout--); |
| |
| last_pc = dbg_snapshot_get_last_pc(cpu); |
| |
| regs = (struct pt_regs *)v_regs; |
| |
| /* Replace real pc value even if it is invalid */ |
| regs->pc = last_pc; |
| |
| /* Then, we expect bug() function works well */ |
| pr_emerg("\n--------------------------------------------------------------------------\n"); |
| pr_auto(ASL4, " Debugging Information for Hardlockup core - CPU(%d), Mask:(0x%x)\n", |
| cpu, dss_desc.hardlockup_core_mask); |
| pr_emerg("--------------------------------------------------------------------------\n\n"); |
| |
| #if defined(CONFIG_HARDLOCKUP_DETECTOR_OTHER_CPU) \ |
| && defined(CONFIG_SEC_DEBUG) |
| update_hardlockup_type(cpu); |
| #endif |
| |
| #ifdef CONFIG_SEC_DEBUG_EXTRA_INFO |
| sec_debug_set_extra_info_backtrace_cpu(v_regs, cpu); |
| #endif |
| } |
| } |
| |
| void dbg_snapshot_hook_hardlockup_exit(void) |
| { |
| int cpu = raw_smp_processor_id(); |
| |
| if (!dss_base.enabled || |
| !dss_desc.hardlockup_core_mask) { |
| return; |
| } |
| |
| /* re-check the cpu number which is lockup */ |
| if (dss_desc.hardlockup_core_mask & BIT(cpu)) { |
| /* clear bit to complete replace */ |
| dss_desc.hardlockup_core_mask &= ~(BIT(cpu)); |
| /* |
| * If this unlock function does not make a side-effect |
| * even it's not lock |
| */ |
| do_raw_spin_unlock(&dss_desc.nmi_lock); |
| } |
| } |
| |
| void dbg_snapshot_recall_hardlockup_core(void) |
| { |
| int i; |
| #ifdef SMC_CMD_KERNEL_PANIC_NOTICE |
| int ret; |
| #endif |
| unsigned long cpu_mask = 0, tmp_bit = 0; |
| unsigned long last_pc_addr = 0, timeout; |
| |
| if (dss_desc.allcorelockup_detected) { |
| pr_emerg("debug-snapshot: skip recall hardlockup for dump of each core\n"); |
| goto out; |
| } |
| |
| for (i = 0; i < DSS_NR_CPUS; i++) { |
| if (i == raw_smp_processor_id()) |
| continue; |
| tmp_bit = cpu_online_mask->bits[DSS_NR_CPUS/SZ_64] & (1 << i); |
| if (tmp_bit) |
| cpu_mask |= tmp_bit; |
| } |
| |
| if (!cpu_mask) |
| goto out; |
| |
| last_pc_addr = dbg_snapshot_get_last_pc_paddr(); |
| |
| pr_emerg("debug-snapshot: core hardlockup mask information: 0x%lx\n", cpu_mask); |
| dss_desc.hardlockup_core_mask = cpu_mask; |
| |
| #ifdef SMC_CMD_KERNEL_PANIC_NOTICE |
| /* Setup for generating NMI interrupt to unstopped CPUs */ |
| ret = dss_soc_ops->soc_smc_call(SMC_CMD_KERNEL_PANIC_NOTICE, |
| cpu_mask, |
| (unsigned long)dbg_snapshot_bug_func, |
| last_pc_addr); |
| if (ret) { |
| pr_emerg("debug-snapshot: failed to generate NMI, " |
| "not support to dump information of core\n"); |
| dss_desc.hardlockup_core_mask = 0; |
| goto out; |
| } |
| #endif |
| /* Wait up to 3 seconds for NMI interrupt */ |
| timeout = USEC_PER_SEC * 3; |
| while (dss_desc.hardlockup_core_mask != 0 && timeout--) |
| udelay(1); |
| out: |
| return; |
| } |
| |
| void dbg_snapshot_save_system(void *unused) |
| { |
| struct dbg_snapshot_mmu_reg *mmu_reg; |
| |
| if (!dbg_snapshot_get_enable("header")) |
| return; |
| |
| mmu_reg = per_cpu(dss_mmu_reg, raw_smp_processor_id()); |
| |
| dss_soc_ops->soc_save_system((void *)mmu_reg); |
| } |
| |
| int dbg_snapshot_dump(void) |
| { |
| dss_soc_ops->soc_dump_info(NULL); |
| return 0; |
| } |
| EXPORT_SYMBOL(dbg_snapshot_dump); |
| |
| int dbg_snapshot_save_core(void *v_regs) |
| { |
| struct pt_regs *regs = (struct pt_regs *)v_regs; |
| struct pt_regs *core_reg = |
| per_cpu(dss_core_reg, smp_processor_id()); |
| |
| if(!dbg_snapshot_get_enable("header")) |
| return 0; |
| |
| if (!regs) |
| dss_soc_ops->soc_save_core((void *)core_reg); |
| else |
| memcpy(core_reg, regs, sizeof(struct user_pt_regs)); |
| |
| pr_emerg("debug-snapshot: core register saved(CPU:%d)\n", |
| smp_processor_id()); |
| return 0; |
| } |
| EXPORT_SYMBOL(dbg_snapshot_save_core); |
| |
| int dbg_snapshot_save_context(void *v_regs) |
| { |
| int cpu; |
| unsigned long flags; |
| struct pt_regs *regs = (struct pt_regs *)v_regs; |
| |
| if (unlikely(!dss_base.enabled)) |
| return 0; |
| |
| dss_soc_ops->soc_save_context_entry(NULL); |
| |
| cpu = smp_processor_id(); |
| raw_spin_lock_irqsave(&dss_desc.ctrl_lock, flags); |
| |
| /* If it was already saved the context information, it should be skipped */ |
| if (dbg_snapshot_get_core_panic_stat(cpu) != DSS_SIGN_PANIC) { |
| dbg_snapshot_save_system(NULL); |
| dbg_snapshot_save_core(regs); |
| dbg_snapshot_dump(); |
| dbg_snapshot_set_core_panic_stat(DSS_SIGN_PANIC, cpu); |
| pr_emerg("debug-snapshot: context saved(CPU:%d)\n", cpu); |
| } else |
| pr_emerg("debug-snapshot: skip context saved(CPU:%d)\n", cpu); |
| |
| raw_spin_unlock_irqrestore(&dss_desc.ctrl_lock, flags); |
| |
| dss_soc_ops->soc_save_context_exit(NULL); |
| return 0; |
| } |
| EXPORT_SYMBOL(dbg_snapshot_save_context); |
| |
| static void dbg_snapshot_dump_one_task_info(struct task_struct *tsk, bool is_main) |
| { |
| char state_array[] = {'R', 'S', 'D', 'T', 't', 'X', 'Z', 'P', 'x', 'K', 'W', 'I', 'N'}; |
| unsigned char idx = 0; |
| unsigned long state; |
| unsigned long wchan; |
| unsigned long pc = 0; |
| char symname[KSYM_NAME_LEN]; |
| |
| if ((tsk == NULL) || !try_get_task_stack(tsk)) |
| return; |
| state = tsk->state | tsk->exit_state; |
| |
| pc = KSTK_EIP(tsk); |
| wchan = get_wchan(tsk); |
| if (lookup_symbol_name(wchan, symname) < 0) |
| snprintf(symname, KSYM_NAME_LEN, "%lu", wchan); |
| |
| while (state) { |
| idx++; |
| state >>= 1; |
| } |
| |
| /* |
| * kick watchdog to prevent unexpected reset during panic sequence |
| * and it prevents the hang during panic sequence by watchedog |
| */ |
| touch_softlockup_watchdog(); |
| dss_soc_ops->soc_kick_watchdog(NULL); |
| |
| pr_info("%8d %8d %8d %16lld %c(%d) %3d %16zx %16zx %16zx %c %16s [%s]\n", |
| tsk->pid, (int)(tsk->utime), (int)(tsk->stime), |
| tsk->se.exec_start, state_array[idx], (int)(tsk->state), |
| task_cpu(tsk), wchan, pc, (unsigned long)tsk, |
| is_main ? '*' : ' ', tsk->comm, symname); |
| |
| if (tsk->state == TASK_RUNNING || tsk->state == TASK_UNINTERRUPTIBLE || tsk->state == TASK_KILLABLE) { |
| sec_debug_wtsk_print_info(tsk, true); |
| show_stack(tsk, NULL); |
| pr_info("\n"); |
| } |
| } |
| |
| static inline struct task_struct *get_next_thread(struct task_struct *tsk) |
| { |
| return container_of(tsk->thread_group.next, |
| struct task_struct, |
| thread_group); |
| } |
| |
| void dbg_snapshot_dump_task_info(void) |
| { |
| struct task_struct *frst_tsk; |
| struct task_struct *curr_tsk; |
| struct task_struct *frst_thr; |
| struct task_struct *curr_thr; |
| |
| pr_info("\n"); |
| pr_info(" current proc : %d %s\n", current->pid, current->comm); |
| pr_info(" ----------------------------------------------------------------------------------------------------------------------------\n"); |
| pr_info(" pid uTime sTime exec(ns) stat cpu wchan user_pc task_struct comm sym_wchan\n"); |
| pr_info(" ----------------------------------------------------------------------------------------------------------------------------\n"); |
| |
| /* processes */ |
| frst_tsk = &init_task; |
| curr_tsk = frst_tsk; |
| while (curr_tsk != NULL) { |
| dbg_snapshot_dump_one_task_info(curr_tsk, true); |
| /* threads */ |
| if (curr_tsk->thread_group.next != NULL) { |
| frst_thr = get_next_thread(curr_tsk); |
| curr_thr = frst_thr; |
| if (frst_thr != curr_tsk) { |
| while (curr_thr != NULL) { |
| dbg_snapshot_dump_one_task_info(curr_thr, false); |
| curr_thr = get_next_thread(curr_thr); |
| if (curr_thr == curr_tsk) |
| break; |
| } |
| } |
| } |
| curr_tsk = container_of(curr_tsk->tasks.next, |
| struct task_struct, tasks); |
| if (curr_tsk == frst_tsk) |
| break; |
| } |
| pr_info(" ----------------------------------------------------------------------------------------------------------------------------\n"); |
| } |
| |
| #ifdef CONFIG_DEBUG_SNAPSHOT_CRASH_KEY |
| void dbg_snapshot_check_crash_key(unsigned int code, int value) |
| { |
| static bool volup_p; |
| static bool voldown_p; |
| static int loopcount; |
| |
| static const unsigned int VOLUME_UP = KEY_VOLUMEUP; |
| static const unsigned int VOLUME_DOWN = KEY_VOLUMEDOWN; |
| |
| if (code == KEY_POWER) |
| pr_info("debug-snapshot: POWER-KEY %s\n", value ? "pressed" : "released"); |
| |
| /* Enter Forced Upload |
| * Hold volume down key first |
| * and then press power key twice |
| * and volume up key should not be pressed |
| */ |
| if (value) { |
| if (code == VOLUME_UP) |
| volup_p = true; |
| if (code == VOLUME_DOWN) |
| voldown_p = true; |
| if (!volup_p && voldown_p) { |
| if (code == KEY_POWER) { |
| pr_info |
| ("debug-snapshot: count for entering forced upload [%d]\n", |
| ++loopcount); |
| if (loopcount == 2) { |
| panic("Crash Key"); |
| } |
| } |
| } |
| } else { |
| if (code == VOLUME_UP) |
| volup_p = false; |
| if (code == VOLUME_DOWN) { |
| loopcount = 0; |
| voldown_p = false; |
| } |
| } |
| } |
| #endif |
| |
| void __init dbg_snapshot_allcorelockup_detector_init(void) |
| { |
| int ret = -1; |
| |
| if (!dss_desc.multistage_wdt_irq) |
| return; |
| |
| dss_allcorelockup_param.last_pc_addr = dbg_snapshot_get_last_pc_paddr(); |
| dss_allcorelockup_param.spin_pc_addr = __pa_symbol(dbg_snapshot_spin_func); |
| |
| __flush_dcache_area((void *)&dss_allcorelockup_param, |
| sizeof(struct dbg_snapshot_allcorelockup_param)); |
| |
| #ifdef SMC_CMD_LOCKUP_NOTICE |
| /* Setup for generating NMI interrupt to unstopped CPUs */ |
| ret = dss_soc_ops->soc_smc_call(SMC_CMD_LOCKUP_NOTICE, |
| (unsigned long)dbg_snapshot_bug_func, |
| dss_desc.multistage_wdt_irq, |
| (unsigned long)(virt_to_phys)(&dss_allcorelockup_param)); |
| #endif |
| |
| pr_emerg("debug-snapshot: %s to register all-core lockup detector - ret: %d\n", |
| ret == 0 ? "success" : "failed", ret); |
| } |
| |
| void __init dbg_snapshot_init_utils(void) |
| { |
| size_t vaddr; |
| int i; |
| |
| vaddr = dss_items[dss_desc.header_num].entry.vaddr; |
| |
| for (i = 0; i < DSS_NR_CPUS; i++) { |
| per_cpu(dss_mmu_reg, i) = (struct dbg_snapshot_mmu_reg *) |
| (vaddr + DSS_HEADER_SZ + |
| i * DSS_MMU_REG_OFFSET); |
| per_cpu(dss_core_reg, i) = (struct pt_regs *) |
| (vaddr + DSS_HEADER_SZ + DSS_MMU_REG_SZ + |
| i * DSS_CORE_REG_OFFSET); |
| } |
| |
| /* hardlockup_detector function should be called before secondary booting */ |
| dbg_snapshot_allcorelockup_detector_init(); |
| } |
| |
| static int __init dbg_snapshot_utils_save_systems_all(void) |
| { |
| smp_call_function(dbg_snapshot_save_system, NULL, 1); |
| dbg_snapshot_save_system(NULL); |
| |
| return 0; |
| } |
| postcore_initcall(dbg_snapshot_utils_save_systems_all); |