Squashed backup commit (no shallow-clone history issues) of xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2 branch, upstream commit 9eece662e) plus all fixes applied to get everpal_defconfig building clean against a modern (GCC 15.2.0) toolchain -- see the previous commit message on the lineage-23.2 branch (also in this repo history is not available here since this is a squashed snapshot; full incremental history lives in the local DL380 checkout and the upstream GitHub remote). Upstream: https://github.com/xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2)
591 lines
14 KiB
C
591 lines
14 KiB
C
/*
|
|
* Copyright (C) 2015 MediaTek Inc.
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mm.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/memory.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/traps.h>
|
|
#include <asm/system_misc.h>
|
|
#include <asm/kexec.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <mrdump.h>
|
|
#include <linux/reboot.h>
|
|
#ifdef CONFIG_MTK_WATCHDOG
|
|
#include <mtk_wd_api.h>
|
|
#endif
|
|
#include "mrdump_private.h"
|
|
#include "mrdump_mini.h"
|
|
#include <mt-plat/mtk_ram_console.h>
|
|
|
|
/* for arm_smccc_smc */
|
|
#include <linux/arm-smccc.h>
|
|
#include <uapi/linux/psci.h>
|
|
|
|
static char mrdump_lk[12];
|
|
bool mrdump_ddr_reserve_ready;
|
|
|
|
void __weak sysrq_sched_debug_show_at_AEE(void)
|
|
{
|
|
pr_notice("%s weak function at %s\n", __func__, __FILE__);
|
|
}
|
|
|
|
__weak void console_unlock(void)
|
|
{
|
|
pr_notice("%s weak function\n", __func__);
|
|
}
|
|
|
|
static inline unsigned long get_linear_memory_size(void)
|
|
{
|
|
return (unsigned long)high_memory - PAGE_OFFSET;
|
|
}
|
|
|
|
/* no export symbol to aee_exception_reboot, only used in exception flow */
|
|
/* PSCI v1.1 extended power state encoding for SYSTEM_RESET2 function */
|
|
#define PSCI_1_1_FN_SYSTEM_RESET2 0x84000012
|
|
#define PSCI_1_1_RESET2_TYPE_VENDOR_SHIFT 31
|
|
#define PSCI_1_1_RESET2_TYPE_VENDOR \
|
|
(1 << PSCI_1_1_RESET2_TYPE_VENDOR_SHIFT)
|
|
|
|
static void aee_exception_reboot(void)
|
|
{
|
|
struct arm_smccc_res res;
|
|
int opt1 = 1, opt2 = 0;
|
|
|
|
arm_smccc_smc(PSCI_1_1_FN_SYSTEM_RESET2,
|
|
PSCI_1_1_RESET2_TYPE_VENDOR | opt1,
|
|
opt2, 0, 0, 0, 0, 0, &res);
|
|
}
|
|
|
|
|
|
/*save stack as binary into buf,
|
|
*return value
|
|
|
|
-1: bottom unaligned
|
|
-2: bottom out of kernel addr space
|
|
-3 top out of kernel addr addr
|
|
-4: buff len not enough
|
|
>0: used length of the buf
|
|
*/
|
|
int aee_dump_stack_top_binary(char *buf, int buf_len, unsigned long bottom,
|
|
unsigned long top)
|
|
{
|
|
/*should check stack address in kernel range */
|
|
if (bottom & 3)
|
|
return -1;
|
|
if (!((bottom >= (PAGE_OFFSET + THREAD_SIZE)) &&
|
|
(bottom <= (PAGE_OFFSET + get_linear_memory_size())))) {
|
|
if (!((bottom >= VMALLOC_START) && (bottom <= VMALLOC_END)))
|
|
return -2;
|
|
}
|
|
|
|
if (!((top >= (PAGE_OFFSET + THREAD_SIZE)) &&
|
|
(top <= (PAGE_OFFSET + get_linear_memory_size())))) {
|
|
if (!((top >= VMALLOC_START) && (top <= VMALLOC_END)))
|
|
return -3;
|
|
}
|
|
|
|
if (top > ALIGN(bottom, THREAD_SIZE))
|
|
top = ALIGN(bottom, THREAD_SIZE);
|
|
|
|
if (buf_len < top - bottom)
|
|
return -4;
|
|
|
|
memcpy((void *)buf, (void *)bottom, top - bottom);
|
|
|
|
return top - bottom;
|
|
}
|
|
|
|
void ipanic_recursive_ke(struct pt_regs *regs, struct pt_regs *excp_regs,
|
|
int cpu)
|
|
{
|
|
struct pt_regs saved_regs;
|
|
|
|
bust_spinlocks(1);
|
|
show_kaslr(false);
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
aee_rr_rec_exp_type(AEE_EXP_TYPE_NESTED_PANIC);
|
|
#endif
|
|
aee_nested_printf("minidump\n");
|
|
if (excp_regs != NULL) {
|
|
__mrdump_create_oops_dump(AEE_REBOOT_MODE_NESTED_EXCEPTION,
|
|
excp_regs, "Kernel NestedPanic");
|
|
} else if (regs != NULL) {
|
|
aee_nested_printf("previous excp_regs NULL\n");
|
|
__mrdump_create_oops_dump(AEE_REBOOT_MODE_NESTED_EXCEPTION,
|
|
regs, "Kernel NestedPanic");
|
|
} else {
|
|
aee_nested_printf("both NULL\n");
|
|
crash_setup_regs(&saved_regs, NULL);
|
|
__mrdump_create_oops_dump(AEE_REBOOT_MODE_NESTED_EXCEPTION,
|
|
&saved_regs, "Kernel NestedPanic");
|
|
}
|
|
mrdump_mini_ke_cpu_regs(excp_regs);
|
|
aee_exception_reboot();
|
|
}
|
|
EXPORT_SYMBOL(ipanic_recursive_ke);
|
|
|
|
__weak void aee_wdt_zap_locks(void)
|
|
{
|
|
pr_notice("%s:weak function\n", __func__);
|
|
}
|
|
|
|
int mrdump_common_die(int fiq_step, int reboot_reason, const char *msg,
|
|
struct pt_regs *regs)
|
|
{
|
|
bust_spinlocks(1);
|
|
aee_disable_api();
|
|
|
|
show_kaslr(true);
|
|
print_modules();
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
aee_rr_rec_fiq_step(fiq_step);
|
|
|
|
aee_rr_rec_scp();
|
|
#endif
|
|
__mrdump_create_oops_dump(reboot_reason, regs, msg);
|
|
mrdump_mini_ke_cpu_regs(regs);
|
|
switch (reboot_reason) {
|
|
case AEE_REBOOT_MODE_KERNEL_OOPS:
|
|
__show_regs(regs);
|
|
dump_stack();
|
|
break;
|
|
case AEE_REBOOT_MODE_KERNEL_PANIC:
|
|
#ifndef CONFIG_DEBUG_BUGVERBOSE
|
|
dump_stack();
|
|
#endif
|
|
break;
|
|
case AEE_REBOOT_MODE_HANG_DETECT:
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
aee_rr_rec_exp_type(AEE_EXP_TYPE_HANG_DETECT);
|
|
#endif
|
|
break;
|
|
default:
|
|
/* Don't print anything */
|
|
break;
|
|
}
|
|
|
|
aee_wdt_zap_locks();
|
|
console_unlock();
|
|
aee_exception_reboot();
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
int ipanic(struct notifier_block *this, unsigned long event, void *ptr)
|
|
{
|
|
struct pt_regs saved_regs;
|
|
int fiq_step = 0;
|
|
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
fiq_step = AEE_FIQ_STEP_KE_IPANIC_START;
|
|
aee_rr_rec_exp_type(AEE_EXP_TYPE_KE);
|
|
#endif
|
|
|
|
crash_setup_regs(&saved_regs, NULL);
|
|
return mrdump_common_die(fiq_step,
|
|
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;
|
|
int fiq_step = 0;
|
|
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
fiq_step = AEE_FIQ_STEP_KE_IPANIC_DIE;
|
|
aee_rr_rec_exp_type(AEE_EXP_TYPE_KE);
|
|
#endif
|
|
return mrdump_common_die(fiq_step,
|
|
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(void)
|
|
{
|
|
struct device_node *node;
|
|
u32 reg[2];
|
|
const char *lkver, *ddr_rsv;
|
|
|
|
node = of_find_node_by_path("/chosen");
|
|
if (node) {
|
|
if (of_property_read_u32_array(node, "mrdump,cblock",
|
|
reg, ARRAY_SIZE(reg)) == 0) {
|
|
mrdump_sram_cb.start_addr = reg[0];
|
|
mrdump_sram_cb.size = reg[1];
|
|
pr_notice("%s: mrdump_cbaddr=%llx, mrdump_cbsize=%llx\n",
|
|
__func__, mrdump_sram_cb.start_addr,
|
|
mrdump_sram_cb.size);
|
|
}
|
|
|
|
if (of_property_read_string(node, "mrdump,lk", &lkver) == 0) {
|
|
strlcpy(mrdump_lk, lkver, sizeof(mrdump_lk));
|
|
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)
|
|
mrdump_ddr_reserve_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)
|
|
{
|
|
if (val == MODULE_STATE_LIVE)
|
|
mrdump_modules_info(NULL, -1);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block mrdump_module_nb = {
|
|
.notifier_call = mrdump_module_callback,
|
|
};
|
|
#endif
|
|
|
|
static int __init mrdump_panic_init(void)
|
|
{
|
|
if (!aee_is_enable()) {
|
|
pr_notice("%s: ipanic: mrdump is disable\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
mrdump_parse_chosen();
|
|
|
|
mrdump_hw_init();
|
|
|
|
mrdump_cblock_init();
|
|
if (mrdump_cblock == NULL) {
|
|
memset(mrdump_lk, 0, sizeof(mrdump_lk));
|
|
pr_notice("%s: MT-RAMDUMP no control block\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mrdump_mini_init();
|
|
|
|
if (strcmp(mrdump_lk, MRDUMP_GO_DUMP) == 0) {
|
|
mrdump_full_init();
|
|
} else {
|
|
pr_notice("%s: Full ramdump disabled, version %s not matched.\n",
|
|
__func__, mrdump_lk);
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
aee_sram_fiq_log(nested_panic_buf);
|
|
#endif
|
|
|
|
return total_len;
|
|
}
|
|
|
|
static void print_error_msg(int len)
|
|
{
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
static char error_msg[][50] = { "Bottom unaligned",
|
|
"Bottom out of kernel addr",
|
|
"Top out of kernel addr", "Buf len not enough"
|
|
};
|
|
int tmp = (-len) - 1;
|
|
|
|
aee_sram_fiq_log(error_msg[tmp]);
|
|
#endif
|
|
}
|
|
|
|
/* extern void mt_fiq_printf(const char *fmt, ...); */
|
|
void *aee_excp_regs;
|
|
static atomic_t nested_panic_time = ATOMIC_INIT(0);
|
|
|
|
#ifdef __aarch64__
|
|
#define FORMAT_LONG "%016lx "
|
|
#else
|
|
#define FORMAT_LONG "%08lx "
|
|
#endif
|
|
inline void aee_print_regs(struct pt_regs *regs)
|
|
{
|
|
int i;
|
|
|
|
aee_nested_printf("[pt_regs]");
|
|
for (i = 0; i < ELF_NGREG; i++)
|
|
aee_nested_printf(FORMAT_LONG, ((unsigned long *)regs)[i]);
|
|
aee_nested_printf("\n");
|
|
}
|
|
|
|
#define AEE_MAX_EXCP_FRAME 64
|
|
inline void aee_print_bt(struct pt_regs *regs)
|
|
{
|
|
int i;
|
|
int ret;
|
|
unsigned long high, bottom, fp;
|
|
struct stackframe cur_frame;
|
|
|
|
bottom = regs->reg_sp;
|
|
if (!mrdump_virt_addr_valid(bottom)) {
|
|
aee_nested_printf("invalid sp[%lx]\n", (unsigned long)regs);
|
|
return;
|
|
}
|
|
memset(&cur_frame, 0, sizeof(cur_frame));
|
|
high = ALIGN(bottom, THREAD_SIZE);
|
|
cur_frame.fp = regs->reg_fp;
|
|
cur_frame.pc = regs->reg_pc;
|
|
#ifndef __aarch64__
|
|
cur_frame.sp = regs->reg_sp;
|
|
#endif
|
|
aee_nested_printf("\n[<%px>] %pS\n", (void *)cur_frame.pc,
|
|
(void *)cur_frame.pc);
|
|
aee_nested_printf("[<%px>] %pS\n", (void *)regs->reg_lr,
|
|
(void *)regs->reg_lr);
|
|
for (i = 0; i < AEE_MAX_EXCP_FRAME; i++) {
|
|
fp = cur_frame.fp;
|
|
if ((fp < bottom) || (fp >= (high + THREAD_SIZE))) {
|
|
if (fp != 0)
|
|
aee_nested_printf("fp(%lx)", fp);
|
|
break;
|
|
}
|
|
#ifdef __aarch64__
|
|
ret = unwind_frame(current, &cur_frame);
|
|
#else
|
|
ret = unwind_frame(&cur_frame);
|
|
#endif
|
|
if (ret < 0 || !mrdump_virt_addr_valid(cur_frame.pc))
|
|
break;
|
|
aee_nested_printf("[<%px>] %pS\n", (void *)cur_frame.pc,
|
|
(void *)cur_frame.pc);
|
|
|
|
}
|
|
aee_nested_printf("\n");
|
|
}
|
|
|
|
inline int aee_nested_save_stack(struct pt_regs *regs)
|
|
{
|
|
int len = 0;
|
|
|
|
if (!mrdump_virt_addr_valid(regs->reg_sp))
|
|
return -1;
|
|
aee_nested_printf("[%lx %lx]\n", (unsigned long)regs->reg_sp,
|
|
(unsigned long)regs->reg_sp + 256);
|
|
|
|
len = aee_dump_stack_top_binary(nested_panic_buf,
|
|
sizeof(nested_panic_buf), regs->reg_sp, regs->reg_sp + 256);
|
|
if (len > 0)
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
aee_sram_fiq_save_bin(nested_panic_buf, len);
|
|
#else
|
|
;
|
|
#endif
|
|
else
|
|
print_error_msg(len);
|
|
return len;
|
|
}
|
|
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
int aee_in_nested_panic(void)
|
|
{
|
|
return (atomic_read(&nested_panic_time) &&
|
|
((aee_rr_curr_fiq_step() & ~(AEE_FIQ_STEP_KE_NESTED_PANIC - 1))
|
|
== AEE_FIQ_STEP_KE_NESTED_PANIC));
|
|
}
|
|
static inline void aee_rec_step_nested_panic(int step)
|
|
{
|
|
if (step < 64)
|
|
aee_rr_rec_fiq_step(AEE_FIQ_STEP_KE_NESTED_PANIC + step);
|
|
}
|
|
#else
|
|
int aee_in_nested_panic(void)
|
|
{
|
|
return -1;
|
|
}
|
|
static inline void aee_rec_step_nested_panic(int step)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#define TS_MAX_LEN 64
|
|
static const char *get_timestamp_string(char *buf, int bufsize)
|
|
{
|
|
u64 ts;
|
|
unsigned long rem_nsec;
|
|
int n;
|
|
|
|
ts = local_clock();
|
|
rem_nsec = do_div(ts, 1000000000);
|
|
n = snprintf(buf, bufsize, "[%5lu.%06lu]",
|
|
(unsigned long)ts, rem_nsec / 1000);
|
|
if (n < 0 || n >= bufsize) {
|
|
pr_info("print time failed\n");
|
|
*buf = '\0';
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
asmlinkage void aee_save_excp_regs(struct pt_regs *regs)
|
|
{
|
|
if (!user_mode(regs))
|
|
aee_excp_regs = regs;
|
|
}
|
|
|
|
asmlinkage void aee_stop_nested_panic(struct pt_regs *regs)
|
|
{
|
|
struct thread_info *thread = current_thread_info();
|
|
int len = 0;
|
|
int timeout = 1000000;
|
|
int cpu;
|
|
#ifdef CONFIG_MTK_WATCHDOG
|
|
int res = 0;
|
|
struct wd_api *wd_api = NULL;
|
|
#endif
|
|
struct pt_regs *excp_regs = NULL;
|
|
int prev_fiq_step = aee_rr_curr_fiq_step();
|
|
/* everytime enter nested_panic flow, add 8 */
|
|
static int step_base = -8;
|
|
char tsbuf[TS_MAX_LEN] = {0};
|
|
|
|
step_base = step_base < 48 ? step_base + 8 : 56;
|
|
|
|
aee_rec_step_nested_panic(step_base);
|
|
local_irq_disable();
|
|
aee_rec_step_nested_panic(step_base + 1);
|
|
cpu = get_HW_cpuid();
|
|
aee_rec_step_nested_panic(step_base + 2);
|
|
/*nested panic may happens more than once on many/single cpus */
|
|
if (atomic_read(&nested_panic_time) < 3)
|
|
aee_nested_printf("\nCPU%dpanic%d@%d-%s\n", cpu,
|
|
nested_panic_time.counter, prev_fiq_step,
|
|
get_timestamp_string(tsbuf, TS_MAX_LEN));
|
|
atomic_inc(&nested_panic_time);
|
|
|
|
switch (atomic_read(&nested_panic_time)) {
|
|
case 2:
|
|
aee_print_regs(regs);
|
|
aee_nested_printf("backtrace:");
|
|
aee_print_bt(regs);
|
|
break;
|
|
|
|
/* must guarantee Only one cpu can run here */
|
|
/* first check if thread valid */
|
|
case 1:
|
|
if (mrdump_virt_addr_valid(thread)
|
|
&& mrdump_virt_addr_valid(thread->regs_on_excp)) {
|
|
excp_regs = thread->regs_on_excp;
|
|
} else {
|
|
/* if thread invalid, which means wrong sp or
|
|
* thread_info corrupted,
|
|
* check global aee_excp_regs instead
|
|
*/
|
|
aee_nested_printf(
|
|
"invalid thread [%lx], excp_regs [%lx]\n",
|
|
(unsigned long)thread,
|
|
(unsigned long)aee_excp_regs);
|
|
excp_regs = aee_excp_regs;
|
|
}
|
|
aee_nested_printf("Nested panic\n");
|
|
if (excp_regs) {
|
|
aee_nested_printf("Previous\n");
|
|
aee_print_regs(excp_regs);
|
|
}
|
|
aee_nested_printf("Current\n");
|
|
aee_print_regs(regs);
|
|
|
|
/*Dump first panic stack */
|
|
aee_nested_printf("Previous\n");
|
|
if (excp_regs) {
|
|
len = aee_nested_save_stack(excp_regs);
|
|
aee_nested_printf("\nbacktrace:");
|
|
aee_print_bt(excp_regs);
|
|
}
|
|
|
|
/*Dump second panic stack */
|
|
aee_nested_printf("Current\n");
|
|
if (mrdump_virt_addr_valid(regs)) {
|
|
len = aee_nested_save_stack(regs);
|
|
aee_nested_printf("\nbacktrace:");
|
|
aee_print_bt(regs);
|
|
}
|
|
|
|
aee_rec_step_nested_panic(step_base + 5);
|
|
ipanic_recursive_ke(regs, excp_regs, cpu);
|
|
|
|
aee_rec_step_nested_panic(step_base + 6);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* we donot want a FIQ after this, so disable hwt */
|
|
#ifdef CONFIG_MTK_WATCHDOG
|
|
res = get_wd_api(&wd_api);
|
|
|
|
if (res)
|
|
aee_nested_printf("get_wd_api error\n");
|
|
else
|
|
wd_api->wd_aee_confirm_hwreboot();
|
|
#else
|
|
aee_nested_printf("mtk watchdog not enable.\n");
|
|
#endif
|
|
aee_rec_step_nested_panic(step_base + 7);
|
|
|
|
/* waiting for the WDT timeout */
|
|
while (1) {
|
|
/* output to UART directly to avoid printk nested panic */
|
|
/* mt_fiq_printf("%s hang here%d\t", __func__, i++); */
|
|
while (timeout--)
|
|
udelay(1);
|
|
timeout = 1000000;
|
|
}
|
|
}
|