blob: ad5408138927fe2a5d5851a7211e01efb8693583 [file] [log] [blame]
/*
* 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();
}