From dd101ca5b92663c600647406cd69b7269a8605de Mon Sep 17 00:00:00 2001 From: Donghyeok Choe Date: Sun, 13 May 2018 15:21:23 +0900 Subject: [PATCH] [COMMON] lib: dss: add to support Debug-SnapShot(DSS) Change-Id: I28ab91356cc2852a38ee6b7c08d1bcb837d6e8ca Signed-off-by: Donghyeok Choe --- arch/arm64/include/asm/core_regs.h | 186 +++ arch/arm64/include/asm/cputype.h | 4 + arch/arm64/include/asm/io.h | 21 + arch/arm64/include/asm/irqflags.h | 5 + arch/arm64/kernel/process.c | 13 + arch/arm64/kernel/smp.c | 12 +- arch/arm64/kernel/traps.c | 1 + arch/arm64/mm/fault.c | 1 + drivers/cpuidle/cpuidle.c | 3 + drivers/input/keyboard/gpio_keys.c | 3 + drivers/scsi/ufs/ufs-exynos-dbg.c | 1 + drivers/soc/samsung/debug/exynos-helper.c | 139 ++ drivers/watchdog/s3c2410_wdt.c | 1 + include/linux/debug-snapshot.h | 10 + include/soc/samsung/exynos-debug.h | 33 + include/soc/samsung/exynos-sdm.h | 23 + kernel/irq/handle.c | 3 + kernel/irq/irqdesc.c | 4 + kernel/panic.c | 10 +- kernel/printk/printk.c | 4 +- kernel/rcu/tree.c | 5 + kernel/sched/core.c | 2 + kernel/softirq.c | 11 + kernel/time/clockevents.c | 4 + kernel/time/hrtimer.c | 3 + kernel/time/timer.c | 3 + kernel/workqueue.c | 3 + lib/Kconfig.debug | 106 ++ lib/Makefile | 3 + lib/debug-snapshot-helper.c | 400 ++++++ lib/debug-snapshot-local.h | 394 ++++++ lib/debug-snapshot-log.c | 1423 +++++++++++++++++++++ lib/debug-snapshot-pstore.c | 244 ++++ lib/debug-snapshot-sysfs.c | 334 +++++ lib/debug-snapshot-utils.c | 646 ++++++++++ lib/debug-snapshot.c | 671 ++++++++++ 36 files changed, 4724 insertions(+), 5 deletions(-) create mode 100644 arch/arm64/include/asm/core_regs.h create mode 100644 drivers/soc/samsung/debug/exynos-helper.c create mode 100644 include/soc/samsung/exynos-debug.h create mode 100644 include/soc/samsung/exynos-sdm.h create mode 100644 lib/debug-snapshot-helper.c create mode 100644 lib/debug-snapshot-local.h create mode 100644 lib/debug-snapshot-log.c create mode 100644 lib/debug-snapshot-pstore.c create mode 100644 lib/debug-snapshot-sysfs.c create mode 100644 lib/debug-snapshot-utils.c create mode 100644 lib/debug-snapshot.c diff --git a/arch/arm64/include/asm/core_regs.h b/arch/arm64/include/asm/core_regs.h new file mode 100644 index 000000000000..8630dbf6983f --- /dev/null +++ b/arch/arm64/include/asm/core_regs.h @@ -0,0 +1,186 @@ +/* + * Copyright 2014 Samsung Electronics Co., Ltd. + * http://www.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. +*/ + +#ifndef __CORESIGHT_REGS_H +#define __CORESIGHT_REGS_H + +#include +#include + +#define OSLOCK_MAGIC (0xc5acce55) + +/* Defines are used by core-sight */ +#define CS_SJTAG_OFFSET (0x8000) +#define SJTAG_STATUS (0x4) +#define SJTAG_SOFT_LOCK (1<<2) + +/* DBG Registers */ +#define DBGWFAR (0x018) /* RW */ +#define DBGVCR (0x01c) /* RW */ +#define DBGECR (0x024) /* RW or RAZ */ +#define DBGDSCCR (0x028) /* RW or RAZ */ +#define DBGDSMCR (0x02c) /* RW or RAZ */ +#define DBGDTRRX (0x080) /* RW */ +#define DBGITR (0x084) /* RW */ +#define DBGDSCR (0x088) /* RW */ +#define DBGDTRTX (0x08c) /* RW */ +#define DBGDRCR (0x090) /* WO */ +#define DBGEACR (0x094) /* RW */ +#define DBGECCR (0x098) /* RW */ +#define DBGPCSRlo (0x0a0) /* RO */ +#define DBGCIDSR (0x0a4) /* RO */ +#define DBGVIDSR (0x0a8) /* RO */ +#define DBGPCSRhi (0x0ac) /* RO */ +#define DBGBXVR0 (0x250) /* RW */ +#define DBGBXVR1 (0x254) /* RW */ +#define DBGOSLAR (0x300) /* WO */ +#define DBGOSLSR (0x304) /* RO */ +#define DBGPRCR (0x310) /* RW */ +#define DBGPRSR (0x314) /* RO, OSLSR in ARMv8 */ +#define DBGITOCTRL (0xef8) /* WO */ +#define DBGITISR (0xefc) /* RO */ +#define DBGITCTRL (0xf00) /* RW */ +#define DBGCLAIMSET (0xfa0) /* RW */ +#define DBGCLAIMCLR (0xfa4) /* RW */ +#define DBGLAR (0xfb0) /* WO */ +#define DBGLSR (0xfb4) /* RO */ +#define DBGAUTHSTATUS (0xfb8) /* RO */ +#define DBGDEVID2 (0xfc0) /* RO */ +#define DBGDEVID1 (0xfc4) /* RO, PC offset */ +#define DBGDEVID0 (0xfc8) /* RO */ +#define DBGDEVTYPE (0xfcc) /* RO */ + +#define MIDR (0xd00) /* RO */ +#define ID_AA64DFR0_EL1 (0xd28) + +/* DBG breakpoint registers (All RW) */ +#define DBGBVRn(n) (0x400 + (n * 0x10)) /* 64bit */ +#define DBGBCRn(n) (0x408 + (n * 0x10)) +/* DBG watchpoint registers (All RW) */ +#define DBGWVRn(n) (0x800 + (n * 0x10)) /* 64bit */ +#define DBGWCRn(n) (0x808 + (n * 0x10)) + +/* DIDR or ID_AA64DFR0_EL1 bit */ +#define DEBUG_ARCH_V8 (0x6) + +/* MIDR bit */ +#define ARMV8_PROCESSOR (0xf << 16) +#define ARMV8_CORTEXA53 (0xd03) +#define ARMV8_CORTEXA57 (0xd07) + +/* TMC(ETB/ETF/ETR) registers */ +#define TMCRSZ (0x004) +#define TMCSTS (0x00c) +#define TMCRRD (0x010) +#define TMCRRP (0x014) +#define TMCRWP (0x018) +#define TMCTGR (0x01c) +#define TMCCTL (0x020) +#define TMCRWD (0x024) +#define TMCMODE (0x028) +#define TMCLBUFLEVEL (0x02c) +#define TMCCBUFLEVEL (0x030) +#define TMCBUFWM (0x034) +#define TMCRRPHI (0x038) +#define TMCRWPHI (0x03c) +#define TMCAXICTL (0x110) +#define TMCDBALO (0x118) +#define TMCDBAHI (0x11c) +#define TMCFFSR (0x300) +#define TMCFFCR (0x304) +#define TMCPSCR (0x308) + +/* Coresight manager register */ +#define ITCTRL (0xf00) +#define CLAIMSET (0xfa0) +#define CLAIMCLR (0xfa4) +#define LAR (0xfb0) +#define LSR (0xfb4) +#define AUTHSTATUS (0xfb8) + +/* FUNNEL configuration register */ +#define FUNCTRL (0x0) +#define FUNPRIORCTRL (0x4) + +#define DBG_OFFSET (0x0) +#define CTI_OFFSET (0x10000) +#define PMU_OFFSET (0x20000) +#define ETM_OFFSET (0x30000) +#define PMUPCSRlo (0x200) +#define PMUPCSRhi (0x204) + +/* ETM registers */ +#define ETMCTLR (0x004) +#define ETMPROCSELR (0x008) +#define ETMSTATUS (0x00c) +#define ETMCONFIG (0x010) +#define ETMAUXCTLR (0x018) +#define ETMEVENTCTL0R (0x020) +#define ETMEVENTCTL1R (0x024) +#define ETMSTALLCTLR (0x02c) +#define ETMTSCTLR (0x030) +#define ETMSYNCPR (0x034) +#define ETMCCCCTLR (0x038) +#define ETMBBCTLR (0x03c) +#define ETMTRACEIDR (0x040) +#define ETMQCTRLR (0x044) +#define ETMVICTLR (0x080) +#define ETMVIIECTLR (0x084) +#define ETMVISSCTLR (0x088) +#define ETMVIPCSSCTLR (0x08c) +#define ETMVDCTLR (0x0a0) +#define ETMVDSACCTLR (0x0a4) +#define ETMVDARCCTLR (0x0a8) +#define ETMSEQEVR(n) (0x100 + (n * 4)) +#define ETMSEQRSTEVR (0x118) +#define ETMSEQSTR (0x11c) +#define ETMEXTINSELR (0x120) +#define ETMCNTRLDVR(n) (0x140 + (n * 4)) +#define ETMCNTCTLR(n) (0x150 + (n * 4)) +#define ETMCNTVR(n) (0x160 + (n * 4)) +#define ETMIDR8 (0x180) +#define ETMIDR9 (0x184) +#define ETMID10 (0x188) +#define ETMID11 (0x18c) +#define ETMID12 (0x190) +#define ETMID13 (0x194) +#define ETMID0 (0x1e0) +#define ETMID1 (0x1e4) +#define ETMID2 (0x1e8) +#define ETMID3 (0x1ec) +#define ETMID4 (0x1f0) +#define ETMID5 (0x1f4) +#define ETMID6 (0x1f8) +#define ETMID7 (0x1fc) +#define ETMRSCTLR(n) (0x200 + (n * 4)) +#define ETMSSCCR(n) (0x280 + (n * 4)) +#define ETMSSCSR(n) (0x2a0 + (n * 4)) +#define ETMSSPCICR(n) (0x2c0 + (n * 4)) +#define ETMOSLAR (0x300) +#define ETMOSLSR (0x304) +#define ETMPDCR (0x310) +#define ETMPDSR (0x314) +#define ETMACVR(n) (0x400 + (n * 4)) +#define ETMACAT(n) (0x480 + (n * 4)) +#define ETMDVCVR(n) (0x500 + (n * 4)) +#define ETMDVCMR(n) (0x580 + (n * 4)) +#define ETMCIDCVR(n) (0x600 + (n * 4)) +#define ETMVMIDCVR(n) (0x640 + (n * 4)) +#define ETMCCIDCCTLR0 (0x680) +#define ETMCCIDCCTLR1 (0x684) +#define ETMVMIDCCTLR0 (0x688) +#define ETMVMIDCCTLR1 (0x68c) + +#ifdef CONFIG_EXYNOS_CORESIGHT_ETM +extern void exynos_trace_stop(void); +#else +#define exynos_trace_stop() do { } while(0) +#endif + +#endif diff --git a/arch/arm64/include/asm/cputype.h b/arch/arm64/include/asm/cputype.h index be7bd19c87ec..108d9fab71ab 100644 --- a/arch/arm64/include/asm/cputype.h +++ b/arch/arm64/include/asm/cputype.h @@ -75,6 +75,7 @@ #define ARM_CPU_IMP_CAVIUM 0x43 #define ARM_CPU_IMP_BRCM 0x42 #define ARM_CPU_IMP_QCOM 0x51 +#define ARM_CPU_IMP_SEC 0x53 #define ARM_CPU_PART_AEM_V8 0xD0F #define ARM_CPU_PART_FOUNDATION 0xD00 @@ -83,6 +84,9 @@ #define ARM_CPU_PART_CORTEX_A53 0xD03 #define ARM_CPU_PART_CORTEX_A73 0xD09 #define ARM_CPU_PART_CORTEX_A75 0xD0A +#define ARM_CPU_PART_ANANKE 0xD05 +#define ARM_CPU_PART_MONGOOSE 0x001 +#define ARM_CPU_PART_MEERKAT 0x002 #define APM_CPU_PART_POTENZA 0x000 diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 35b2e50f17fb..1681f557ac46 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -22,6 +22,8 @@ #ifdef __KERNEL__ #include +#include +#include #include #include @@ -39,35 +41,46 @@ #define __raw_writeb __raw_writeb static inline void __raw_writeb(u8 val, volatile void __iomem *addr) { + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_IN); asm volatile("strb %w0, [%1]" : : "rZ" (val), "r" (addr)); + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_OUT); } #define __raw_writew __raw_writew static inline void __raw_writew(u16 val, volatile void __iomem *addr) { + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_IN); asm volatile("strh %w0, [%1]" : : "rZ" (val), "r" (addr)); + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_OUT); } #define __raw_writel __raw_writel static inline void __raw_writel(u32 val, volatile void __iomem *addr) { + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_IN); asm volatile("str %w0, [%1]" : : "rZ" (val), "r" (addr)); + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_OUT); } #define __raw_writeq __raw_writeq static inline void __raw_writeq(u64 val, volatile void __iomem *addr) { + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_IN); asm volatile("str %x0, [%1]" : : "rZ" (val), "r" (addr)); + dbg_snapshot_reg(0, (size_t)val, (size_t)addr, DSS_FLAG_OUT); } #define __raw_readb __raw_readb static inline u8 __raw_readb(const volatile void __iomem *addr) { u8 val; + + dbg_snapshot_reg(1, 0, (size_t)addr, DSS_FLAG_IN); asm volatile(ALTERNATIVE("ldrb %w0, [%1]", "ldarb %w0, [%1]", ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) : "=r" (val) : "r" (addr)); + dbg_snapshot_reg(1, (size_t)val, (size_t)addr, DSS_FLAG_OUT); return val; } @@ -76,10 +89,12 @@ static inline u16 __raw_readw(const volatile void __iomem *addr) { u16 val; + dbg_snapshot_reg(1, 0, (size_t)addr, DSS_FLAG_IN); asm volatile(ALTERNATIVE("ldrh %w0, [%1]", "ldarh %w0, [%1]", ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) : "=r" (val) : "r" (addr)); + dbg_snapshot_reg(1, (size_t)val, (size_t)addr, DSS_FLAG_OUT); return val; } @@ -87,10 +102,13 @@ static inline u16 __raw_readw(const volatile void __iomem *addr) static inline u32 __raw_readl(const volatile void __iomem *addr) { u32 val; + + dbg_snapshot_reg(1, 0, (size_t)addr, DSS_FLAG_IN); asm volatile(ALTERNATIVE("ldr %w0, [%1]", "ldar %w0, [%1]", ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) : "=r" (val) : "r" (addr)); + dbg_snapshot_reg(1, (size_t)val, (size_t)addr, DSS_FLAG_OUT); return val; } @@ -98,10 +116,13 @@ static inline u32 __raw_readl(const volatile void __iomem *addr) static inline u64 __raw_readq(const volatile void __iomem *addr) { u64 val; + + dbg_snapshot_reg(1, 0, (size_t)addr, DSS_FLAG_IN); asm volatile(ALTERNATIVE("ldr %0, [%1]", "ldar %0, [%1]", ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) : "=r" (val) : "r" (addr)); + dbg_snapshot_reg(1, (size_t)val, (size_t)addr, DSS_FLAG_OUT); return val; } diff --git a/arch/arm64/include/asm/irqflags.h b/arch/arm64/include/asm/irqflags.h index 8c581281fa12..39cce7d84c7f 100644 --- a/arch/arm64/include/asm/irqflags.h +++ b/arch/arm64/include/asm/irqflags.h @@ -19,6 +19,7 @@ #ifdef __KERNEL__ #include +#include /* * CPU interrupt mask handling. @@ -32,11 +33,13 @@ static inline unsigned long arch_local_irq_save(void) : "=r" (flags) : : "memory"); + dbg_snapshot_irqs_disabled(0); return flags; } static inline void arch_local_irq_enable(void) { + dbg_snapshot_irqs_disabled(1); asm volatile( "msr daifclr, #2 // arch_local_irq_enable" : @@ -51,6 +54,7 @@ static inline void arch_local_irq_disable(void) : : : "memory"); + dbg_snapshot_irqs_disabled(0); } #define local_fiq_enable() asm("msr daifclr, #1" : : : "memory") @@ -78,6 +82,7 @@ static inline unsigned long arch_local_save_flags(void) */ static inline void arch_local_irq_restore(unsigned long flags) { + dbg_snapshot_irqs_disabled(flags & PSR_I_BIT); asm volatile( "msr daif, %0 // arch_local_irq_restore" : diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index 07de304210e3..6260752e1b4e 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -157,6 +158,8 @@ void machine_restart(char *cmd) if (efi_enabled(EFI_RUNTIME_SERVICES)) efi_reboot(reboot_mode, NULL); + dbg_snapshot_post_reboot(cmd); + /* Now call the architecture specific reboot code. */ if (arm_pm_restart) arm_pm_restart(reboot_mode, cmd); @@ -249,6 +252,16 @@ void __show_regs(struct pt_regs *regs) top_reg = 29; } + if (!user_mode(regs)) { + dbg_snapshot_save_context(regs); + /* + * If you want to see more kernel events after panic, + * you should modify dbg_snapshot_set_enable's function 2nd parameter + * to true. + */ + dbg_snapshot_set_enable("log_kevents", false); + } + show_regs_print_info(KERN_DEFAULT); print_symbol("PC is at %s\n", instruction_pointer(regs)); print_symbol("LR is at %s\n", lr); diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 9f7195a5773e..20705f5090e2 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -833,12 +834,14 @@ void arch_irq_work_raise(void) /* * ipi_cpu_stop - handle IPI from smp_send_stop() */ -static void ipi_cpu_stop(unsigned int cpu) +static void ipi_cpu_stop(unsigned int cpu, struct pt_regs *regs) { set_cpu_online(cpu, false); local_irq_disable(); + dbg_snapshot_save_context(regs); + while (1) cpu_relax(); } @@ -879,6 +882,8 @@ void handle_IPI(int ipinr, struct pt_regs *regs) __inc_irq_stat(cpu, ipi_irqs[ipinr]); } + dbg_snapshot_irq(ipinr, handle_IPI, irqs_disabled(), DSS_FLAG_IN); + switch (ipinr) { case IPI_RESCHEDULE: scheduler_ipi(); @@ -892,7 +897,7 @@ void handle_IPI(int ipinr, struct pt_regs *regs) case IPI_CPU_STOP: irq_enter(); - ipi_cpu_stop(cpu); + ipi_cpu_stop(cpu, regs); irq_exit(); break; @@ -936,6 +941,9 @@ void handle_IPI(int ipinr, struct pt_regs *regs) if ((unsigned)ipinr < NR_IPI) trace_ipi_exit_rcuidle(ipi_types[ipinr]); + + dbg_snapshot_irq(ipinr, handle_IPI, irqs_disabled(), DSS_FLAG_OUT); + set_irq_regs(old_regs); } diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c index 4fc0e958770b..a7738d7685dc 100644 --- a/arch/arm64/kernel/traps.c +++ b/arch/arm64/kernel/traps.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 5edb706aacb0..a1d37cd4604d 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index e47e14101768..6158f57ebdb8 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -215,6 +215,7 @@ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, sched_idle_set_state(target_state, index); trace_cpu_idle_rcuidle(index, dev->cpu); + dbg_snapshot_cpuidle(drv->states[index].desc, index, 0, DSS_FLAG_IN); time_start = ns_to_ktime(local_clock()); stop_critical_timings(); @@ -223,6 +224,8 @@ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, sched_clock_idle_wakeup_event(); time_end = ns_to_ktime(local_clock()); + dbg_snapshot_cpuidle(drv->states[index].desc, entered_state, + (int)ktime_to_us(ktime_sub(time_end, time_start)), DSS_FLAG_OUT); trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, dev->cpu); /* The cpu is no longer idle or about to enter idle. */ diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index e9f0ebf3267a..39b91d6dfb5c 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -30,6 +30,7 @@ #include #include #include +#include struct gpio_button_data { const struct gpio_keys_button *button; @@ -371,6 +372,8 @@ static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata) return; } + dbg_snapshot_check_crash_key(button->code, state); + if (type == EV_ABS) { if (state) input_event(input, type, button->code, button->value); diff --git a/drivers/scsi/ufs/ufs-exynos-dbg.c b/drivers/scsi/ufs/ufs-exynos-dbg.c index 1d7de9973abc..a23e2a4fe870 100644 --- a/drivers/scsi/ufs/ufs-exynos-dbg.c +++ b/drivers/scsi/ufs/ufs-exynos-dbg.c @@ -9,6 +9,7 @@ #include #include +#include #include "ufshcd.h" #include "unipro.h" diff --git a/drivers/soc/samsung/debug/exynos-helper.c b/drivers/soc/samsung/debug/exynos-helper.c new file mode 100644 index 000000000000..c7ce6b420eed --- /dev/null +++ b/drivers/soc/samsung/debug/exynos-helper.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Exynos - Support SoC For Debug SnapShot + * Author: Hosung 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 +#include +#include + +#ifdef CONFIG_EXYNOS_CORESIGHT_ETR +#include +#endif + +#if defined(CONFIG_SEC_SIPC_MODEM_IF) +#include +#endif + +#if defined(CONFIG_ACPM_DVFS) +#include +#endif + +static void exynos_early_panic(void *val) +{ +} + +static void exynos_prepare_panic_entry(void *val) +{ + /* TODO: Something */ +} + +static void exynos_prepare_panic_exit(void *val) +{ +#if defined(CONFIG_SEC_SIPC_MODEM_IF) + modem_send_panic_noti_ext(); +#endif +#if defined(CONFIG_ACPM_DVFS) + acpm_stop_log(); +#endif +} + +static void exynos_post_panic_entry(void *val) +{ + /* TODO: Something */ + +} + +static void exynos_post_panic_exit(void *val) +{ +#ifdef CONFIG_EXYNOS_SDM + if (dbg_snapshot_is_scratch()) + exynos_sdm_dump_secure_region(); +#endif + +} + +static void exynos_save_context_entry(void *val) +{ +#ifdef CONFIG_EXYNOS_CORESIGHT_ETR + exynos_trace_stop(); +#endif +} + +static void exynos_save_context_exit(void *val) +{ + /* TODO: Something */ +} + +static void exynos_start_watchdog(void *val) +{ +#ifdef CONFIG_EXYNOS_SNAPSHOT_WATCHDOG_RESET + s3c2410wdt_keepalive_emergency(true, 0); +#endif +} + +static void exynos_expire_watchdog(void *val) +{ +#ifdef CONFIG_EXYNOS_SNAPSHOT_WATCHDOG_RESET + s3c2410wdt_set_emergency_reset(100, 0); +#endif +} + +static void exynos_stop_watchdog(void *val) +{ + +} + +static void exynos_kick_watchdog(void *val) +{ +#ifdef CONFIG_EXYNOS_SNAPSHOT_WATCHDOG_RESET + s3c2410wdt_keepalive_emergency(false, 0); +#endif +} + +static int exynos_is_power_cpu(void *cpu) +{ +#ifdef CONFIG_EXYNOS_PMU + return exynos_cpu.power_state((unsigned int)(unsigned long)cpu); +#else + return 0; +#endif +} + +struct dbg_snapshot_helper_ops exynos_debug_ops = { + .soc_early_panic = exynos_early_panic, + .soc_prepare_panic_entry = exynos_prepare_panic_entry, + .soc_prepare_panic_exit = exynos_prepare_panic_exit, + .soc_post_panic_entry = exynos_post_panic_entry, + .soc_post_panic_exit = exynos_post_panic_exit, + .soc_save_context_entry = exynos_save_context_entry, + .soc_save_context_exit = exynos_save_context_exit, + .soc_start_watchdog = exynos_start_watchdog, + .soc_expire_watchdog = exynos_expire_watchdog, + .soc_stop_watchdog = exynos_stop_watchdog, + .soc_kick_watchdog = exynos_kick_watchdog, + .soc_smc_call = exynos_smc, + .soc_is_power_cpu = exynos_is_power_cpu, +}; + +void __init dbg_snapshot_soc_helper_init(void) +{ + dbg_snapshot_register_soc_ops(&exynos_debug_ops); +} diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index b327c423357f..e53f4af3e0ea 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -39,6 +39,7 @@ #include #include #include +#include #define S3C2410_WTCON 0x00 #define S3C2410_WTDAT 0x04 diff --git a/include/linux/debug-snapshot.h b/include/linux/debug-snapshot.h index 468c40861e93..8869bbfcd6c2 100644 --- a/include/linux/debug-snapshot.h +++ b/include/linux/debug-snapshot.h @@ -161,6 +161,16 @@ void dbg_snapshot_check_crash_key(unsigned int code, int value); #define dbg_snapshot_check_crash_key(a,b) do { } while(0) #endif +#ifdef CONFIG_S3C2410_WATCHDOG +extern int s3c2410wdt_set_emergency_stop(int index); +extern int s3c2410wdt_set_emergency_reset(unsigned int timeout, int index); +extern int s3c2410wdt_keepalive_emergency(bool reset, int index); +#else +#define s3c2410wdt_set_emergency_stop(a) (-1) +#define s3c2410wdt_set_emergency_reset(a, b) do { } while(0) +#define s3c2410wdt_keepalive_emergency(a, b) do { } while(0) +#endif + #else #define dbg_snapshot_acpm(a,b,c) do { } while(0) #define dbg_snapshot_task(a,b) do { } while(0) diff --git a/include/soc/samsung/exynos-debug.h b/include/soc/samsung/exynos-debug.h new file mode 100644 index 000000000000..aaaca6f31481 --- /dev/null +++ b/include/soc/samsung/exynos-debug.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef EXYNOS_DEBUG_H +#define EXYNOS_DEBUG_H + +#ifdef CONFIG_S3C2410_WATCHDOG +extern int s3c2410wdt_set_emergency_stop(int index); +extern int s3c2410wdt_set_emergency_reset(unsigned int timeout, int index); +extern int s3c2410wdt_keepalive_emergency(bool reset, int index); +extern void s3c2410wdt_reset_confirm(unsigned long mtime, int index); +#else +#define s3c2410wdt_set_emergency_stop(a) (-1) +#define s3c2410wdt_set_emergency_reset(a, b) do { } while(0) +#define s3c2410wdt_keepalive_emergency(a, b) do { } while(0) +#define s3c2410wdt_reset_confirm(a, b) do { } while(0) +#endif + +#endif + + diff --git a/include/soc/samsung/exynos-sdm.h b/include/soc/samsung/exynos-sdm.h new file mode 100644 index 000000000000..86d2696a192b --- /dev/null +++ b/include/soc/samsung/exynos-sdm.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS - Security Dump Manager support + * + * 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. + */ + +#ifndef EXYNOS_SDM_H +#define EXYNOS_SDM_H + +#ifdef CONFIG_EXYNOS_SDM +int exynos_sdm_dump_secure_region(void); +int exynos_sdm_flush_secdram(void); +#else +#define exynos_sdm_dump_secure_region() do { } while(0) +#define exynos_sdm_flush_secdram() do { } while(0) +#endif + +#endif /* EXYNOS_SDM_H */ diff --git a/kernel/irq/handle.c b/kernel/irq/handle.c index 79f987b942b8..ab75b68f6dd7 100644 --- a/kernel/irq/handle.c +++ b/kernel/irq/handle.c @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -144,7 +145,9 @@ irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags irqreturn_t res; trace_irq_handler_entry(irq, action); + dbg_snapshot_irq(irq, (void *)action->handler, (int)irqs_disabled(), DSS_FLAG_IN); res = action->handler(irq, action->dev_id); + dbg_snapshot_irq(irq, (void *)action->handler, (int)irqs_disabled(), DSS_FLAG_OUT); trace_irq_handler_exit(irq, action, res); if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n", diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 82afb7ed369f..a9e4f33bba4e 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "internals.h" @@ -623,9 +624,11 @@ int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq, bool lookup, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); + unsigned long long start_time; unsigned int irq = hwirq; int ret = 0; + dbg_snapshot_irq_exit_var(start_time); irq_enter(); #ifdef CONFIG_IRQ_DOMAIN @@ -645,6 +648,7 @@ int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq, } irq_exit(); + dbg_snapshot_irq_exit(irq, start_time); set_irq_regs(old_regs); return ret; } diff --git a/kernel/panic.c b/kernel/panic.c index bdd18afa19a4..717e8be75ab8 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -27,6 +27,7 @@ #include #include #include +#include #define PANIC_TIMER_STEP 100 #define PANIC_BLINK_SPD 18 @@ -164,8 +165,9 @@ void panic(const char *fmt, ...) this_cpu = raw_smp_processor_id(); old_cpu = atomic_cmpxchg(&panic_cpu, PANIC_CPU_INVALID, this_cpu); - if (old_cpu != PANIC_CPU_INVALID && old_cpu != this_cpu) + if (old_cpu != PANIC_CPU_INVALID && old_cpu != this_cpu) { panic_smp_self_stop(); + } console_verbose(); bust_spinlocks(1); @@ -173,6 +175,9 @@ void panic(const char *fmt, ...) vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); pr_emerg("Kernel panic - not syncing: %s\n", buf); + + dbg_snapshot_prepare_panic(); + dbg_snapshot_dump_panic(buf, (size_t)strnlen(buf, sizeof(buf))); #ifdef CONFIG_DEBUG_BUGVERBOSE /* * Avoid nested stack-dumping if a panic occurs during oops processing @@ -180,6 +185,7 @@ void panic(const char *fmt, ...) if (!test_taint(TAINT_DIE) && oops_in_progress <= 1) dump_stack(); #endif + //sysrq_sched_debug_show(); /* * If we have crashed and we have a crash kernel loaded let it handle @@ -218,6 +224,8 @@ void panic(const char *fmt, ...) printk_safe_flush_on_panic(); kmsg_dump(KMSG_DUMP_PANIC); + dbg_snapshot_post_panic(); + /* * If you doubt kdump always works fine in any situation, * "crash_kexec_post_notifiers" offers you a chance to run diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index c23ba5e402b0..69bd1abbc9dc 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -586,7 +586,7 @@ static size_t print_process(const struct printk_log *msg, char *buf) #endif module_param_named(process, printk_process, bool, S_IRUGO | S_IWUSR); -#ifdef CONFIG_EXYNOS_SNAPSHOT +#ifdef CONFIG_DEBUG_SNAPSHOT static size_t hook_size; static char hook_text[LOG_LINE_MAX + PREFIX_MAX]; static void (*func_hook_logbuf)(const char *buf, size_t size); @@ -710,7 +710,7 @@ static int log_store(int facility, int level, msg->in_interrupt = in_interrupt() ? 1 : 0; } #endif -#ifdef CONFIG_EXYNOS_SNAPSHOT +#ifdef CONFIG_DEBUG_SNAPSHOT if (func_hook_logbuf) { hook_size = msg_print_text(msg, true, hook_text, LOG_LINE_MAX + PREFIX_MAX); diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index 3e3650e94ae6..5dc63c20ae4c 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -58,6 +58,7 @@ #include #include #include +#include #include "tree.h" #include "rcu.h" @@ -1458,6 +1459,8 @@ static void print_other_cpu_stall(struct rcu_state *rsp, unsigned long gpnum) * See Documentation/RCU/stallwarn.txt for info on how to debug * RCU CPU stall warnings. */ + + dbg_snapshot_printkl((size_t)rsp->name, (size_t)rsp); pr_err("INFO: %s detected stalls on CPUs/tasks:", rsp->name); print_cpu_stall_info_begin(); @@ -1526,6 +1529,8 @@ static void print_cpu_stall(struct rcu_state *rsp) * See Documentation/RCU/stallwarn.txt for info on how to debug * RCU CPU stall warnings. */ + + dbg_snapshot_printkl((size_t)rsp->name, (size_t)rsp); pr_err("INFO: %s self-detected stall on CPU", rsp->name); print_cpu_stall_info_begin(); print_cpu_stall_info(rsp, smp_processor_id()); diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 8a4e64f74bf8..ab6d57b537b3 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -3432,6 +3433,7 @@ static void __sched notrace __schedule(bool preempt) rq_unlock_irq(rq, &rf); } + dbg_snapshot_task(cpu, rq->curr); balance_callback(rq); } diff --git a/kernel/softirq.c b/kernel/softirq.c index e89c3b0cff6d..6930f35260dd 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -26,6 +26,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -281,7 +282,9 @@ restart: kstat_incr_softirqs_this_cpu(vec_nr); trace_softirq_entry(vec_nr); + dbg_snapshot_irq(DSS_FLAG_SOFTIRQ, h->action, irqs_disabled(), DSS_FLAG_IN); h->action(h); + dbg_snapshot_irq(DSS_FLAG_SOFTIRQ, h->action, irqs_disabled(), DSS_FLAG_OUT); trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n", @@ -506,7 +509,11 @@ static __latent_entropy void tasklet_action(struct softirq_action *a) if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) BUG(); + dbg_snapshot_irq(DSS_FLAG_SOFTIRQ_TASKLET, + t->func, irqs_disabled(), DSS_FLAG_IN); t->func(t->data); + dbg_snapshot_irq(DSS_FLAG_SOFTIRQ_TASKLET, + t->func, irqs_disabled(), DSS_FLAG_OUT); tasklet_unlock(t); continue; } @@ -542,7 +549,11 @@ static __latent_entropy void tasklet_hi_action(struct softirq_action *a) if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) BUG(); + dbg_snapshot_irq(DSS_FLAG_SOFTIRQ_HI_TASKLET, + t->func, irqs_disabled(), DSS_FLAG_IN); t->func(t->data); + dbg_snapshot_irq(DSS_FLAG_SOFTIRQ_HI_TASKLET, + t->func, irqs_disabled(), DSS_FLAG_OUT); tasklet_unlock(t); continue; } diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 4237e0744e26..3d2baafdbac6 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "tick-internal.h" @@ -253,6 +254,7 @@ static int clockevents_program_min_delta(struct clock_event_device *dev) dev->retries++; clc = ((unsigned long long) delta * dev->mult) >> dev->shift; + dbg_snapshot_clockevent(clc, delta, &dev->next_event); if (dev->set_next_event((unsigned long) clc, dev) == 0) return 0; @@ -290,6 +292,7 @@ static int clockevents_program_min_delta(struct clock_event_device *dev) dev->retries++; clc = ((unsigned long long) delta * dev->mult) >> dev->shift; + dbg_snapshot_clockevent(clc, delta, &dev->next_event); return dev->set_next_event((unsigned long) clc, dev); } @@ -336,6 +339,7 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, delta = max(delta, (int64_t) dev->min_delta_ns); clc = ((unsigned long long) delta * dev->mult) >> dev->shift; + dbg_snapshot_clockevent(clc, delta, &dev->next_event); rc = dev->set_next_event((unsigned long) clc, dev); return (rc && force) ? clockevents_program_min_delta(dev) : rc; diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index d00e85ac10d6..4cefe0c2ee0d 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -1217,7 +1218,9 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, */ raw_spin_unlock(&cpu_base->lock); trace_hrtimer_expire_entry(timer, now); + dbg_snapshot_hrtimer(timer, now, fn, DSS_FLAG_IN); restart = fn(timer); + dbg_snapshot_hrtimer(timer, now, fn, DSS_FLAG_OUT); trace_hrtimer_expire_exit(timer); raw_spin_lock(&cpu_base->lock); diff --git a/kernel/time/timer.c b/kernel/time/timer.c index 9fe525f410bf..a16d8e940858 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -1276,7 +1277,9 @@ static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long), lock_map_acquire(&lockdep_map); trace_timer_expire_entry(timer); + dbg_snapshot_irq(DSS_FLAG_CALL_TIMER_FN, fn, irqs_disabled(), DSS_FLAG_IN); fn(data); + dbg_snapshot_irq(DSS_FLAG_CALL_TIMER_FN, fn, irqs_disabled(), DSS_FLAG_OUT); trace_timer_expire_exit(timer); lock_map_release(&lockdep_map); diff --git a/kernel/workqueue.c b/kernel/workqueue.c index d0c6b50792c8..60b5836a2bc5 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -49,6 +49,7 @@ #include #include #include +#include #include "workqueue_internal.h" @@ -2111,7 +2112,9 @@ __acquires(&pool->lock) */ lockdep_invariant_state(true); trace_workqueue_execute_start(work); + dbg_snapshot_work(worker, worker->task, worker->current_func, DSS_FLAG_IN); worker->current_func(work); + dbg_snapshot_work(worker, worker->task, worker->current_func, DSS_FLAG_OUT); /* * While we must be careful to not use "work" after this, the trace * point will only record its address. diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index fc3c8906d2be..9007855d536a 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1990,3 +1990,109 @@ config IO_STRICT_DEVMEM if the driver using a given range cannot be disabled. If in doubt, say Y. + +menuconfig DEBUG_SNAPSHOT + bool "Debug SnapShot Support" + default n + +config DEBUG_SNAPSHOT_CALLSTACK + int "shown callstack level" + depends on DEBUG_SNAPSHOT + range 1 4 + default 4 + +config DEBUG_SNAPSHOT_IRQ_EXIT + bool "Enable debugging of interrupt exit event by kevent" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_IRQ_EXIT_THRESHOLD + int "threshold of detection(microsecond)" + depends on DEBUG_SNAPSHOT_IRQ_EXIT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + range 0 1000000 + default 0 + +config DEBUG_SNAPSHOT_IRQ_DISABLED + bool "Enable debugging of interrupt disable event by kevent(EXPERIMENTAL)" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default n + +config DEBUG_SNAPSHOT_SPINLOCK + bool "Enable debugging of spinlock event by kevent(EXPERIMENTAL)" + depends on DEBUG_SNAPSHOT && DEBUG_SPINLOCK && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_CLK + bool "Enable debugging of clock event by kevent(EXPERIMENTAL)" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_PMU + bool "Enable debugging of pmu event by kevent(EXPERIMENTAL)" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_FREQ + bool "Enable debugging of frequence event by kevent(EXPERIMENTAL)" + depends on DEBUG_SNAPSHOT && PM_DEVFREQ && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_DM + bool "Enable debugging of dvfs manager event by kevent(EXPERIMENTAL)" + depends on DEBUG_SNAPSHOT && EXYNOS_DVFS_MANAGER && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_HRTIMER + bool "Enable debugging of hrtimer by kevent" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_REG + bool "Enable debugging of accessing register by kevent" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default n + +config DEBUG_SNAPSHOT_REGULATOR + bool "Enable debugging of regulator and pmic driver" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_ACPM + bool "Enable debugging of acpm framework" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_THERMAL + bool "Enable debugging of thermal driver" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE && EXYNOS_THERMAL + default y + +config DEBUG_SNAPSHOT_I2C + bool "Enable debugging of i2c driver" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_SPI + bool "Enable debugging of spi driver" + depends on DEBUG_SNAPSHOT && !DEBUG_SNAPSHOT_MINIMIZED_MODE + default y + +config DEBUG_SNAPSHOT_PANIC_REBOOT + bool "Enable forced reboot after panic for ramdump" + depends on DEBUG_SNAPSHOT + default y + +config DEBUG_SNAPSHOT_WATCHDOG_RESET + bool "Support watchdog reset when hardlockup detect" + depends on DEBUG_SNAPSHOT && DEBUG_SNAPSHOT_PANIC_REBOOT + default y + +config DEBUG_SNAPSHOT_CRASH_KEY + bool "Support Crash Key to artificial panic for debugging" + depends on DEBUG_SNAPSHOT && KEYBOARD_GPIO + default y + +config DEBUG_SNAPSHOT_MINIMIZED_MODE + bool "Support minimized feature configuration" + depends on DEBUG_SNAPSHOT + default n diff --git a/lib/Makefile b/lib/Makefile index b8f2c16fccaa..f3649ee13ea1 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -248,3 +248,6 @@ UBSAN_SANITIZE_ubsan.o := n obj-$(CONFIG_SBITMAP) += sbitmap.o obj-$(CONFIG_PARMAN) += parman.o + +obj-$(CONFIG_DEBUG_SNAPSHOT) += debug-snapshot.o debug-snapshot-log.o debug-snapshot-utils.o \ + debug-snapshot-pstore.o debug-snapshot-sysfs.o debug-snapshot-helper.o diff --git a/lib/debug-snapshot-helper.c b/lib/debug-snapshot-helper.c new file mode 100644 index 000000000000..89e665b635f8 --- /dev/null +++ b/lib/debug-snapshot-helper.c @@ -0,0 +1,400 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "debug-snapshot-local.h" +#include + +static void dbg_snapshot_soc_dummy_func(void *dummy) {return;} +static int dbg_snapshot_soc_dummy_func_int(void *dummy) {return 0;} +static int dbg_snapshot_soc_dummy_func_smc(unsigned long dummy1, + unsigned long dummy2, + unsigned long dummy3, + unsigned long dummy4) {return 0;} + +static struct dbg_snapshot_helper_ops dss_soc_dummy_ops = { + .soc_early_panic = dbg_snapshot_soc_dummy_func, + .soc_prepare_panic_entry = dbg_snapshot_soc_dummy_func, + .soc_prepare_panic_exit = dbg_snapshot_soc_dummy_func, + .soc_save_context_entry = dbg_snapshot_soc_dummy_func, + .soc_save_context_exit = dbg_snapshot_soc_dummy_func, + .soc_post_panic_entry = dbg_snapshot_soc_dummy_func, + .soc_post_panic_exit = dbg_snapshot_soc_dummy_func, + .soc_start_watchdog = dbg_snapshot_soc_dummy_func, + .soc_expire_watchdog = dbg_snapshot_soc_dummy_func, + .soc_stop_watchdog = dbg_snapshot_soc_dummy_func, + .soc_kick_watchdog = dbg_snapshot_soc_dummy_func, + .soc_is_power_cpu = dbg_snapshot_soc_dummy_func_int, + .soc_smc_call = dbg_snapshot_soc_dummy_func_smc, +}; + +struct dbg_snapshot_helper_ops *dss_soc_ops; + +void __iomem *dbg_snapshot_get_base_vaddr(void) +{ + return (void __iomem *)(dss_base.vaddr); +} + +void __iomem *dbg_snapshot_get_base_paddr(void) +{ + return (void __iomem *)(dss_base.paddr); +} + +static void dbg_snapshot_set_core_power_stat(unsigned int val, unsigned cpu) +{ + if (dbg_snapshot_get_enable("header")) + __raw_writel(val, (dbg_snapshot_get_base_vaddr() + + DSS_OFFSET_CORE_POWER_STAT + cpu * 4)); +} + +unsigned int dbg_snapshot_get_core_panic_stat(unsigned cpu) +{ + if (dbg_snapshot_get_enable("header")) + return __raw_readl(dbg_snapshot_get_base_vaddr() + + DSS_OFFSET_PANIC_STAT + cpu * 4); + else + return 0; +} + +void dbg_snapshot_set_core_panic_stat(unsigned int val, unsigned cpu) +{ + if (dbg_snapshot_get_enable("header")) + __raw_writel(val, (dbg_snapshot_get_base_vaddr() + + DSS_OFFSET_PANIC_STAT + cpu * 4)); +} + +static void dbg_snapshot_report_reason(unsigned int val) +{ + if (dbg_snapshot_get_enable("header")) + __raw_writel(val, dbg_snapshot_get_base_vaddr() + DSS_OFFSET_EMERGENCY_REASON); +} + +void dbg_snapshot_scratch_reg(unsigned int val) +{ + if (dbg_snapshot_get_enable("header")) + __raw_writel(val, dbg_snapshot_get_base_vaddr() + DSS_OFFSET_SCRATCH); +} + +bool dbg_snapshot_is_scratch(void) +{ + return __raw_readl(dbg_snapshot_get_base_vaddr() + + DSS_OFFSET_SCRATCH) == DSS_SIGN_SCRATCH; +} + +unsigned long dbg_snapshot_get_last_pc_paddr(void) +{ + /* + * Basically we want to save the pc value to non-cacheable region + * if ESS is enabled. But we should also consider cases that are not so. + */ + + if (dbg_snapshot_get_enable("header")) + return ((unsigned long)dbg_snapshot_get_base_paddr() + DSS_OFFSET_CORE_LAST_PC); + else + return virt_to_phys((void *)dss_desc.hardlockup_core_pc); +} + +unsigned long dbg_snapshot_get_last_pc(unsigned int cpu) +{ + if (dbg_snapshot_get_enable("header")) + return __raw_readq(dbg_snapshot_get_base_vaddr() + + DSS_OFFSET_CORE_LAST_PC + cpu * 8); + else + return dss_desc.hardlockup_core_pc[cpu]; +} + +unsigned long dbg_snapshot_get_spare_vaddr(unsigned int offset) +{ + return (unsigned long)(dbg_snapshot_get_base_vaddr() + + DSS_OFFSET_SPARE_BASE + offset); +} + +unsigned long dbg_snapshot_get_spare_paddr(unsigned int offset) +{ + unsigned long base_vaddr = 0; + unsigned long base_paddr = (unsigned long)dbg_snapshot_get_base_paddr(); + + if (base_paddr) + base_vaddr = (unsigned long)(base_paddr + + DSS_OFFSET_SPARE_BASE + offset); + + return base_vaddr; +} + +unsigned int dbg_snapshot_get_item_size(char* name) +{ + unsigned long i; + + for (i = 0; i < dss_desc.log_enable_cnt; i++) { + if (!strncmp(dss_items[i].name, name, strlen(name))) + return dss_items[i].entry.size; + } + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_get_item_size); + +unsigned long dbg_snapshot_get_item_vaddr(char *name) +{ + unsigned long i; + + for (i = 0; i < dss_desc.log_enable_cnt; i++) { + if (!strncmp(dss_items[i].name, name, strlen(name))) + return dss_items[i].entry.vaddr; + } + return 0; +} + +unsigned int dbg_snapshot_get_item_paddr(char* name) +{ + unsigned long i; + + for (i = 0; i < dss_desc.log_enable_cnt; i++) { + if (!strncmp(dss_items[i].name, name, strlen(name))) + return dss_items[i].entry.paddr; + } + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_get_item_paddr); + +int dbg_snapshot_get_hardlockup(void) +{ + return dss_desc.hardlockup_detected; +} +EXPORT_SYMBOL(dbg_snapshot_get_hardlockup); + +int dbg_snapshot_set_hardlockup(int val) +{ + unsigned long flags; + + if (unlikely(!dss_base.enabled)) + return 0; + + raw_spin_lock_irqsave(&dss_desc.ctrl_lock, flags); + dss_desc.hardlockup_detected = val; + raw_spin_unlock_irqrestore(&dss_desc.ctrl_lock, flags); + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_set_hardlockup); + +int dbg_snapshot_early_panic(void) +{ + dss_soc_ops->soc_early_panic(NULL); + return 0; +} + +int dbg_snapshot_prepare_panic(void) +{ + unsigned long cpu; + + if (unlikely(!dss_base.enabled)) + return 0; + /* + * kick watchdog to prevent unexpected reset during panic sequence + * and it prevents the hang during panic sequence by watchedog + */ + dss_soc_ops->soc_start_watchdog(NULL); + + dss_soc_ops->soc_prepare_panic_entry(NULL); + + /* Again disable log_kevents */ + dbg_snapshot_set_enable("log_kevents", false); + + for_each_possible_cpu(cpu) { + if (dss_soc_ops->soc_is_power_cpu((void *)cpu)) + dbg_snapshot_set_core_power_stat(DSS_SIGN_ALIVE, cpu); + else + dbg_snapshot_set_core_power_stat(DSS_SIGN_DEAD, cpu); + } + dss_soc_ops->soc_prepare_panic_exit(NULL); + + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_prepare_panic); + +int dbg_snapshot_post_panic(void) +{ + if (dss_base.enabled) { + dss_soc_ops->soc_post_panic_entry(NULL); + + dbg_snapshot_recall_hardlockup_core(); +#ifdef CONFIG_DEBUG_SNAPSHOT_PMU + dbg_snapshot_dump_sfr(); +#endif + dbg_snapshot_save_context(NULL); + flush_cache_all(); + dbg_snapshot_print_panic_report(); + + dss_soc_ops->soc_post_panic_exit(NULL); + +#ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT + if (!dss_desc.no_wdt_dev) { +#ifdef CONFIG_DEBUG_SNAPSHOT_WATCHDOG_RESET + if (dss_desc.hardlockup_detected || num_online_cpus() > 1) { + /* for stall cpu */ + while(1) + wfi(); + } +#endif + } +#endif + } +#ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT + arm_pm_restart(0, "panic"); +#endif + goto loop; + /* for stall cpu when not enabling panic reboot */ +loop: + while(1) + wfi(); + + /* Never run this function */ + pr_emerg("debug-snapshot: %s DO NOT RUN this function (CPU:%d)\n", + __func__, raw_smp_processor_id()); + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_post_panic); + +int dbg_snapshot_dump_panic(char *str, size_t len) +{ + if (unlikely(!dss_base.enabled) || + !dbg_snapshot_get_enable("header")) + return 0; + + /* This function is only one which runs in panic funcion */ + if (str && len && len < SZ_1K) + memcpy(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_PANIC_STRING, str, len); + + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_dump_panic); + +int dbg_snapshot_post_reboot(char *cmd) +{ + int cpu; + + if (unlikely(!dss_base.enabled)) + return 0; + + /* clear DSS_SIGN_PANIC when normal reboot */ + for_each_possible_cpu(cpu) { + dbg_snapshot_set_core_panic_stat(DSS_SIGN_RESET, cpu); + } + + dbg_snapshot_report_reason(DSS_SIGN_NORMAL_REBOOT); + + if (!cmd) + dbg_snapshot_scratch_reg(DSS_SIGN_RESET); + else if (strcmp((char *)cmd, "bootloader") && strcmp((char *)cmd, "ramdump")) + dbg_snapshot_scratch_reg(DSS_SIGN_RESET); + + pr_emerg("debug-snapshot: normal reboot done\n"); + + dbg_snapshot_save_context(NULL); + flush_cache_all(); + + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_post_reboot); + +static int dbg_snapshot_reboot_handler(struct notifier_block *nb, + unsigned long l, void *p) +{ + if (unlikely(!dss_base.enabled)) + return 0; + + pr_emerg("debug-snapshot: normal reboot starting\n"); + + return 0; +} + +static int dbg_snapshot_panic_handler(struct notifier_block *nb, + unsigned long l, void *buf) +{ + dbg_snapshot_report_reason(DSS_SIGN_PANIC); + if (unlikely(!dss_base.enabled)) + return 0; + +#ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT + local_irq_disable(); + pr_emerg("debug-snapshot: panic - reboot[%s]\n", __func__); +#else + pr_emerg("debug-snapshot: panic - normal[%s]\n", __func__); +#endif + dbg_snapshot_dump_task_info(); + pr_emerg("linux_banner: %s\n", linux_banner); + flush_cache_all(); + return 0; +} + +static struct notifier_block nb_reboot_block = { + .notifier_call = dbg_snapshot_reboot_handler +}; + +static struct notifier_block nb_panic_block = { + .notifier_call = dbg_snapshot_panic_handler, +}; + +void dbg_snapshot_panic_handler_safe(void) +{ + char *cpu_num[SZ_16] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + char text[SZ_32] = "safe panic handler at cpu "; + int cpu = raw_smp_processor_id(); + size_t len; + + if (unlikely(!dss_base.enabled)) + return; + + strncat(text, cpu_num[cpu], 1); + len = strnlen(text, SZ_32); + + dbg_snapshot_report_reason(DSS_SIGN_SAFE_FAULT); + dbg_snapshot_dump_panic(text, len); + dss_soc_ops->soc_expire_watchdog((void *)NULL); +} + +void dbg_snapshot_register_soc_ops(struct dbg_snapshot_helper_ops *ops) +{ + if (ops) + dss_soc_ops = ops; +} + +void __init dbg_snapshot_helper_init(void) +{ + register_reboot_notifier(&nb_reboot_block); + atomic_notifier_chain_register(&panic_notifier_list, &nb_panic_block); + dss_soc_ops = &dss_soc_dummy_ops; + + /* hardlockup_detector function should be called before secondary booting */ + dbg_snapshot_soc_helper_init(); +} diff --git a/lib/debug-snapshot-local.h b/lib/debug-snapshot-local.h new file mode 100644 index 000000000000..186a74429de1 --- /dev/null +++ b/lib/debug-snapshot-local.h @@ -0,0 +1,394 @@ +/* + * 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. + */ + +#ifndef DEBUG_SNAPSHOT_LOCAL_H +#define DEBUG_SNAPSHOT_LOCAL_H +#include +#include +#include +#include + +extern void (*arm_pm_restart)(char str, const char *cmd); + +extern void dbg_snapshot_log_idx_init(void); +extern void dbg_snapshot_utils_init(void); +extern void dbg_snapshot_helper_init(void); +extern void __iomem *dbg_snapshot_get_base_vaddr(void); +extern void __iomem *dbg_snapshot_get_base_paddr(void); +extern void dbg_snapshot_scratch_reg(unsigned int val); +extern void dbg_snapshot_print_panic_report(void); +extern void dbg_snapshot_dump_task_info(void); + +extern unsigned int dbg_snapshot_get_core_panic_stat(unsigned cpu); +extern void dbg_snapshot_set_core_panic_stat(unsigned int val, unsigned cpu); +extern void dbg_snapshot_recall_hardlockup_core(void); + +extern struct dbg_snapshot_helper_ops *dss_soc_ops; + +/* Size domain */ +#define DSS_KEEP_HEADER_SZ (SZ_256 * 3) +#define DSS_HEADER_SZ SZ_4K +#define DSS_MMU_REG_SZ SZ_4K +#define DSS_CORE_REG_SZ SZ_4K +#define DSS_DBGC_LOG_SZ SZ_4K +#define DSS_HEADER_TOTAL_SZ (DSS_HEADER_SZ + DSS_MMU_REG_SZ + DSS_CORE_REG_SZ + DSS_DBGC_LOG_SZ) +#define DSS_SPARE_SZ (DSS_HEADER_SIZE - DSS_HEADER_TOTAL_SZ) + +/* Length domain */ +#define DSS_LOG_STRING_LENGTH SZ_128 +#define DSS_MMU_REG_OFFSET SZ_512 +#define DSS_CORE_REG_OFFSET SZ_512 +#define DSS_LOG_MAX_NUM SZ_1K +#define DSS_API_MAX_NUM SZ_2K +#define DSS_EX_MAX_NUM SZ_8 +#define DSS_IN_MAX_NUM SZ_8 +#define DSS_CALLSTACK_MAX_NUM 4 +#define DSS_ITERATION 5 +#define DSS_NR_CPUS NR_CPUS +#define DSS_ITEM_MAX_NUM 10 + +/* Sign domain */ +#define DSS_SIGN_RESET 0x0 +#define DSS_SIGN_RESERVED 0x1 +#define DSS_SIGN_SCRATCH 0xD +#define DSS_SIGN_ALIVE 0xFACE +#define DSS_SIGN_DEAD 0xDEAD +#define DSS_SIGN_PANIC 0xBABA +#define DSS_SIGN_SAFE_FAULT 0xFAFA +#define DSS_SIGN_NORMAL_REBOOT 0xCAFE +#define DSS_SIGN_FORCE_REBOOT 0xDAFE +#define DSS_SIGN_LOCKUP 0xDEADBEEF + +/* Specific Address Information */ +#define DSS_FIXED_VIRT_BASE (VMALLOC_START + 0xF6000000) +#define DSS_OFFSET_SCRATCH (0x100) +#define DSS_OFFSET_LAST_LOGBUF (0x200) +#define DSS_OFFSET_EMERGENCY_REASON (0x300) +#define DSS_OFFSET_CORE_POWER_STAT (0x400) +#define DSS_OFFSET_PANIC_STAT (0x500) +#define DSS_OFFSET_CORE_LAST_PC (0x600) + +/* S5P_VA_SS_BASE + 0xC00 -- 0xFFF is reserved */ +#define DSS_OFFSET_PANIC_STRING (0xC00) +#define DSS_OFFSET_SPARE_BASE (DSS_HEADER_TOTAL_SZ) + +typedef int (*dss_initcall_t)(const struct device_node *); + +struct dbg_snapshot_base { + size_t size; + size_t vaddr; + size_t paddr; + unsigned int persist; + unsigned int enabled; +}; + +struct dbg_snapshot_item { + char *name; + struct dbg_snapshot_base entry; + unsigned char *head_ptr; + unsigned char *curr_ptr; + unsigned long long time; + struct vm_struct vm; +}; + +#ifdef CONFIG_ARM64 +struct dbg_snapshot_mmu_reg { + long SCTLR_EL1; + long TTBR0_EL1; + long TTBR1_EL1; + long TCR_EL1; + long ESR_EL1; + long FAR_EL1; + long CONTEXTIDR_EL1; + long TPIDR_EL0; + long TPIDRRO_EL0; + long TPIDR_EL1; + long MAIR_EL1; + long ELR_EL1; + long SP_EL0; +}; + +#else +struct dbg_snapshot_mmu_reg { + int SCTLR; + int TTBR0; + int TTBR1; + int TTBCR; + int DACR; + int DFSR; + int DFAR; + int IFSR; + int IFAR; + int DAFSR; + int IAFSR; + int PMRRR; + int NMRRR; + int FCSEPID; + int CONTEXT; + int URWTPID; + int UROTPID; + int POTPIDR; +}; +#endif + +struct dbg_snapshot_sfrdump { + char *name; + void __iomem *reg; + unsigned int phy_reg; + unsigned int num; + struct device_node *node; + struct list_head list; + bool pwr_mode; +}; + +struct dbg_snapshot_desc { + struct list_head sfrdump_list; + raw_spinlock_t ctrl_lock; + raw_spinlock_t nmi_lock; + unsigned int header_num; + unsigned int kevents_num; + unsigned int log_kernel_num; + unsigned int log_platform_num; + unsigned int log_sfr_num; + unsigned int log_pstore_num; + unsigned int log_etm_num; + unsigned int log_enable_cnt; + + unsigned int callstack; + unsigned long hardlockup_core_mask; + unsigned long hardlockup_core_pc[DSS_NR_CPUS]; + int multistage_wdt_irq; + int hardlockup_detected; + int allcorelockup_detected; + int no_wdt_dev; +}; + +struct dbg_snapshot_log { + struct __task_log { + unsigned long long time; + unsigned long sp; + struct task_struct *task; + char task_comm[TASK_COMM_LEN]; + } task[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; + + struct __work_log { + unsigned long long time; + unsigned long sp; + struct worker *worker; + char task_comm[TASK_COMM_LEN]; + work_func_t fn; + int en; + } work[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; + + struct __cpuidle_log { + unsigned long long time; + unsigned long sp; + char *modes; + unsigned state; + u32 num_online_cpus; + int delta; + int en; + } cpuidle[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; + + struct __suspend_log { + unsigned long long time; + unsigned long sp; + void *fn; + struct device *dev; + int en; + int core; + } suspend[DSS_LOG_MAX_NUM * 4]; + + struct __irq_log { + unsigned long long time; + unsigned long sp; + int irq; + void *fn; + unsigned int preempt; + unsigned int val; + int en; + } irq[DSS_NR_CPUS][DSS_LOG_MAX_NUM * 2]; + +#ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_EXIT + struct __irq_exit_log { + unsigned long long time; + unsigned long sp; + unsigned long long end_time; + unsigned long long latency; + int irq; + } irq_exit[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_SPINLOCK + struct __spinlock_log { + unsigned long long time; + unsigned long sp; + unsigned long long jiffies; + raw_spinlock_t *lock; +#ifdef CONFIG_DEBUG_SPINLOCK + u16 next; + u16 owner; +#endif + int en; + void *caller[DSS_CALLSTACK_MAX_NUM]; + } spinlock[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_DISABLED + struct __irqs_disabled_log { + unsigned long long time; + unsigned long index; + struct task_struct *task; + char *task_comm; + void *caller[DSS_CALLSTACK_MAX_NUM]; + } irqs_disabled[DSS_NR_CPUS][SZ_32]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_CLK + struct __clk_log { + unsigned long long time; + struct clk_hw *clk; + const char* f_name; + int mode; + unsigned long arg; + } clk[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_PMU + struct __pmu_log { + unsigned long long time; + unsigned int id; + const char* f_name; + int mode; + } pmu[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_FREQ + struct __freq_log { + unsigned long long time; + int cpu; + char* freq_name; + unsigned long old_freq; + unsigned long target_freq; + int en; + } freq[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_DM + struct __dm_log { + unsigned long long time; + int cpu; + int dm_num; + unsigned long min_freq; + unsigned long max_freq; + s32 wait_dmt; + s32 do_dmt; + } dm[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_REG + struct __reg_log { + unsigned long long time; + int read; + size_t val; + size_t reg; + int en; + void *caller[DSS_CALLSTACK_MAX_NUM]; + } reg[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_HRTIMER + struct __hrtimer_log { + unsigned long long time; + unsigned long long now; + struct hrtimer *timer; + void *fn; + int en; + } hrtimers[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_REGULATOR + struct __regulator_log { + unsigned long long time; + unsigned long long acpm_time; + int cpu; + char name[SZ_16]; + unsigned int reg; + unsigned int voltage; + unsigned int raw_volt; + int en; + } regulator[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_THERMAL + struct __thermal_log { + unsigned long long time; + int cpu; + struct exynos_tmu_platform_data *data; + unsigned int temp; + char* cooling_device; + unsigned int cooling_state; + } thermal[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_ACPM + struct __acpm_log { + unsigned long long time; + unsigned long long acpm_time; + char log[9]; + unsigned int data; + } acpm[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_I2C + struct __i2c_log { + unsigned long long time; + int cpu; + struct i2c_adapter *adap; + struct i2c_msg *msgs; + int num; + int en; + } i2c[DSS_LOG_MAX_NUM]; +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_SPI + struct __spi_log { + unsigned long long time; + int cpu; + struct spi_controller *ctlr; + struct spi_message *cur_msg; + int en; + } spi[DSS_LOG_MAX_NUM]; +#endif + +#ifndef CONFIG_DEBUG_SNAPSHOT_MINIMIZED_MODE + struct __clockevent_log { + unsigned long long time; + unsigned long long mct_cycle; + int64_t delta_ns; + ktime_t next_event; + void *caller[DSS_CALLSTACK_MAX_NUM]; + } clockevent[DSS_NR_CPUS][DSS_LOG_MAX_NUM]; + + struct __printkl_log { + unsigned long long time; + int cpu; + size_t msg; + size_t val; + void *caller[DSS_CALLSTACK_MAX_NUM]; + } printkl[DSS_API_MAX_NUM]; + + struct __printk_log { + unsigned long long time; + int cpu; + char log[DSS_LOG_STRING_LENGTH]; + void *caller[DSS_CALLSTACK_MAX_NUM]; + } printk[DSS_API_MAX_NUM]; +#endif +}; + +extern struct dbg_snapshot_base dss_base; +extern struct dbg_snapshot_log *dss_log; +extern struct dbg_snapshot_desc dss_desc; +extern struct dbg_snapshot_item dss_items[]; +extern int dbg_snapshot_log_size; +#endif diff --git a/lib/debug-snapshot-log.c b/lib/debug-snapshot-log.c new file mode 100644 index 000000000000..845090ad82ec --- /dev/null +++ b/lib/debug-snapshot-log.c @@ -0,0 +1,1423 @@ +/* + * 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 + +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_IRQ_EXIT + atomic_t irq_exit_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 +#ifndef CONFIG_DEBUG_SNAPSHOT_MINIMIZED_MODE + atomic_t clockevent_log_idx[DSS_NR_CPUS]; + 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_IRQ_EXIT +int dss_irqexit_exlist[DSS_EX_MAX_NUM] = { +/* interrupt number ex) 152, 153, 154, */ + -1, +}; + +unsigned int dss_irqexit_threshold = + CONFIG_DEBUG_SNAPSHOT_IRQ_EXIT_THRESHOLD; +#endif + +#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[] = { + "LIT", "MID", "BIG", "INT", "MIF", "ISP", "DISP", "INTCAM", "AUD", "IVA", "SCORE", "FSYS0", "MFC", "NPU", +}; +#endif + +/* Internal interface variable */ +static struct dbg_snapshot_log_idx dss_idx; +static struct dbg_snapshot_lastinfo dss_lastinfo; + +void __init dbg_snapshot_log_idx_init(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 + 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); +#ifndef CONFIG_DEBUG_SNAPSHOT_MINIMIZED_MODE + atomic_set(&(dss_idx.clockevent_log_idx[i]), -1); +#endif + 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_IRQ_EXIT + atomic_set(&(dss_idx.irq_exit_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 + } +} + +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, preempt, val; + + 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; + preempt = dss_log->irq[cpu][idx].preempt; + val = dss_log->irq[cpu][idx].val; + en = dss_log->irq[cpu][idx].en; + + *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] irq:%6d, irq_fn:%32s, " + "preempt:%6d, val:%6d, %3s\n", + (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, + irq, irq_fn, preempt, val, + en == DSS_FLAG_IN ? "IN" : "OUT"); + break; + } +#ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_EXIT + case DSS_FLAG_IRQ_EXIT: + { + unsigned long end_time, latency; + int irq; + + array_size = ARRAY_SIZE(dss_log->irq_exit[0]) - 1; + if (!dumper->active) { + idx = (atomic_read(&dss_idx.irq_exit_log_idx[0]) + 1) & array_size; + dumper->init_idx = idx; + dumper->active = true; + } + ts = dss_log->irq_exit[cpu][idx].time; + rem_nsec = do_div(ts, NSEC_PER_SEC); + + end_time = dss_log->irq_exit[cpu][idx].end_time; + latency = dss_log->irq_exit[cpu][idx].latency; + irq = dss_log->irq_exit[cpu][idx].irq; + + *len = snprintf(line, size, "[%8lu.%09lu][%04d:CPU%u] irq:%6d, " + "latency:%16zu, end_time:%16zu\n", + (unsigned long)ts, rem_nsec / NSEC_PER_USEC, idx, cpu, + irq, latency, end_time); + break; + } +#endif +#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; + 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(void *fn, void *dev, 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.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; + 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, unsigned int val, 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 i; + + for (i = 0; i < ARRAY_SIZE(dss_irqlog_exlist); i++) { + if (irq == dss_irqlog_exlist[i]) { + pure_arch_local_irq_restore(flags); + return; + } + } + i = atomic_inc_return(&dss_idx.irq_log_idx[cpu]) & + (ARRAY_SIZE(dss_log->irq[0]) - 1); + + dss_log->irq[cpu][i].time = cpu_clock(cpu); + 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].preempt = preempt_count(); + dss_log->irq[cpu][i].val = val; + dss_log->irq[cpu][i].en = en; + } + pure_arch_local_irq_restore(flags); +} + +#ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_EXIT +void dbg_snapshot_irq_exit(unsigned int irq, unsigned long long start_time) +{ + struct dbg_snapshot_item *item = &dss_items[dss_desc.kevents_num]; + unsigned long i; + + if (unlikely(!dss_base.enabled || !item->entry.enabled)) + return; + + for (i = 0; i < ARRAY_SIZE(dss_irqexit_exlist); i++) + if (irq == dss_irqexit_exlist[i]) + return; + { + int cpu = raw_smp_processor_id(); + unsigned long long time, latency; + + i = atomic_inc_return(&dss_idx.irq_exit_log_idx[cpu]) & + (ARRAY_SIZE(dss_log->irq_exit[0]) - 1); + + time = cpu_clock(cpu); + latency = time - start_time; + + if (unlikely(latency > + (dss_irqexit_threshold * 1000))) { + dss_log->irq_exit[cpu][i].latency = latency; + dss_log->irq_exit[cpu][i].sp = (unsigned long) current_stack_pointer; + dss_log->irq_exit[cpu][i].end_time = time; + dss_log->irq_exit[cpu][i].time = start_time; + dss_log->irq_exit[cpu][i].irq = irq; + } else + atomic_dec(&dss_idx.irq_exit_log_idx[cpu]); + } +} +#endif + +#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 + +#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].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_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 >= 9) + len = 9; + + 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[8] = '\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_clockevent(unsigned long long clc, int64_t delta, void *next_event) +{ + 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.clockevent_log_idx[cpu]) & + (ARRAY_SIZE(dss_log->clockevent[0]) - 1); + + dss_log->clockevent[cpu][i].time = cpu_clock(cpu); + dss_log->clockevent[cpu][i].mct_cycle = clc; + dss_log->clockevent[cpu][i].delta_ns = delta; + dss_log->clockevent[cpu][i].next_event = *((ktime_t *)next_event); + + for (j = 0; j < dss_desc.callstack; j++) { + dss_log->clockevent[cpu][i].caller[j] = + (void *)((size_t)return_address(j + 1)); + } + } +} + +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 diff --git a/lib/debug-snapshot-pstore.c b/lib/debug-snapshot-pstore.c new file mode 100644 index 000000000000..fdaee9805a55 --- /dev/null +++ b/lib/debug-snapshot-pstore.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Exynos-SnapShot debugging framework for Exynos SoC + * + * Author: Hosung 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 + +/* This defines are for PSTORE */ +#define DSS_LOGGER_LEVEL_HEADER (1) +#define DSS_LOGGER_LEVEL_PREFIX (2) +#define DSS_LOGGER_LEVEL_TEXT (3) +#define DSS_LOGGER_LEVEL_MAX (4) +#define DSS_LOGGER_SKIP_COUNT (4) +#define DSS_LOGGER_STRING_PAD (1) +#define DSS_LOGGER_HEADER_SIZE (68) + +#define DSS_LOG_ID_MAIN (0) +#define DSS_LOG_ID_RADIO (1) +#define DSS_LOG_ID_EVENTS (2) +#define DSS_LOG_ID_SYSTEM (3) +#define DSS_LOG_ID_CRASH (4) +#define DSS_LOG_ID_KERNEL (5) + +typedef struct __attribute__((__packed__)) { + uint8_t magic; + uint16_t len; + uint16_t uid; + uint16_t pid; +} dss_pmsg_log_header_t; + +typedef struct __attribute__((__packed__)) { + unsigned char id; + uint16_t tid; + int32_t tv_sec; + int32_t tv_nsec; +} dss_android_log_header_t; + +typedef struct dss_logger { + uint16_t len; + uint16_t id; + uint16_t pid; + uint16_t tid; + uint16_t uid; + uint16_t level; + int32_t tv_sec; + int32_t tv_nsec; + char msg; + char *buffer; + void (*func_hook_logger)(const char*, const char*, size_t); +} __attribute__((__packed__)) dss_logger; + +static dss_logger logger; + +void register_hook_logger(void (*func)(const char *name, const char *buf, size_t size)) +{ + logger.func_hook_logger = func; + logger.buffer = vmalloc(PAGE_SIZE * 3); + + if (logger.buffer) + pr_info("debug-snapshot: logger buffer alloc address: 0x%p\n", logger.buffer); +} +EXPORT_SYMBOL(register_hook_logger); + +static int dbg_snapshot_combine_pmsg(char *buffer, size_t count, unsigned int level) +{ + char *logbuf = logger.buffer; + + if (!logbuf) + return -ENOMEM; + + switch (level) { + case DSS_LOGGER_LEVEL_HEADER: + { + struct tm tmBuf; + u64 tv_kernel; + unsigned int logbuf_len; + unsigned long rem_nsec; + + if (logger.id == DSS_LOG_ID_EVENTS) + break; + + tv_kernel = local_clock(); + rem_nsec = do_div(tv_kernel, 1000000000); + time_to_tm(logger.tv_sec, 0, &tmBuf); + + logbuf_len = snprintf(logbuf, DSS_LOGGER_HEADER_SIZE, + "\n[%5lu.%06lu][%d:%16s] %02d-%02d %02d:%02d:%02d.%03d %5d %5d ", + (unsigned long)tv_kernel, rem_nsec / 1000, + raw_smp_processor_id(), current->comm, + tmBuf.tm_mon + 1, tmBuf.tm_mday, + tmBuf.tm_hour, tmBuf.tm_min, tmBuf.tm_sec, + logger.tv_nsec / 1000000, logger.pid, logger.tid); + + logger.func_hook_logger("log_platform", logbuf, logbuf_len - 1); + } + break; + case DSS_LOGGER_LEVEL_PREFIX: + { + static const char *kPrioChars = "!.VDIWEFS"; + unsigned char prio = logger.msg; + + if (logger.id == DSS_LOG_ID_EVENTS) + break; + + logbuf[0] = prio < strlen(kPrioChars) ? kPrioChars[prio] : '?'; + logbuf[1] = ' '; + + logger.func_hook_logger("log_platform", logbuf, DSS_LOGGER_LEVEL_PREFIX); + } + break; + case DSS_LOGGER_LEVEL_TEXT: + { + char *eatnl = buffer + count - DSS_LOGGER_STRING_PAD; + + if (logger.id == DSS_LOG_ID_EVENTS) + break; + if (count == DSS_LOGGER_SKIP_COUNT && *eatnl != '\0') + break; + + logger.func_hook_logger("log_platform", buffer, count - 1); + } + break; + default: + break; + } + return 0; +} + +int dbg_snapshot_hook_pmsg(char *buffer, size_t count) +{ + dss_android_log_header_t header; + dss_pmsg_log_header_t pmsg_header; + + if (!logger.buffer) + return -ENOMEM; + + switch (count) { + case sizeof(pmsg_header): + memcpy((void *)&pmsg_header, buffer, count); + if (pmsg_header.magic != 'l') { + dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_TEXT); + } else { + /* save logger data */ + logger.pid = pmsg_header.pid; + logger.uid = pmsg_header.uid; + logger.len = pmsg_header.len; + } + break; + case sizeof(header): + /* save logger data */ + memcpy((void *)&header, buffer, count); + logger.id = header.id; + logger.tid = header.tid; + logger.tv_sec = header.tv_sec; + logger.tv_nsec = header.tv_nsec; + if (logger.id > 7) { + /* write string */ + dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_TEXT); + } else { + /* write header */ + dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_HEADER); + } + break; + case sizeof(unsigned char): + logger.msg = buffer[0]; + /* write char for prefix */ + dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_PREFIX); + break; + default: + /* write string */ + dbg_snapshot_combine_pmsg(buffer, count, DSS_LOGGER_LEVEL_TEXT); + break; + } + + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_hook_pmsg); + +/* + * To support pstore/pmsg/pstore_ram, following is implementation for debug-snapshot + * dss_ramoops platform_device is used by pstore fs. + */ + +static struct ramoops_platform_data dss_ramoops_data = { + .record_size = SZ_512K, + .console_size = SZ_512K, + .ftrace_size = SZ_512K, + .pmsg_size = SZ_512K, + .dump_oops = 1, +}; + +static struct platform_device dss_ramoops = { + .name = "ramoops", + .dev = { + .platform_data = &dss_ramoops_data, + }, +}; + +static int __init dss_pstore_init(void) +{ + if (dbg_snapshot_get_enable("log_pstore")) { + dss_ramoops_data.mem_size = dbg_snapshot_get_item_size("log_pstore"); + dss_ramoops_data.mem_address = dbg_snapshot_get_item_paddr("log_pstore"); + } + return platform_device_register(&dss_ramoops); +} + +static void __exit dss_pstore_exit(void) +{ + platform_device_unregister(&dss_ramoops); +} +module_init(dss_pstore_init); +module_exit(dss_pstore_exit); + +MODULE_DESCRIPTION("Exynos Snapshot pstore module"); +MODULE_LICENSE("GPL"); diff --git a/lib/debug-snapshot-sysfs.c b/lib/debug-snapshot-sysfs.c new file mode 100644 index 000000000000..17c5c09efadb --- /dev/null +++ b/lib/debug-snapshot-sysfs.c @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Exynos-SnapShot debugging framework for Exynos SoC + * + * Author: Hosung 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 + +/* + * sysfs implementation for debug-snapshot + * you can access the sysfs of debug-snapshot to /sys/devices/system/debug_snapshot + * path. + */ +static struct bus_type dss_subsys = { + .name = "debug-snapshot", + .dev_name = "debug-snapshot", +}; + +extern int dss_irqlog_exlist[DSS_EX_MAX_NUM]; +extern int dss_irqexit_exlist[DSS_EX_MAX_NUM]; +extern unsigned int dss_irqexit_threshold; + +static ssize_t dss_enable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct dbg_snapshot_item *item; + unsigned long i; + ssize_t n = 0; + + /* item */ + for (i = 0; i < dss_desc.log_enable_cnt; i++) { + item = &dss_items[i]; + n += scnprintf(buf + n, 24, "%-12s : %sable\n", + item->name, item->entry.enabled ? "en" : "dis"); + } + + /* base */ + n += scnprintf(buf + n, 24, "%-12s : %sable\n", + "base", dss_base.enabled ? "en" : "dis"); + + return n; +} + +static ssize_t dss_enable_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + int en; + char *name; + + name = (char *)kstrndup(buf, count, GFP_KERNEL); + if (!name) + return count; + + name[count - 1] = '\0'; + + en = dbg_snapshot_get_enable(name); + + if (en == -1) + pr_info("echo name > enabled\n"); + else { + if (en) + dbg_snapshot_set_enable(name, false); + else + dbg_snapshot_set_enable(name, true); + } + + kfree(name); + return count; +} + +static ssize_t dss_callstack_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t n = 0; + + n = scnprintf(buf, 24, "callstack depth : %d\n", dss_desc.callstack); + + return n; +} + +static ssize_t dss_callstack_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + unsigned long callstack; + + callstack = simple_strtoul(buf, NULL, 0); + pr_info("callstack depth(min 1, max 4) : %lu\n", callstack); + + if (callstack < 5 && callstack > 0) { + dss_desc.callstack = (unsigned int)callstack; + pr_info("success inserting %lu to callstack value\n", callstack); + } + return count; +} + +static ssize_t dss_irqlog_exlist_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + unsigned long i; + ssize_t n = 0; + + n = scnprintf(buf, 24, "excluded irq number\n"); + + for (i = 0; i < ARRAY_SIZE(dss_irqlog_exlist); i++) { + if (dss_irqlog_exlist[i] == 0) + break; + n += scnprintf(buf + n, 24, "irq num: %-4d\n", dss_irqlog_exlist[i]); + } + return n; +} + +static ssize_t dss_irqlog_exlist_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + unsigned long i; + unsigned long irq; + + irq = simple_strtoul(buf, NULL, 0); + pr_info("irq number : %lu\n", irq); + + for (i = 0; i < ARRAY_SIZE(dss_irqlog_exlist); i++) { + if (dss_irqlog_exlist[i] == 0) + break; + } + + if (i == ARRAY_SIZE(dss_irqlog_exlist)) { + pr_err("list is full\n"); + return count; + } + + if (irq != 0) { + dss_irqlog_exlist[i] = irq; + pr_info("success inserting %lu to list\n", irq); + } + return count; +} + +#ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_EXIT +static ssize_t dss_irqexit_exlist_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + unsigned long i; + ssize_t n = 0; + + n = scnprintf(buf, 36, "Excluded irq number\n"); + for (i = 0; i < ARRAY_SIZE(dss_irqexit_exlist); i++) { + if (dss_irqexit_exlist[i] == 0) + break; + n += scnprintf(buf + n, 24, "IRQ num: %-4d\n", dss_irqexit_exlist[i]); + } + return n; +} + +static ssize_t dss_irqexit_exlist_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + unsigned long i; + unsigned long irq; + + irq = simple_strtoul(buf, NULL, 0); + pr_info("irq number : %lu\n", irq); + + for (i = 0; i < ARRAY_SIZE(dss_irqexit_exlist); i++) { + if (dss_irqexit_exlist[i] == 0) + break; + } + + if (i == ARRAY_SIZE(dss_irqexit_exlist)) { + pr_err("list is full\n"); + return count; + } + + if (irq != 0) { + dss_irqexit_exlist[i] = irq; + pr_info("success inserting %lu to list\n", irq); + } + return count; +} + +static ssize_t dss_irqexit_threshold_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t n; + + n = scnprintf(buf, 46, "threshold : %12u us\n", dss_irqexit_threshold); + return n; +} + +static ssize_t dss_irqexit_threshold_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + unsigned long val; + + val = simple_strtoul(buf, NULL, 0); + pr_info("threshold value : %lu\n", val); + + if (val != 0) { + dss_irqexit_threshold = (unsigned int)val; + pr_info("success %lu to threshold\n", val); + } + return count; +} +#endif + +#ifdef CONFIG_DEBUG_SNAPSHOT_REG +static ssize_t dss_reg_exlist_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + unsigned long i; + ssize_t n = 0; + + n = scnprintf(buf, 36, "excluded register address\n"); + for (i = 0; i < ARRAY_SIZE(dss_reg_exlist); i++) { + if (dss_reg_exlist[i].addr == 0) + break; + n += scnprintf(buf + n, 40, "register addr: %08zx size: %08zx\n", + dss_reg_exlist[i].addr, dss_reg_exlist[i].size); + } + return n; +} + +static ssize_t dss_reg_exlist_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + unsigned long i; + size_t addr; + + addr = simple_strtoul(buf, NULL, 0); + pr_info("register addr: %zx\n", addr); + + for (i = 0; i < ARRAY_SIZE(dss_reg_exlist); i++) { + if (dss_reg_exlist[i].addr == 0) + break; + } + if (addr != 0) { + dss_reg_exlist[i].size = SZ_4K; + dss_reg_exlist[i].addr = addr; + pr_info("success %zx to threshold\n", (addr)); + } + return count; +} +#endif + + +static struct kobj_attribute dss_enable_attr = +__ATTR(enabled, 0644, dss_enable_show, dss_enable_store); + +static struct kobj_attribute dss_callstack_attr = +__ATTR(callstack, 0644, dss_callstack_show, dss_callstack_store); + +static struct kobj_attribute dss_irqlog_attr = +__ATTR(exlist_irqdisabled, 0644, dss_irqlog_exlist_show, + dss_irqlog_exlist_store); +#ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_EXIT +static struct kobj_attribute dss_irqexit_attr = +__ATTR(exlist_irqexit, 0644, dss_irqexit_exlist_show, + dss_irqexit_exlist_store); + +static struct kobj_attribute dss_irqexit_threshold_attr = +__ATTR(threshold_irqexit, 0644, dss_irqexit_threshold_show, + dss_irqexit_threshold_store); +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_REG + +static struct kobj_attribute dss_reg_attr = +__ATTR(exlist_reg, 0644, dss_reg_exlist_show, dss_reg_exlist_store); +#endif + +static struct attribute *dss_sysfs_attrs[] = { + &dss_enable_attr.attr, + &dss_callstack_attr.attr, + &dss_irqlog_attr.attr, +#ifdef CONFIG_DEBUG_SNAPSHOT_IRQ_EXIT + &dss_irqexit_attr.attr, + &dss_irqexit_threshold_attr.attr, +#endif +#ifdef CONFIG_DEBUG_SNAPSHOT_REG + &dss_reg_attr.attr, +#endif + NULL, +}; + +static struct attribute_group dss_sysfs_group = { + .attrs = dss_sysfs_attrs, +}; + +static const struct attribute_group *dss_sysfs_groups[] = { + &dss_sysfs_group, + NULL, +}; + +static int __init dbg_snapshot_sysfs_init(void) +{ + int ret = 0; + + ret = subsys_system_register(&dss_subsys, dss_sysfs_groups); + if (ret) + pr_err("fail to register debug-snapshop subsys\n"); + + return ret; +} +late_initcall(dbg_snapshot_sysfs_init); diff --git a/lib/debug-snapshot-utils.c b/lib/debug-snapshot-utils.c new file mode 100644 index 000000000000..f64c0f7c14fc --- /dev/null +++ b/lib/debug-snapshot-utils.c @@ -0,0 +1,646 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#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 * 3; + + 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" + " Debugging Information for Hardlockup core - CPU %d" + "\n--------------------------------------------------------------------------\n\n", cpu); + } +} + +void dbg_snapshot_hook_hardlockup_exit(void) +{ + int cpu = raw_smp_processor_id(); + + if (!dss_base.enabled || + !dss_desc.hardlockup_core_mask || + !dss_desc.allcorelockup_detected) { + 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()); +#ifdef CONFIG_ARM64 + asm("mrs x1, SCTLR_EL1\n\t" /* SCTLR_EL1 */ + "str x1, [%0]\n\t" + "mrs x1, TTBR0_EL1\n\t" /* TTBR0_EL1 */ + "str x1, [%0,#8]\n\t" + "mrs x1, TTBR1_EL1\n\t" /* TTBR1_EL1 */ + "str x1, [%0,#16]\n\t" + "mrs x1, TCR_EL1\n\t" /* TCR_EL1 */ + "str x1, [%0,#24]\n\t" + "mrs x1, ESR_EL1\n\t" /* ESR_EL1 */ + "str x1, [%0,#32]\n\t" + "mrs x1, FAR_EL1\n\t" /* FAR_EL1 */ + "str x1, [%0,#40]\n\t" + /* Don't populate AFSR0_EL1 and AFSR1_EL1 */ + "mrs x1, CONTEXTIDR_EL1\n\t" /* CONTEXTIDR_EL1 */ + "str x1, [%0,#48]\n\t" + "mrs x1, TPIDR_EL0\n\t" /* TPIDR_EL0 */ + "str x1, [%0,#56]\n\t" + "mrs x1, TPIDRRO_EL0\n\t" /* TPIDRRO_EL0 */ + "str x1, [%0,#64]\n\t" + "mrs x1, TPIDR_EL1\n\t" /* TPIDR_EL1 */ + "str x1, [%0,#72]\n\t" + "mrs x1, MAIR_EL1\n\t" /* MAIR_EL1 */ + "str x1, [%0,#80]\n\t" + "mrs x1, ELR_EL1\n\t" /* ELR_EL1 */ + "str x1, [%0, #88]\n\t" + "mrs x1, SP_EL0\n\t" /* SP_EL0 */ + "str x1, [%0, #96]\n\t" : /* output */ + : "r"(mmu_reg) /* input */ + : "%x1", "memory" /* clobbered register */ + ); +#else + asm("mrc p15, 0, r1, c1, c0, 0\n\t" /* SCTLR */ + "str r1, [%0]\n\t" + "mrc p15, 0, r1, c2, c0, 0\n\t" /* TTBR0 */ + "str r1, [%0,#4]\n\t" + "mrc p15, 0, r1, c2, c0,1\n\t" /* TTBR1 */ + "str r1, [%0,#8]\n\t" + "mrc p15, 0, r1, c2, c0,2\n\t" /* TTBCR */ + "str r1, [%0,#12]\n\t" + "mrc p15, 0, r1, c3, c0,0\n\t" /* DACR */ + "str r1, [%0,#16]\n\t" + "mrc p15, 0, r1, c5, c0,0\n\t" /* DFSR */ + "str r1, [%0,#20]\n\t" + "mrc p15, 0, r1, c6, c0,0\n\t" /* DFAR */ + "str r1, [%0,#24]\n\t" + "mrc p15, 0, r1, c5, c0,1\n\t" /* IFSR */ + "str r1, [%0,#28]\n\t" + "mrc p15, 0, r1, c6, c0,2\n\t" /* IFAR */ + "str r1, [%0,#32]\n\t" + /* Don't populate DAFSR and RAFSR */ + "mrc p15, 0, r1, c10, c2,0\n\t" /* PMRRR */ + "str r1, [%0,#44]\n\t" + "mrc p15, 0, r1, c10, c2,1\n\t" /* NMRRR */ + "str r1, [%0,#48]\n\t" + "mrc p15, 0, r1, c13, c0,0\n\t" /* FCSEPID */ + "str r1, [%0,#52]\n\t" + "mrc p15, 0, r1, c13, c0,1\n\t" /* CONTEXT */ + "str r1, [%0,#56]\n\t" + "mrc p15, 0, r1, c13, c0,2\n\t" /* URWTPID */ + "str r1, [%0,#60]\n\t" + "mrc p15, 0, r1, c13, c0,3\n\t" /* UROTPID */ + "str r1, [%0,#64]\n\t" + "mrc p15, 0, r1, c13, c0,4\n\t" /* POTPIDR */ + "str r1, [%0,#68]\n\t" : /* output */ + : "r"(mmu_reg) /* input */ + : "%r1", "memory" /* clobbered register */ + ); +#endif +} + +int dbg_snapshot_dump(void) +{ + /* + * Output CPU Memory Error syndrome Register + * CPUMERRSR, L2MERRSR + */ +#ifdef CONFIG_ARM64 + unsigned long reg1, reg2, reg3; + + if (read_cpuid_implementor() == ARM_CPU_IMP_SEC) { + switch (read_cpuid_part_number()) { + case ARM_CPU_PART_MONGOOSE: + case ARM_CPU_PART_MEERKAT: + asm ("mrs %0, S3_1_c15_c2_0\n\t" + "mrs %1, S3_1_c15_c2_4\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("FEMERR0SR: %016lx, FEMERR1SR: %016lx\n", reg1, reg2); + asm ("mrs %0, S3_1_c15_c2_1\n\t" + "mrs %1, S3_1_c15_c2_5\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("LSMERR0SR: %016lx, LSMERR1SR: %016lx\n", reg1, reg2); + asm ("mrs %0, S3_1_c15_c2_2\n\t" + "mrs %1, S3_1_c15_c2_6\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("TBWMERR0SR: %016lx, TBWMERR1SR: %016lx\n", reg1, reg2); + asm ("mrs %0, S3_1_c15_c2_3\n\t" + "mrs %1, S3_1_c15_c2_7\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("L2MERR0SR: %016lx, L2MERR1SR: %016lx\n", reg1, reg2); + + /* L3 MERR */ + asm ("msr S3_1_c15_c7_1, %0\n\t" + "isb\n" + :: "r" (0)); + asm ("mrs %0, S3_1_c15_c3_0\n\t" + "mrs %1, S3_1_c15_c3_4\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("BANK0 L3MERR0SR: %016lx, L3MERR1SR: %016lx\n", reg1, reg2); + asm ("msr S3_1_c15_c7_1, %0\n\t" + "isb\n" + :: "r" (1)); + asm ("mrs %0, S3_1_c15_c3_0\n\t" + "mrs %1, S3_1_c15_c3_4\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("BANK1 L3MERR0SR: %016lx, L3MERR1SR: %016lx\n", reg1, reg2); + asm ("msr S3_1_c15_c7_1, %0\n\t" + "isb\n" + :: "r" (2)); + asm ("mrs %0, S3_1_c15_c3_0\n\t" + "mrs %1, S3_1_c15_c3_4\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("BANK2 L3MERR0SR: %016lx, L3MERR1SR: %016lx\n", reg1, reg2); + asm ("msr S3_1_c15_c7_1, %0\n\t" + "isb\n" + :: "r" (3)); + asm ("mrs %0, S3_1_c15_c3_0\n\t" + "mrs %1, S3_1_c15_c3_4\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("BANK3 L3MERR0SR: %016lx, L3MERR1SR: %016lx\n", reg1, reg2); + + break; + default: + break; + } + } else { + switch (read_cpuid_part_number()) { + case ARM_CPU_PART_CORTEX_A57: + case ARM_CPU_PART_CORTEX_A53: + asm ("mrs %0, S3_1_c15_c2_2\n\t" + "mrs %1, S3_1_c15_c2_3\n" + : "=r" (reg1), "=r" (reg2)); + pr_emerg("CPUMERRSR: %016lx, L2MERRSR: %016lx\n", reg1, reg2); + break; + case ARM_CPU_PART_ANANKE: + asm ("HINT #16"); + asm ("mrs %0, S3_0_c12_c1_1\n" : "=r" (reg1)); /* read DISR_EL1 */ + pr_emerg("DISR_EL1: %016lx\n", reg1); + + asm ("msr S3_0_c5_c3_1, %0\n" :: "r" (0)); /* set 1st ERRSELR_EL1 */ + + asm ("mrs %0, S3_0_c5_c4_2\n" + "mrs %1, S3_0_c5_c4_3\n" + "mrs %2, S3_0_c5_c5_0\n" + : "=r" (reg1), "=r" (reg2), "=r" (reg3)); + pr_emerg("1st : ERXSTATUS_EL1: %016lx, ERXADDR_EL1: %016lx, " + "ERXMISC0_EL1: %016lx\n", reg1, reg2, reg3); + + asm ("msr S3_0_c5_c3_1, %0\n" :: "r" (1)); /* set 2nd ERRSELR_EL1 */ + + asm ("mrs %0, S3_0_c5_c4_2\n" + "mrs %1, S3_0_c5_c4_3\n" + "mrs %2, S3_0_c5_c5_0\n" + : "=r" (reg1), "=r" (reg2), "=r" (reg3)); + pr_emerg("2nd : ERXSTATUS_EL1: %016lx, ERXADDR_EL1: %016lx, " + "ERXMISC0_EL1: %016lx\n", reg1, reg2, reg3); + + break; + default: + break; + } + } +#else + unsigned long reg0; + asm ("mrc p15, 0, %0, c0, c0, 0\n": "=r" (reg0)); + if (((reg0 >> 4) & 0xFFF) == 0xC0F) { + /* Only Cortex-A15 */ + unsigned long reg1, reg2, reg3; + asm ("mrrc p15, 0, %0, %1, c15\n\t" + "mrrc p15, 1, %2, %3, c15\n" + : "=r" (reg0), "=r" (reg1), + "=r" (reg2), "=r" (reg3)); + pr_emerg("CPUMERRSR: %08lx_%08lx, L2MERRSR: %08lx_%08lx\n", + reg1, reg0, reg3, reg2); + } +#endif + 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) { + asm("str x0, [%0, #0]\n\t" + "mov x0, %0\n\t" + "str x1, [x0, #8]\n\t" + "str x2, [x0, #16]\n\t" + "str x3, [x0, #24]\n\t" + "str x4, [x0, #32]\n\t" + "str x5, [x0, #40]\n\t" + "str x6, [x0, #48]\n\t" + "str x7, [x0, #56]\n\t" + "str x8, [x0, #64]\n\t" + "str x9, [x0, #72]\n\t" + "str x10, [x0, #80]\n\t" + "str x11, [x0, #88]\n\t" + "str x12, [x0, #96]\n\t" + "str x13, [x0, #104]\n\t" + "str x14, [x0, #112]\n\t" + "str x15, [x0, #120]\n\t" + "str x16, [x0, #128]\n\t" + "str x17, [x0, #136]\n\t" + "str x18, [x0, #144]\n\t" + "str x19, [x0, #152]\n\t" + "str x20, [x0, #160]\n\t" + "str x21, [x0, #168]\n\t" + "str x22, [x0, #176]\n\t" + "str x23, [x0, #184]\n\t" + "str x24, [x0, #192]\n\t" + "str x25, [x0, #200]\n\t" + "str x26, [x0, #208]\n\t" + "str x27, [x0, #216]\n\t" + "str x28, [x0, #224]\n\t" + "str x29, [x0, #232]\n\t" + "str x30, [x0, #240]\n\t" : + : "r"(core_reg)); + core_reg->sp = core_reg->regs[29]; + core_reg->pc = + (unsigned long)(core_reg->regs[30] - sizeof(unsigned int)); + } 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); + + flush_cache_all(); + 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', 'Z', 'X', 'x', 'K', 'W'}; + unsigned char idx = 0; + unsigned long state = (tsk->state & TASK_REPORT) | tsk->exit_state; + unsigned long wchan; + unsigned long pc = 0; + char symname[KSYM_NAME_LEN]; + + if ((tsk == NULL) || (tsk->stack == NULL)) + return; + + 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->mm == NULL) { + print_worker_info(KERN_INFO, tsk); + 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; + + 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_utils_init(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); diff --git a/lib/debug-snapshot.c b/lib/debug-snapshot.c new file mode 100644 index 000000000000..8eee4b13daf4 --- /dev/null +++ b/lib/debug-snapshot.c @@ -0,0 +1,671 @@ +/* + * 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 +#include + +#include "debug-snapshot-local.h" + +extern void register_hook_logbuf(void (*)(const char *, size_t)); +extern void register_hook_logger(void (*)(const char *, const char *, size_t)); + +struct dbg_snapshot_interface { + struct dbg_snapshot_log *info_event; + struct dbg_snapshot_item info_log[DSS_ITEM_MAX_NUM]; +}; + +#ifdef CONFIG_DEBUG_SNAPSHOT_PMU +struct dbg_snapshot_ops dss_ops = { + .pd_status = cal_pd_status, +}; +#endif + +struct dbg_snapshot_item dss_items[] = { + {"header", {0, 0, 0, false, false}, NULL ,NULL, 0, }, + {"log_kernel", {0, 0, 0, false, false}, NULL ,NULL, 0, }, + {"log_platform", {0, 0, 0, false, false}, NULL ,NULL, 0, }, + {"log_sfr", {0, 0, 0, false, false}, NULL ,NULL, 0, }, + {"log_s2d", {0, 0, 0, true, false}, NULL, NULL, 0, }, + {"log_cachedump", {0, 0, 0, true, false}, NULL, NULL, 0, }, + {"log_etm", {0, 0, 0, true, false}, NULL ,NULL, 0, }, + {"log_bcm", {0, 0, 0, false, false}, NULL ,NULL, 0, }, + {"log_pstore", {0, 0, 0, true, false}, NULL ,NULL, 0, }, + {"log_kevents", {0, 0, 0, false, false}, NULL ,NULL, 0, }, +}; + +/* External interface variable for trace debugging */ +static struct dbg_snapshot_interface dss_info __attribute__ ((used)); +static struct dbg_snapshot_interface *ess_info __attribute__ ((used)); + +struct dbg_snapshot_base dss_base; +struct dbg_snapshot_base ess_base; +struct dbg_snapshot_log *dss_log = NULL; +struct dbg_snapshot_desc dss_desc; + +/* Variable for assigning virtual address base */ +static size_t g_dbg_snapshot_vaddr_base = DSS_FIXED_VIRT_BASE; + +int dbg_snapshot_set_enable(const char *name, int en) +{ + struct dbg_snapshot_item *item = NULL; + unsigned long i; + + if (!strncmp(name, "base", strlen(name))) { + dss_base.enabled = en; + pr_info("debug-snapshot: %sabled\n", en ? "en" : "dis"); + } else { + for (i = 0; i < ARRAY_SIZE(dss_items); i++) { + if (!strncmp(dss_items[i].name, name, strlen(name))) { + item = &dss_items[i]; + item->entry.enabled = en; + item->time = local_clock(); + pr_info("debug-snapshot: item - %s is %sabled\n", + name, en ? "en" : "dis"); + break; + } + } + } + return 0; +} +EXPORT_SYMBOL(dbg_snapshot_set_enable); + +int dbg_snapshot_try_enable(const char *name, unsigned long long duration) +{ + struct dbg_snapshot_item *item = NULL; + unsigned long long time; + unsigned long i; + int ret = -1; + + /* If DSS was disabled, just return */ + if (unlikely(!dss_base.enabled) || !dbg_snapshot_get_enable("header")) + return ret; + + for (i = 0; i < ARRAY_SIZE(dss_items); i++) { + if (!strncmp(dss_items[i].name, name, strlen(name))) { + item = &dss_items[i]; + + /* We only interest in disabled */ + if (!item->entry.enabled) { + time = local_clock() - item->time; + if (time > duration) { + item->entry.enabled = true; + ret = 1; + } else + ret = 0; + } + break; + } + } + return ret; +} +EXPORT_SYMBOL(dbg_snapshot_try_enable); + +int dbg_snapshot_get_enable(const char *name) +{ + struct dbg_snapshot_item *item = NULL; + unsigned long i; + int ret = 0; + + if (!strncmp(name, "base", strlen(name))) + return dss_base.enabled; + + for (i = 0; i < ARRAY_SIZE(dss_items); i++) { + if (!strncmp(dss_items[i].name, name, strlen(name))) { + item = &dss_items[i]; + ret = item->entry.enabled; + break; + } + } + return ret; +} +EXPORT_SYMBOL(dbg_snapshot_get_enable); + +static inline int dbg_snapshot_check_eob(struct dbg_snapshot_item *item, + size_t size) +{ + size_t max, cur; + + max = (size_t)(item->head_ptr + item->entry.size); + cur = (size_t)(item->curr_ptr + size); + + if (unlikely(cur > max)) + return -1; + else + return 0; +} + +static inline void dbg_snapshot_hook_logger(const char *name, + const char *buf, size_t size) +{ + struct dbg_snapshot_item *item = &dss_items[dss_desc.log_platform_num]; + + if (likely(dss_base.enabled && item->entry.enabled)) { + if (unlikely((dbg_snapshot_check_eob(item, size)))) + item->curr_ptr = item->head_ptr; + memcpy(item->curr_ptr, buf, size); + item->curr_ptr += size; + } +} + +static inline void dbg_snapshot_hook_logbuf(const char *buf, size_t size) +{ + struct dbg_snapshot_item *item = &dss_items[dss_desc.log_kernel_num]; + + if (likely(dss_base.enabled && item->entry.enabled)) { + size_t last_buf; + + if (dbg_snapshot_check_eob(item, size)) + item->curr_ptr = item->head_ptr; + + memcpy(item->curr_ptr, buf, size); + item->curr_ptr += size; + /* save the address of last_buf to physical address */ + last_buf = (size_t)item->curr_ptr; + + __raw_writel(item->entry.paddr + (last_buf - item->entry.vaddr), + dbg_snapshot_get_base_vaddr() + DSS_OFFSET_LAST_LOGBUF); + } +} + +#ifdef CONFIG_DEBUG_SNAPSHOT_PMU +static bool dbg_snapshot_check_pmu(struct dbg_snapshot_sfrdump *sfrdump, + const struct device_node *np) +{ + int ret = 0, count, i; + unsigned int val; + + if (!sfrdump->pwr_mode) + return true; + + count = of_property_count_u32_elems(np, "cal-pd-id"); + for (i = 0; i < count; i++) { + ret = of_property_read_u32_index(np, "cal-pd-id", i, &val); + if (ret < 0) { + pr_err("failed to get pd-id - %s\n", sfrdump->name); + return false; + } + ret = dss_ops.pd_status(val); + if (ret < 0) { + pr_err("not powered - %s (pd-id: %d)\n", sfrdump->name, i); + return false; + } + } + return true; +} + +void dbg_snapshot_dump_sfr(void) +{ + struct dbg_snapshot_sfrdump *sfrdump; + struct dbg_snapshot_item *item = &dss_items[dss_desc.log_sfr_num]; + struct list_head *entry; + struct device_node *np; + unsigned int reg, offset, val, size; + int i, ret; + static char buf[SZ_64]; + + if (unlikely(!dss_base.enabled || !item->entry.enabled)) + return; + + if (list_empty(&dss_desc.sfrdump_list)) { + pr_emerg("debug-snapshot: %s: No information\n", __func__); + return; + } + + list_for_each(entry, &dss_desc.sfrdump_list) { + sfrdump = list_entry(entry, struct dbg_snapshot_sfrdump, list); + np = of_node_get(sfrdump->node); + ret = dbg_snapshot_check_pmu(sfrdump, np); + if (!ret) + /* may off */ + continue; + + for (i = 0; i < sfrdump->num; i++) { + ret = of_property_read_u32_index(np, "addr", i, ®); + if (ret < 0) { + pr_err("debug-snapshot: failed to get address information - %s\n", + sfrdump->name); + break; + } + if (reg == 0xFFFFFFFF || reg == 0) + break; + offset = reg - sfrdump->phy_reg; + if (reg < offset) { + pr_err("debug-snapshot: invalid address information - %s: 0x%08x\n", + sfrdump->name, reg); + break; + } + val = __raw_readl(sfrdump->reg + offset); + snprintf(buf, SZ_64, "0x%X = 0x%0X\n",reg, val); + size = (unsigned int)strlen(buf); + if (unlikely((dbg_snapshot_check_eob(item, size)))) + item->curr_ptr = item->head_ptr; + memcpy(item->curr_ptr, buf, strlen(buf)); + item->curr_ptr += strlen(buf); + } + of_node_put(np); + pr_info("debug-snapshot: complete to dump %s\n", sfrdump->name); + } + +} + +static int dbg_snapshot_sfr_dump_init(struct device_node *np) +{ + struct device_node *dump_np; + struct dbg_snapshot_sfrdump *sfrdump; + char *dump_str; + int count, ret, i; + u32 phy_regs[2]; + + ret = of_property_count_strings(np, "sfr-dump-list"); + if (ret < 0) { + pr_err("failed to get sfr-dump-list\n"); + return ret; + } + count = ret; + + INIT_LIST_HEAD(&dss_desc.sfrdump_list); + for (i = 0; i < count; i++) { + ret = of_property_read_string_index(np, "sfr-dump-list", i, + (const char **)&dump_str); + if (ret < 0) { + pr_err("failed to get sfr-dump-list\n"); + continue; + } + + dump_np = of_get_child_by_name(np, dump_str); + if (!dump_np) { + pr_err("failed to get %s node, count:%d\n", dump_str, count); + continue; + } + + sfrdump = kzalloc(sizeof(struct dbg_snapshot_sfrdump), GFP_KERNEL); + if (!sfrdump) { + pr_err("failed to get memory region of dbg_snapshot_sfrdump\n"); + of_node_put(dump_np); + continue; + } + + ret = of_property_read_u32_array(dump_np, "reg", phy_regs, 2); + if (ret < 0) { + pr_err("failed to get register information\n"); + of_node_put(dump_np); + kfree(sfrdump); + continue; + } + + sfrdump->reg = ioremap(phy_regs[0], phy_regs[1]); + if (!sfrdump->reg) { + pr_err("failed to get i/o address %s node\n", dump_str); + of_node_put(dump_np); + kfree(sfrdump); + continue; + } + sfrdump->name = dump_str; + + ret = of_property_count_u32_elems(dump_np, "addr"); + if (ret < 0) { + pr_err("failed to get addr count\n"); + of_node_put(dump_np); + kfree(sfrdump); + continue; + } + sfrdump->phy_reg = phy_regs[0]; + sfrdump->num = ret; + + ret = of_property_count_u32_elems(dump_np, "cal-pd-id"); + if (ret < 0) + sfrdump->pwr_mode = false; + else + sfrdump->pwr_mode = true; + + sfrdump->node = dump_np; + list_add(&sfrdump->list, &dss_desc.sfrdump_list); + + pr_info("success to regsiter %s\n", sfrdump->name); + of_node_put(dump_np); + ret = 0; + } + return ret; +} +#endif + +static int __init dbg_snapshot_remap(void) +{ + unsigned long i, j; + unsigned int enabled_count = 0; + pgprot_t prot = __pgprot(PROT_NORMAL_NC); + int page_size, ret; + struct page *page; + struct page **pages; + + for (i = 0; i < ARRAY_SIZE(dss_items); i++) { + if (dss_items[i].entry.enabled) { + enabled_count++; + page_size = dss_items[i].entry.size / PAGE_SIZE; + pages = kzalloc(sizeof(struct page *) * page_size, GFP_KERNEL); + page = phys_to_page(dss_items[i].entry.paddr); + + for (j = 0; j < page_size; j++) + pages[j] = page++; + + ret = map_vm_area(&dss_items[i].vm, prot, pages); + kfree(pages); + if (ret) { + pr_err("debug-snapshot: failed to mapping between virt and phys"); + return -ENOMEM; + } + + dss_items[i].entry.vaddr = (size_t)dss_items[i].vm.addr; + dss_items[i].head_ptr = (unsigned char *)dss_items[i].entry.vaddr; + dss_items[i].curr_ptr = (unsigned char *)dss_items[i].entry.vaddr; + } + } + dss_desc.log_enable_cnt = enabled_count; + return enabled_count; +} + +static int __init dbg_snapshot_init_desc(void) +{ + unsigned int i, len; + + /* initialize dss_desc */ + memset((struct dbg_snapshot_desc *)&dss_desc, 0, sizeof(struct dbg_snapshot_desc)); + dss_desc.callstack = CONFIG_DEBUG_SNAPSHOT_CALLSTACK; + raw_spin_lock_init(&dss_desc.ctrl_lock); + raw_spin_lock_init(&dss_desc.nmi_lock); + + for (i = 0; i < (unsigned int)ARRAY_SIZE(dss_items); i++) { + len = strlen(dss_items[i].name); + if (!strncmp(dss_items[i].name, "header", len)) + dss_desc.header_num = i; + else if (!strncmp(dss_items[i].name, "log_kevents", len)) + dss_desc.kevents_num = i; + else if (!strncmp(dss_items[i].name, "log_kernel", len)) + dss_desc.log_kernel_num = i; + else if (!strncmp(dss_items[i].name, "log_platform", len)) + dss_desc.log_platform_num = i; + else if (!strncmp(dss_items[i].name, "log_sfr", len)) + dss_desc.log_sfr_num = i; + else if (!strncmp(dss_items[i].name, "log_pstore", len)) + dss_desc.log_pstore_num = i; + } + +#ifdef CONFIG_S3C2410_WATCHDOG + dss_desc.no_wdt_dev = false; +#else + dss_desc.no_wdt_dev = true; +#endif + return 0; +} + +#ifdef CONFIG_OF_RESERVED_MEM +static int __init dbg_snapshot_item_reserved_mem_setup(struct reserved_mem *remem) +{ + unsigned int i; + + for (i = 0; i < (unsigned int)ARRAY_SIZE(dss_items); i++) { + if (strnstr(remem->name, dss_items[i].name, strlen(remem->name))) + break; + } + + if (i == ARRAY_SIZE(dss_items)) + return -ENODEV; + + dss_items[i].entry.paddr = remem->base; + dss_items[i].entry.size = remem->size; + dss_items[i].entry.enabled = true; + + dss_items[i].vm.phys_addr = remem->base; + dss_items[i].vm.addr = (void *)g_dbg_snapshot_vaddr_base; + dss_items[i].vm.size = remem->size; + dss_items[i].vm.flags = VM_NO_GUARD; + g_dbg_snapshot_vaddr_base += remem->size; + + vm_area_add_early(&dss_items[i].vm); + + if (strnstr(remem->name, "header", strlen(remem->name))) { + dss_base.paddr = remem->base; + dss_base.vaddr = (size_t)dss_items[i].vm.addr; + ess_base = dss_base; + dss_base.enabled = false; + } + dss_base.size += remem->size; + return 0; +} + +#define DECLARE_DBG_SNAPSHOT_RESERVED_REGION(compat, name) \ +RESERVEDMEM_OF_DECLARE(name, compat#name, dbg_snapshot_item_reserved_mem_setup) + +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", header); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_kernel); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_platform); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_sfr); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_s2d); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_cachedump); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_etm); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_bcm); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_pstore); +DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_kevents); +#endif + +/* + * --------------------------------------------------------------------- + * - dummy data:phy_addr, virtual_addr, buffer_size, magic_key(4K) - + * --------------------------------------------------------------------- + * - Cores MMU register(4K) - + * --------------------------------------------------------------------- + * - Cores CPU register(4K) - + * --------------------------------------------------------------------- + */ +static int __init dbg_snapshot_output(void) +{ + unsigned long i, size = 0; + + pr_info("debug-snapshot physical / virtual memory layout:\n"); + for (i = 0; i < ARRAY_SIZE(dss_items); i++) { + if (dss_items[i].entry.enabled) + pr_info("%-12s: phys:0x%zx / virt:0x%zx / size:0x%zx\n", + dss_items[i].name, + dss_items[i].entry.paddr, + dss_items[i].entry.vaddr, + dss_items[i].entry.size); + size += dss_items[i].entry.size; + } + + pr_info("total_item_size: %ldKB, dbg_snapshot_log struct size: %dKB\n", + size / SZ_1K, dbg_snapshot_log_size / SZ_1K); + + return 0; +} + +/* Header dummy data(4K) + * ------------------------------------------------------------------------- + * 0 4 8 C + * ------------------------------------------------------------------------- + * 0 vaddr phy_addr size magic_code + * 4 Scratch_val logbuf_addr 0 0 + * ------------------------------------------------------------------------- +*/ + +static void __init dbg_snapshot_fixmap_header(void) +{ + /* fill 0 to next to header */ + size_t vaddr, paddr, size; + size_t *addr; + + vaddr = dss_items[dss_desc.header_num].entry.vaddr; + paddr = dss_items[dss_desc.header_num].entry.paddr; + size = dss_items[dss_desc.header_num].entry.size; + + /* set to confirm debug-snapshot */ + addr = (size_t *)vaddr; + memcpy(addr, &dss_base, sizeof(struct dbg_snapshot_base)); + + if (!dbg_snapshot_get_enable("header")) + return; + + /* initialize kernel event to 0 except only header */ + memset((size_t *)(vaddr + DSS_KEEP_HEADER_SZ), 0, size - DSS_KEEP_HEADER_SZ); +} + +static void __init dbg_snapshot_fixmap(void) +{ + size_t last_buf; + size_t vaddr, paddr, size; + unsigned long i; + + /* fixmap to header first */ + dbg_snapshot_fixmap_header(); + + for (i = 1; i < ARRAY_SIZE(dss_items); i++) { + if (!dss_items[i].entry.enabled) + continue; + + /* assign dss_item information */ + paddr = dss_items[i].entry.paddr; + vaddr = dss_items[i].entry.vaddr; + size = dss_items[i].entry.size; + + if (i == dss_desc.log_kernel_num) { + /* load last_buf address value(phy) by virt address */ + last_buf = (size_t)__raw_readl(dbg_snapshot_get_base_vaddr() + + DSS_OFFSET_LAST_LOGBUF); + /* check physical address offset of kernel logbuf */ + if (last_buf >= dss_items[i].entry.paddr && + (last_buf) <= (dss_items[i].entry.paddr + dss_items[i].entry.size)) { + /* assumed valid address, conversion to virt */ + dss_items[i].curr_ptr = (unsigned char *)(dss_items[i].entry.vaddr + + (last_buf - dss_items[i].entry.paddr)); + } else { + /* invalid address, set to first line */ + dss_items[i].curr_ptr = (unsigned char *)vaddr; + /* initialize logbuf to 0 */ + memset((size_t *)vaddr, 0, size); + } + } else { + /* initialized log to 0 if persist == false */ + if (!dss_items[i].entry.persist) + memset((size_t *)vaddr, 0, size); + } + dss_info.info_log[i - 1].name = kstrdup(dss_items[i].name, GFP_KERNEL); + dss_info.info_log[i - 1].head_ptr = (unsigned char *)dss_items[i].entry.vaddr; + dss_info.info_log[i - 1].curr_ptr = NULL; + dss_info.info_log[i - 1].entry.size = size; + } + + dss_log = (struct dbg_snapshot_log *)(dss_items[dss_desc.kevents_num].entry.vaddr); + + /* set fake translation to virtual address to debug trace */ + dss_info.info_event = dss_log; + ess_info = &dss_info; + + /* output the information of debug-snapshot */ + dbg_snapshot_output(); +} + +static int dbg_snapshot_init_dt_parse(struct device_node *np) +{ + int ret = 0; + struct device_node *sfr_dump_np = of_get_child_by_name(np, "dump-info"); + + if (!sfr_dump_np) { + pr_err("debug-snapshot: failed to get dump-info node\n"); + ret = -ENODEV; + } else { +#ifdef CONFIG_DEBUG_SNAPSHOT_PMU + ret = dbg_snapshot_sfr_dump_init(sfr_dump_np); + if (ret < 0) { + pr_err("debug-snapshot: failed to register sfr dump node\n"); + ret = -ENODEV; + of_node_put(sfr_dump_np); + } +#endif + } + if (ret < 0) + dbg_snapshot_set_enable("log_sfr", false); + + if (of_property_read_u32(np, "use_multistage_wdt_irq", + &dss_desc.multistage_wdt_irq)) { + dss_desc.multistage_wdt_irq = 0; + pr_err("debug-snapshot: no support multistage_wdt\n"); + ret = -EINVAL; + } + + of_node_put(np); + return ret; +} + +static const struct of_device_id dss_of_match[] __initconst = { + { .compatible = "debug-snapshot-soc", + .data = dbg_snapshot_init_dt_parse}, + {}, +}; + +static int __init dbg_snapshot_init_dt(void) +{ + struct device_node *np; + const struct of_device_id *matched_np; + dss_initcall_t init_fn; + + np = of_find_matching_node_and_match(NULL, dss_of_match, &matched_np); + + if (!np) { + pr_info("debug-snapshot: couldn't find device tree file of debug-snapshot\n"); + dbg_snapshot_set_enable("log_sfr", false); + return -ENODEV; + } + + init_fn = (dss_initcall_t)matched_np->data; + return init_fn(np); +} + +static int __init dbg_snapshot_init(void) +{ + dbg_snapshot_init_desc(); + if (dbg_snapshot_remap() > 0) { + /* + * for debugging when we don't know the virtual address of pointer, + * In just privous the debug buffer, It is added 16byte dummy data. + * start address(dummy 16bytes) + * --> @virtual_addr | @phy_addr | @buffer_size | @magic_key(0xDBDBDBDB) + * And then, the debug buffer is shown. + */ + dbg_snapshot_log_idx_init(); + dbg_snapshot_fixmap(); + dbg_snapshot_init_dt(); + dbg_snapshot_helper_init(); + dbg_snapshot_utils_init(); + + dbg_snapshot_scratch_reg(DSS_SIGN_SCRATCH); + dbg_snapshot_set_enable("base", true); + + register_hook_logbuf(dbg_snapshot_hook_logbuf); + register_hook_logger(dbg_snapshot_hook_logger); + } else + pr_err("debug-snapshot: %s failed\n", __func__); + + return 0; +} +early_initcall(dbg_snapshot_init); -- 2.20.1