1
0
mirror of https://github.com/physwizz/a155-U-u1.git synced 2024-11-19 13:27:49 +00:00
a155-U-u1/kernel-5.10/drivers/misc/mediatek/aee/mrdump/mrdump_panic.c
2024-03-11 06:53:12 +11:00

488 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2015 MediaTek Inc.
*/
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/kdebug.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/sched/clock.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <asm/cacheflush.h>
#include <asm/kexec.h>
#include <asm/memory.h>
#include <asm/stacktrace.h>
#include <asm/system_misc.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_fdt.h>
#include <linux/of_reserved_mem.h>
#include <debug_kinfo.h>
#include <mrdump.h>
#include <mt-plat/mboot_params.h>
#include <mt-plat/mtk_system_reset.h>
#include "mrdump_mini.h"
#include "mrdump_private.h"
/* for arm_smccc_smc */
#include <linux/arm-smccc.h>
static struct pt_regs saved_regs;
static void aee_exception_reboot(int reboot_reason)
{
struct arm_smccc_res res;
int opt1 = 0, opt2 = 0;
if (reboot_reason == AEE_REBOOT_MODE_HANG_DETECT)
opt1 |= ((unsigned char)AEE_EXP_TYPE_HANG_DETECT) << RESET2_TYPE_DOMAIN_USAGE_SHIFT;
else if (reboot_reason == AEE_REBOOT_MODE_WDT)
opt1 |= ((unsigned char)AEE_EXP_TYPE_HWT) << RESET2_TYPE_DOMAIN_USAGE_SHIFT;
else
opt1 |= ((unsigned char)AEE_EXP_TYPE_KE) << RESET2_TYPE_DOMAIN_USAGE_SHIFT;
opt1 |= (unsigned char)MTK_DOMAIN_AEE;
arm_smccc_smc(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2),
PSCI_1_1_RESET2_TYPE_VENDOR | opt1,
opt2, 0, 0, 0, 0, 0, &res);
}
#if defined(CONFIG_RANDOMIZE_BASE) && defined(CONFIG_ARM64)
static inline void show_kaslr(void)
{
u64 const kaslr_off = kaslr_offset();
pr_notice("Kernel Offset: 0x%llx from 0x%lx\n",
kaslr_off, KIMAGE_VADDR);
pr_notice("PHYS_OFFSET: 0x%llx\n", PHYS_OFFSET);
aee_rr_rec_kaslr_offset(kaslr_off);
}
#else
static inline void show_kaslr(void)
{
pr_notice("Kernel Offset: disabled\n");
aee_rr_rec_kaslr_offset(0xd15ab1e);
}
#endif
static char nested_panic_buf[1024];
int aee_nested_printf(const char *fmt, ...)
{
va_list args;
static int total_len;
va_start(args, fmt);
total_len += vsnprintf(nested_panic_buf, sizeof(nested_panic_buf),
fmt, args);
va_end(args);
aee_sram_fiq_log(nested_panic_buf);
return total_len;
}
static void check_last_ko(void)
{
struct list_head *p_modules = aee_get_modules();
struct module *mod;
if (!p_modules)
return;
list_for_each_entry_rcu(mod, p_modules, list) {
if (mod->state == MODULE_STATE_UNFORMED)
break;
load_ko_addr_list(mod);
break;
}
}
static void mrdump_cblock_update(enum AEE_REBOOT_MODE reboot_mode,
struct pt_regs *regs, const char *msg, ...)
{
struct mrdump_crash_record *crash_record;
void *creg;
int cpu;
size_t msg_count;
elf_gregset_t *reg;
local_irq_disable();
switch (reboot_mode) {
case AEE_REBOOT_MODE_KERNEL_OOPS:
aee_rr_rec_exp_type(AEE_EXP_TYPE_KE);
break;
case AEE_REBOOT_MODE_KERNEL_PANIC:
aee_rr_rec_exp_type(AEE_EXP_TYPE_KE);
break;
case AEE_REBOOT_MODE_HANG_DETECT:
aee_rr_rec_exp_type(AEE_EXP_TYPE_HANG_DETECT);
break;
case AEE_REBOOT_MODE_WDT:
aee_rr_rec_exp_type(AEE_EXP_TYPE_HWT);
break;
default:
/* Don't print anything */
aee_rr_rec_exp_type(AEE_EXP_TYPE_KE);
break;
}
if (mrdump_cblock) {
crash_record = &mrdump_cblock->crash_record;
cpu = raw_smp_processor_id();
switch (sizeof(unsigned long)) {
case 4:
reg = (elf_gregset_t *)&crash_record->cpu_reg[cpu].arm32_reg.arm32_regs;
creg = (void *)&crash_record->cpu_reg[cpu].arm32_reg.arm32_creg;
break;
case 8:
reg = (elf_gregset_t *)&crash_record->cpu_reg[cpu].arm64_reg.arm64_regs;
creg = (void *)&crash_record->cpu_reg[cpu].arm64_reg.arm64_creg;
break;
default:
BUILD_BUG();
}
if (cpu >= 0 && cpu < nr_cpu_ids) {
/* null regs, no register dump */
if (regs)
elf_core_copy_kernel_regs(reg, regs);
mrdump_save_control_register(creg);
}
msg_count = strlen(msg);
if (msg_count >= sizeof(crash_record->msg))
msg_count = sizeof(crash_record->msg) - 1;
memcpy_toio(crash_record->msg, msg, msg_count);
__raw_writeb(0, &crash_record->msg[msg_count]);
crash_record->fault_cpu = cpu;
/* FIXME: Check reboot_mode is valid */
crash_record->reboot_mode = reboot_mode;
}
}
static void (*p_show_task_info)(void);
void mrdump_regist_hang_bt(void (*fn)(void))
{
p_show_task_info = fn;
}
EXPORT_SYMBOL_GPL(mrdump_regist_hang_bt);
#if IS_ENABLED(CONFIG_SEC_DEBUG)
/*******************************************************
Module loading order for SEC_DEBUG
< ko_order_table.csv >
sec_deub.ko
mrdump.ko
...
mtk-pmic-keys.ko
sec_reboot.ko
sec_rst.ko
sec_ext.ko
*******************************************************/
extern void sec_debug_dump_info(struct pt_regs *regs);
#if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO)
extern void sec_debug_set_extra_info_fault(unsigned long addr, struct pt_regs *regs);
#endif
static void (*reset_delay)(void);
void register_mrdump_reset_delay(void (*func)(void))
{
if (!func)
return;
reset_delay = func;
pr_info("%s done!\n", __func__);
}
EXPORT_SYMBOL(register_mrdump_reset_delay);
#endif
static int num_die;
atomic_t first_cpu = ATOMIC_INIT(-1);
int mrdump_common_die(int reboot_reason, const char *msg,
struct pt_regs *regs)
{
int last_step;
int next_step;
int cpu_tmp;
if (!aee_is_enable()) {
pr_notice("%s: ipanic: mrdump is disable\n", __func__);
panic(msg);
return 0;
}
num_die++;
cpu_tmp = raw_smp_processor_id();
if (atomic_read(&first_cpu) == -1) {
atomic_set(&first_cpu, cpu_tmp);
} else if (atomic_read(&first_cpu) != cpu_tmp) {
pr_info("mrdump: first crash cpu %d, second crash cpu %d\n",
atomic_read(&first_cpu), cpu_tmp);
while (1)
cpu_relax();
}
last_step = aee_rr_curr_fiq_step();
if (num_die > 1) {
/* NESTED KE */
aee_reinit_die_lock();
}
aee_nested_printf("num_die-%d, last_step-%d\n",
num_die, last_step);
/* if we were in nested ke now, then the if condition would be false */
if (last_step < AEE_FIQ_STEP_COMMON_DIE_START)
last_step = AEE_FIQ_STEP_COMMON_DIE_START - 1;
/* skip the works of last_step */
next_step = last_step + 1;
switch (next_step) {
case AEE_FIQ_STEP_COMMON_DIE_START:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_START);
mrdump_cblock_update(reboot_reason, regs, msg);
mrdump_mini_ke_cpu_regs(regs);
case AEE_FIQ_STEP_COMMON_DIE_LOCK:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_LOCK);
/* release locks after set up cblock */
aee_reinit_die_lock();
case AEE_FIQ_STEP_COMMON_DIE_KASLR:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_KASLR);
show_kaslr();
case AEE_FIQ_STEP_COMMON_DIE_SCP:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_SCP);
aee_rr_rec_scp();
case AEE_FIQ_STEP_COMMON_DIE_TRACE:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_TRACE);
switch (reboot_reason) {
case AEE_REBOOT_MODE_KERNEL_OOPS:
aee_show_regs(regs);
dump_stack();
break;
case AEE_REBOOT_MODE_KERNEL_PANIC:
#ifndef CONFIG_DEBUG_BUGVERBOSE
dump_stack();
#endif
break;
default:
/* Don't print anything */
break;
}
if (p_show_task_info && !strcmp(current->comm, "llkd"))
p_show_task_info();
case AEE_FIQ_STEP_COMMON_DIE_EMISC:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_EMISC);
mrdump_mini_add_extra_misc();
check_last_ko();
case AEE_FIQ_STEP_COMMON_DIE_CS:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_CS);
case AEE_FIQ_STEP_COMMON_DIE_DONE:
aee_rr_rec_fiq_step(AEE_FIQ_STEP_COMMON_DIE_DONE);
default:
#if IS_ENABLED(CONFIG_SEC_DEBUG)
sec_debug_dump_info(regs);
#if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO)
if (!user_mode(regs))
sec_debug_set_extra_info_fault((unsigned long)regs->pc, regs);
#endif
if (reset_delay)
reset_delay();
#endif
aee_nested_printf("num_die-%d, last_step-%d, next_step-%d\n",
num_die, last_step, next_step);
aee_exception_reboot(reboot_reason);
break;
}
return NOTIFY_DONE;
}
EXPORT_SYMBOL(mrdump_common_die);
#if IS_ENABLED(CONFIG_SEC_DEBUG)
extern void sec_upload_cause(void *buf);
#endif
int ipanic(struct notifier_block *this, unsigned long event, void *ptr)
{
crash_setup_regs(&saved_regs, NULL);
#if IS_ENABLED(CONFIG_SEC_DEBUG)
sec_upload_cause(ptr);
#endif
return mrdump_common_die(AEE_REBOOT_MODE_KERNEL_PANIC,
"Kernel Panic", &saved_regs);
}
static int ipanic_die(struct notifier_block *self, unsigned long cmd, void *ptr)
{
struct die_args *dargs = (struct die_args *)ptr;
#if IS_ENABLED(CONFIG_SEC_DEBUG)
sec_upload_cause((void *)(dargs->str));
#endif
return mrdump_common_die(AEE_REBOOT_MODE_KERNEL_OOPS,
"Kernel Oops", dargs->regs);
}
static struct notifier_block panic_blk = {
.notifier_call = ipanic,
};
static struct notifier_block die_blk = {
.notifier_call = ipanic_die,
};
static __init int mrdump_parse_chosen(struct mrdump_params *mparams)
{
struct device_node *node;
u32 reg[2];
const char *lkver, *ddr_rsv;
memset(mparams, 0, sizeof(struct mrdump_params));
node = of_find_node_by_path("/chosen");
if (node) {
if (of_property_read_u32_array(node, "mrdump,cblock",
reg, ARRAY_SIZE(reg)) == 0) {
mparams->cb_addr = reg[0];
mparams->cb_size = reg[1];
pr_notice("%s: mrdump_cbaddr=%pa, mrdump_cbsize=%pa\n",
__func__, &mparams->cb_addr, &mparams->cb_size);
}
if (of_property_read_string(node, "mrdump,lk", &lkver) == 0) {
strlcpy(mparams->lk_version, lkver,
sizeof(mparams->lk_version));
pr_notice("%s: lk version %s\n", __func__, lkver);
}
if (of_property_read_string(node, "mrdump,ddr_rsv",
&ddr_rsv) == 0) {
if (strcmp(ddr_rsv, "yes") == 0)
mparams->drm_ready = true;
pr_notice("%s: ddr reserve mode %s\n", __func__,
ddr_rsv);
}
return 0;
}
of_node_put(node);
pr_notice("%s: Can't find chosen node\n", __func__);
return -1;
}
#ifdef CONFIG_MODULES
/* Module notifier call back, update module info list */
static int mrdump_module_callback(struct notifier_block *nb,
unsigned long val, void *data)
{
struct module *mod = data;
if (val == MODULE_STATE_LIVE)
load_ko_addr_list(mod);
else if (val == MODULE_STATE_GOING)
unload_ko_addr_list(mod);
return NOTIFY_DONE;
}
static struct notifier_block mrdump_module_nb = {
.notifier_call = mrdump_module_callback,
};
#endif
static int __init mrdump_panic_init(void)
{
struct mrdump_params mparams = {};
struct device_node *rmem_node;
struct reserved_mem *rmem;
void *kinfo_vaddr;
if (!aee_is_enable()) {
pr_notice("%s: ipanic: mrdump is disable\n", __func__);
return 0;
}
/* Get reserved memory */
rmem_node = of_find_compatible_node(NULL, NULL, DEBUG_COMPATIBLE);
if (!rmem_node) {
pr_info("[mrdump] no node for reserved memory\n");
return -EINVAL;
}
rmem = of_reserved_mem_lookup(rmem_node);
if (!rmem) {
pr_info("[mrdump] cannot lookup reserved memory\n");
return -EINVAL;
}
pr_info("[mrdump] phys:0x%llx - 0x%llx (0x%llx)\n",
(unsigned long long)rmem->base,
(unsigned long long)rmem->base + (unsigned long long)rmem->size,
(unsigned long long)rmem->size);
kinfo_vaddr = memremap(rmem->base, rmem->size, MEMREMAP_WB);
if (!kinfo_vaddr) {
pr_info("[mrdump] failed to map debug-kinfo\n");
return -ENOMEM;
} else {
memset(kinfo_vaddr, 0, sizeof(struct kernel_all_info));
rmem->priv = kinfo_vaddr;
pr_info("[mrdump] rmem->priv = %px\n", rmem->priv);
}
mrdump_parse_chosen(&mparams);
#ifdef MODULE
mrdump_module_init_mboot_params();
#endif
mrdump_cblock_init(&mparams);
if (mrdump_cblock == NULL) {
pr_notice("%s: MT-RAMDUMP no control block\n", __func__);
return -EINVAL;
}
mrdump_mini_init(&mparams);
#ifdef MODULE
mrdump_mini_add_misc_pa((unsigned long)rmem->priv, rmem->base,
rmem->size, 0, MRDUMP_MINI_MISC_LOAD);
mrdump_ka_init(rmem->priv);
#endif
atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
register_die_notifier(&die_blk);
#ifdef CONFIG_MODULES
register_module_notifier(&mrdump_module_nb);
#endif
pr_debug("ipanic: startup\n");
return 0;
}
arch_initcall(mrdump_panic_init);
#ifdef MODULE
static void __exit mrdump_panic_exit(void)
{
atomic_notifier_chain_unregister(&panic_notifier_list, &panic_blk);
unregister_die_notifier(&die_blk);
#ifdef CONFIG_MODULES
unregister_module_notifier(&mrdump_module_nb);
#endif
pr_debug("ipanic: exit\n");
}
module_exit(mrdump_panic_exit);
#endif