463 lines
12 KiB
C
Executable File
463 lines
12 KiB
C
Executable File
/*
|
|
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* Debug-SnapShot: Debug Framework for Ramdump based debugging method
|
|
* The original code is Exynos-Snapshot for Exynos SoC
|
|
*
|
|
* Author: Hosung Kim <hosung0.kim@samsung.com>
|
|
* Author: Changki Kim <changki.kim@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/io.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/input.h>
|
|
#include <linux/smc.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/nmi.h>
|
|
#include <linux/init_task.h>
|
|
#include <linux/ftrace.h>
|
|
|
|
#include <asm/cputype.h>
|
|
#include <asm/smp_plat.h>
|
|
#include <asm/core_regs.h>
|
|
|
|
#include "debug-snapshot-local.h"
|
|
#include <linux/debug-snapshot-helper.h>
|
|
|
|
#ifdef CONFIG_SEC_DEBUG
|
|
#include <linux/sec_debug.h>
|
|
#endif /* CONFIG_SEC_DEBUG */
|
|
|
|
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_post_panic_entry = dbg_snapshot_soc_dummy_func,
|
|
.soc_post_panic_exit = dbg_snapshot_soc_dummy_func,
|
|
.soc_post_reboot_entry = dbg_snapshot_soc_dummy_func,
|
|
.soc_post_reboot_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_save_core = dbg_snapshot_soc_dummy_func,
|
|
.soc_save_system = dbg_snapshot_soc_dummy_func,
|
|
.soc_dump_info = 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_set_debug_level_reg(void)
|
|
{
|
|
if (dbg_snapshot_get_enable("header"))
|
|
__raw_writel(dss_desc.debug_level | DSS_DEBUG_LEVEL_PREFIX,
|
|
dbg_snapshot_get_base_vaddr() + DSS_OFFSET_DEBUG_LEVEL);
|
|
}
|
|
|
|
int dbg_snapshot_get_debug_level_reg(void)
|
|
{
|
|
int ret = DSS_DEBUG_LEVEL_NONE;
|
|
|
|
if (dbg_snapshot_get_enable("header")) {
|
|
int val = __raw_readl(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_DEBUG_LEVEL);
|
|
|
|
if ((val & GENMASK(31, 16)) == DSS_DEBUG_LEVEL_PREFIX)
|
|
ret = val & GENMASK(15, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void dbg_snapshot_set_sjtag_status(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = dss_soc_ops->soc_smc_call(SMC_CMD_GET_SJTAG_STATUS, 0x3, 0, 0);
|
|
|
|
if (ret == true || ret == false) {
|
|
dss_desc.sjtag_status = ret;
|
|
pr_info("debug-snapshot: SJTAG is %sabled\n",
|
|
ret == true ? "en" : "dis");
|
|
return;
|
|
}
|
|
|
|
dss_desc.sjtag_status = -1;
|
|
}
|
|
|
|
int dbg_snapshot_get_sjtag_status(void)
|
|
{
|
|
return dss_desc.sjtag_status;
|
|
}
|
|
|
|
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_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_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_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) {
|
|
dbg_snapshot_recall_hardlockup_core();
|
|
#ifdef CONFIG_DEBUG_SNAPSHOT_PMU
|
|
dbg_snapshot_dump_sfr();
|
|
#endif
|
|
dbg_snapshot_save_context(NULL);
|
|
|
|
dbg_snapshot_print_panic_report();
|
|
|
|
dss_soc_ops->soc_post_panic_entry(NULL);
|
|
|
|
#ifdef CONFIG_DEBUG_SNAPSHOT_PANIC_REBOOT
|
|
if (!dss_desc.no_wdt_dev && !dbg_snapshot_get_sjtag_status()) {
|
|
#ifdef CONFIG_DEBUG_SNAPSHOT_WATCHDOG_RESET
|
|
if (dss_desc.hardlockup_detected || num_online_cpus() > 1) {
|
|
/* for stall cpu */
|
|
dbg_snapshot_spin_func();
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_SEC_DEBUG
|
|
sec_debug_post_panic_handler();
|
|
#endif
|
|
|
|
dss_soc_ops->soc_post_panic_exit(NULL);
|
|
|
|
/* for stall cpu when not enabling panic reboot */
|
|
dbg_snapshot_spin_func();
|
|
|
|
/* 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;
|
|
|
|
dss_soc_ops->soc_post_reboot_entry(NULL);
|
|
|
|
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);
|
|
|
|
/* clear DSS_SIGN_PANIC when normal reboot */
|
|
for_each_possible_cpu(cpu) {
|
|
dbg_snapshot_set_core_panic_stat(DSS_SIGN_RESET, cpu);
|
|
}
|
|
|
|
dss_soc_ops->soc_post_reboot_exit(NULL);
|
|
|
|
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");
|
|
|
|
#ifdef CONFIG_SEC_DEBUG
|
|
sec_debug_reboot_handler(p);
|
|
#endif
|
|
|
|
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);
|
|
|
|
#ifdef CONFIG_SEC_DEBUG
|
|
sec_debug_panic_handler(buf, true);
|
|
#endif
|
|
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);
|
|
#ifdef CONFIG_SEC_DEBUG
|
|
dss_soc_ops->soc_expire_watchdog((void *)_RET_IP_);
|
|
#else
|
|
dss_soc_ops->soc_expire_watchdog((void *)NULL);
|
|
#endif
|
|
}
|
|
|
|
void dbg_snapshot_register_soc_ops(struct dbg_snapshot_helper_ops *ops)
|
|
{
|
|
if (ops)
|
|
dss_soc_ops = ops;
|
|
}
|
|
|
|
void __init dbg_snapshot_init_helper(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();
|
|
}
|