kernel_samsung_a34x-permissive/drivers/samsung/debug/sec_debug.c

705 lines
18 KiB
C
Raw Normal View History

/*
* Copyright (c) 2022 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.
*/
#include <asm/cacheflush.h>
#include <asm/io.h>
#include <asm/stacktrace.h>
#include <linux/bootmem.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>
#include <linux/kernel_stat.h>
#include <linux/kexec.h>
#include <linux/kmsg_dump.h>
#include <linux/memblock.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/ptrace.h>
#include <linux/reboot.h>
#include <linux/sec_debug.h>
#include <linux/tick.h>
#include <linux/uaccess.h>
#include <linux/sched/mm.h>
#include <linux/sched/debug.h>
#include <linux/nmi.h>
#include <linux/sec_hard_reset_hook.h>
/* get time function to log */
#ifndef arch_irq_stat_cpu
#define arch_irq_stat_cpu(cpu) 0
#endif
#ifndef arch_irq_stat
#define arch_irq_stat() 0
#endif
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
static u32 sec_extra_info_base;
static u32 sec_extra_info_size = SZ_64K;
#endif
DEFINE_PER_CPU(unsigned char, coreregs_stored);
DEFINE_PER_CPU(struct pt_regs, sec_aarch64_core_reg);
DEFINE_PER_CPU(sec_debug_mmu_reg_t, sec_aarch64_mmu_reg);
union sec_debug_level_t sec_debug_level = { .en.kernel_fault = 1, };
module_param_named(enable, sec_debug_level.en.kernel_fault, ushort, 0644);
module_param_named(enable_user, sec_debug_level.en.user_fault, ushort, 0644);
module_param_named(level, sec_debug_level.uint_val, uint, 0644);
void sec_debug_save_core_reg(void *v_regs)
{
struct pt_regs *regs = (struct pt_regs *)v_regs;
struct pt_regs *core_reg =
&per_cpu(sec_aarch64_core_reg, smp_processor_id());
if (!regs) {
asm volatile (
"stp x0, x1, [%0, #8 * 0]\n\t"
"stp x2, x3, [%0, #8 * 2]\n\t"
"stp x4, x5, [%0, #8 * 4]\n\t"
"stp x6, x7, [%0, #8 * 6]\n\t"
"stp x8, x9, [%0, #8 * 8]\n\t"
"stp x10, x11, [%0, #8 * 10]\n\t"
"stp x12, x13, [%0, #8 * 12]\n\t"
"stp x14, x15, [%0, #8 * 14]\n\t"
"stp x16, x17, [%0, #8 * 16]\n\t"
"stp x18, x19, [%0, #8 * 18]\n\t"
"stp x20, x21, [%0, #8 * 20]\n\t"
"stp x22, x23, [%0, #8 * 22]\n\t"
"stp x24, x25, [%0, #8 * 24]\n\t"
"stp x26, x27, [%0, #8 * 26]\n\t"
"stp x28, x29, [%0, #8 * 28]\n\t"
"stp x5, x6, [sp,#-32]!\n\t"
"str x7, [sp,#16]\n\t"
"mov x5, sp\n\t"
"stp x30, x5, [%0, #8 * 30]\n\t"
"adr x6, 1f\n\t"
"mrs x7, spsr_el1\n\t"
"stp x6, x7, [%0, #8 * 32]\n\t"
"ldr x7, [sp,#16]\n\t"
"ldp x5, x6, [sp],#32\n\t"
"1:"
:
: "r" (&core_reg->regs[0])
: "memory"
);
} else {
memcpy(core_reg, regs, sizeof(struct pt_regs));
}
return;
}
void sec_debug_save_mmu_reg(sec_debug_mmu_reg_t *mmu_reg)
{
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" : /* output */
: "r"(mmu_reg) /* input */
: "%x1", "memory" /* clobbered register */
);
}
void sec_debug_init_mmu(void *unused)
{
sec_debug_save_mmu_reg(&per_cpu(sec_aarch64_mmu_reg, raw_smp_processor_id()));
}
int sec_save_context(int cpu, void *v_regs)
{
unsigned long flags;
struct pt_regs *regs = (struct pt_regs *)v_regs;
//struct pt_regs tmp;
if (cpu == _THIS_CPU) {
if (__this_cpu_read(coreregs_stored)) {
pr_emerg("skip context saved(CPU:%d)\n", smp_processor_id());
return 0;
}
} else {
if (per_cpu(coreregs_stored, cpu)) {
pr_emerg("skip context saved(CPU:%d)\n", cpu);
return 0;
}
}
local_irq_save(flags);
sec_debug_save_core_reg(regs);
if (cpu == _THIS_CPU) {
sec_debug_save_mmu_reg(&per_cpu(sec_aarch64_mmu_reg, smp_processor_id()));
} else {
sec_debug_save_mmu_reg(&per_cpu(sec_aarch64_mmu_reg, cpu));
}
//crash_setup_regs(&tmp, regs);
//crash_save_vmcoreinfo();
if (cpu == _THIS_CPU) {
//crash_save_cpu(&tmp, smp_processor_id());
__this_cpu_inc(coreregs_stored);
pr_emerg("context saved(CPU:%d)\n", smp_processor_id());
} else {
//crash_save_cpu(&tmp, cpu);
++per_cpu(coreregs_stored, cpu);
pr_emerg("context saved(CPU:%d)\n", cpu);
}
__inner_flush_dcache_all();
local_irq_restore(flags);
return 0;
}
static int __init sec_debug_levelset(char *str)
{
int level;
if (get_option(&str, &level)) {
memcpy(&sec_debug_level, &level, sizeof(sec_debug_level));
printk(KERN_INFO "sec_debug_level:0x%x\n", sec_debug_level.uint_val);
return 0;
}
return -EINVAL;
}
early_param("sec_debug", sec_debug_levelset);
int sec_debug_get_debug_level(void)
{
return sec_debug_level.uint_val;
}
static void sec_debug_user_fault_dump(void)
{
if (SEC_DEBUG_LEVEL(kernel) == 1
&& SEC_DEBUG_LEVEL(user) == 1)
panic("User Fault");
}
static ssize_t
sec_debug_user_fault_write(struct file *file, const char __user *buffer,
size_t count, loff_t *offs)
{
char buf[100];
if (count > sizeof(buf) - 1)
return -EINVAL;
if (copy_from_user(buf, buffer, count))
return -EFAULT;
buf[count] = '\0';
if (strncmp(buf, "dump_user_fault", 15) == 0)
sec_debug_user_fault_dump();
return count;
}
static const struct file_operations sec_debug_user_fault_proc_fops = {
.write = sec_debug_user_fault_write,
};
static int __init sec_debug_user_fault_init(void)
{
struct proc_dir_entry *entry;
entry = proc_create("user_fault", S_IWUSR | S_IWGRP, NULL,
&sec_debug_user_fault_proc_fops);
if (!entry)
return -ENOMEM;
return 0;
}
device_initcall(sec_debug_user_fault_init);
void sec_debug_check_crash_key(unsigned int code, int value)
{
static bool volup_p;
static bool voldown_p;
static int loopcount;
if (!SEC_DEBUG_LEVEL(kernel))
return;
pr_debug("%s: code is %d, value is %d\n", __func__, code, value);
/* Enter Force Upload
* Hold volume down key first
* and then press power key twice
* and volume up key should not be pressed
*/
if (value) {
if (code == KEY_VOLUMEUP)
volup_p = true;
if (code == KEY_VOLUMEDOWN)
voldown_p = true;
if (!volup_p && voldown_p) {
if (code == KEY_POWER) {
pr_info
("%s: count for enter forced upload : %d\n",
__func__, ++loopcount);
if (loopcount == 2)
panic("Crash Key");
}
}
} else {
if (code == KEY_VOLUMEUP) {
volup_p = false;
}
if (code == KEY_VOLUMEDOWN) {
loopcount = 0;
voldown_p = false;
}
}
}
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
static void sec_debug_set_extra_info_upload(char *str)
{
if (str) {
sec_debug_set_extra_info_panic(str);
sec_debug_finish_extra_info();
}
}
#endif
void sec_upload_cause(void *buf)
{
LAST_RR_SET(is_upload, UPLOAD_MAGIC_UPLOAD);
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
sec_debug_set_extra_info_upload(buf);
#endif
if (!strncmp(buf, "User Fault", 10))
LAST_RR_SET(upload_reason, UPLOAD_CAUSE_USER_FAULT);
else if (is_hard_reset_occurred())
LAST_RR_SET(upload_reason, UPLOAD_CAUSE_FORCED_UPLOAD);
else if (!strncmp(buf, "Crash Key", 9))
LAST_RR_SET(upload_reason, UPLOAD_CAUSE_FORCED_UPLOAD);
else if (!strncmp(buf, "User Crash Key", 14))
LAST_RR_SET(upload_reason, UPLOAD_CAUSE_USER_FORCED_UPLOAD);
else if (!strncmp(buf, "CP Crash", 8))
LAST_RR_SET(upload_reason, UPLOAD_CAUSE_CP_ERROR_FATAL);
else if (!strncmp(buf, "Watchdog", 8))
LAST_RR_SET(upload_reason, UPLOAD_CAUSE_WATCHDOG);
else
LAST_RR_SET(upload_reason, UPLOAD_CAUSE_KERNEL_PANIC);
LAST_RR_MEMCPY(panic_str, buf, PANIC_STRBUF_LEN);
}
static void sec_dump_one_task_info(struct task_struct *tsk, bool is_main)
{
char state_array[] = {'R', 'S', 'D', 'T', 't', 'X', 'Z', 'P', 'x', 'K', 'W', 'I', 'N'};
unsigned char idx = 0;
unsigned long state;
unsigned long wchan;
unsigned long pc = 0;
char symname[KSYM_NAME_LEN];
if ((tsk == NULL) || !try_get_task_stack(tsk))
return;
state = tsk->state | tsk->exit_state;
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;
}
touch_softlockup_watchdog();
pr_info("%8d %8d %8d %16lld %c(%d) %3d %16lx %16lx %16lx %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_WAKING ||
task_contributes_to_load(tsk)) {
print_worker_info(KERN_INFO, tsk);
if (tsk->on_cpu && tsk->on_rq &&
tsk->cpu != smp_processor_id())
return;
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);
}
static void sec_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(ms) sTime(ms) 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) {
sec_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) {
sec_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");
}
static void sec_dump_irq_info(void)
{
int i, j;
unsigned long long 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");
pr_info("<irq info>");
pr_info("------------------------------------------------------------------\n");
pr_info("sum irq : %llu", 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(hwirq-%-4d) : %8u %s\n",
j, (int)desc->irq_data.hwirq, irq_stat, name);
}
}
pr_info("------------------------------------------------------------------\n");
}
extern void smp_send_stop(void);
extern void hard_reset_delay(void);
void sec_debug_dump_info(void)
{
smp_send_stop();
sec_dump_task_info();
sec_dump_irq_info();
hard_reset_delay();
}
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
static int sec_debug_check_magic(struct sec_debug_shared_info *sdi)
{
if (sdi->magic[0] != SEC_DEBUG_SHARED_MAGIC0) {
pr_crit("%s: wrong magic 0: %x|%x\n",
__func__, sdi->magic[0], SEC_DEBUG_SHARED_MAGIC0);
return 0;
}
if (sdi->magic[1] != SEC_DEBUG_SHARED_MAGIC1) {
pr_crit("%s: wrong magic 1: %x|%x\n",
__func__, sdi->magic[1], SEC_DEBUG_SHARED_MAGIC1);
return 0;
}
if (sdi->magic[2] != SEC_DEBUG_SHARED_MAGIC2) {
pr_crit("%s: wrong magic 2: %x|%x\n",
__func__, sdi->magic[2], SEC_DEBUG_SHARED_MAGIC2);
return 0;
}
if (sdi->magic[3] != SEC_DEBUG_SHARED_MAGIC3) {
pr_crit("%s: wrong magic 3: %x|%x\n",
__func__, sdi->magic[3], SEC_DEBUG_SHARED_MAGIC3);
return 0;
}
return 1;
}
static struct sec_debug_shared_info *sec_debug_info;
#ifdef CONFIG_SEC_DEBUG_HIST_LOG
extern void sec_debug_hist_base_init(int extrainfo_base);
#endif
static void sec_debug_init_base_buffer(void)
{
int magic_status = 0;
struct reserved_mem *rmem;
rmem = sec_log_get_rmem("samsung,sec-extrainfo");
sec_extra_info_base = rmem->base;
sec_debug_info = (struct sec_debug_shared_info *)phys_to_virt(sec_extra_info_base);
if (sec_debug_info) {
if (sec_debug_check_magic(sec_debug_info))
magic_status = 1;
sec_debug_info->magic[0] = SEC_DEBUG_SHARED_MAGIC0;
sec_debug_info->magic[1] = SEC_DEBUG_SHARED_MAGIC1;
sec_debug_info->magic[2] = SEC_DEBUG_SHARED_MAGIC2;
sec_debug_info->magic[3] = SEC_DEBUG_SHARED_MAGIC3;
//sec_debug_set_kallsyms_info(sec_debug_info);
sec_debug_init_extra_info(sec_debug_info, sec_extra_info_size, magic_status);
}
pr_info("%s, base(virt):0x%lx size:0x%x\n", __func__, (unsigned long)sec_debug_info, sec_extra_info_size);
#ifdef CONFIG_SEC_DEBUG_HIST_LOG
sec_debug_hist_base_init(sec_extra_info_base);
#endif
}
#endif
#if 0
static void sec_free_dbg_mem(phys_addr_t base, phys_addr_t size)
{
memblock_free_late(base, size);
pr_info("%s: freed memory [%#016llx-%#016llx]\n",
__func__, (u64)base, (u64)base + size - 1);
}
#endif
static int __init sec_debug_init(void)
{
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
sec_debug_init_base_buffer();
#endif
smp_call_function(sec_debug_init_mmu, NULL, 1);
sec_debug_init_mmu(NULL);
if (SEC_DEBUG_LEVEL(kernel)) {
LAST_RR_SET(mcupm_skip, UPLOAD_MAGIC_UPLOAD);
//sec_free_dbg_mem(LK_RSV_ADDR, LK_RSV_SIZE);
}
return 0;
}
postcore_initcall(sec_debug_init);
unsigned int reset_reason = RR_N;
module_param_named(reset_reason, reset_reason, uint, 0644);
static int set_reset_reason_proc_show(struct seq_file *m, void *v)
{
if (reset_reason == RR_S)
seq_puts(m, "SPON\n");
else if (reset_reason == RR_W)
seq_puts(m, "WPON\n");
else if (reset_reason == RR_D)
seq_puts(m, "DPON\n");
else if (reset_reason == RR_K)
seq_puts(m, "KPON\n");
else if (reset_reason == RR_M)
seq_puts(m, "MPON\n");
else if (reset_reason == RR_P)
seq_puts(m, "PPON\n");
else if (reset_reason == RR_R)
seq_puts(m, "RPON\n");
else if (reset_reason == RR_B)
seq_puts(m, "BPON\n");
else if (reset_reason == RR_T)
seq_puts(m, "TPON\n");
else if (reset_reason == RR_C)
seq_puts(m, "CPON\n");
else
seq_puts(m, "NPON\n");
return 0;
}
static int sec_reset_reason_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, set_reset_reason_proc_show, NULL);
}
static const struct file_operations sec_reset_reason_proc_fops = {
.open = sec_reset_reason_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int sec_debug_reset_reason_store_lastkmsg_proc_show(struct seq_file *m, void *v)
{
if (reset_reason == RR_K || reset_reason == RR_D || reset_reason == RR_P)
seq_puts(m, "1\n");
else
seq_puts(m, "0\n");
return 0;
}
static int sec_debug_reset_reason_store_lastkmsg_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, sec_debug_reset_reason_store_lastkmsg_proc_show, NULL);
}
static const struct file_operations sec_debug_reset_reason_store_lastkmsg_proc_fops = {
.open = sec_debug_reset_reason_store_lastkmsg_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
/*
* proc/extra
* RSTCNT (32) + PC (256) + LR (256) (MAX: 544)
* + BUG (256) + PANIC (256) (MAX: 512)
*/
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
extern struct sec_debug_panic_extra_info *sec_debug_extra_info_backup;
static int sec_debug_reset_reason_extra_show(struct seq_file *m, void *v)
{
ssize_t size = 0;
char buf[SZ_1K];
if (!sec_debug_extra_info_backup)
return -ENOENT;
size += scnprintf((char *)(buf + size), SZ_1K - size,
"%s: %s ",
sec_debug_extra_info_backup->item[INFO_RSTCNT].key,
sec_debug_extra_info_backup->item[INFO_RSTCNT].val);
size += scnprintf((char *)(buf + size), SZ_1K - size,
"%s: %s ",
sec_debug_extra_info_backup->item[INFO_PC].key,
sec_debug_extra_info_backup->item[INFO_PC].val);
size += scnprintf((char *)(buf + size), SZ_1K - size,
"%s: %s ",
sec_debug_extra_info_backup->item[INFO_LR].key,
sec_debug_extra_info_backup->item[INFO_LR].val);
size += scnprintf((char *)(buf + size), SZ_1K - size,
"%s: %s ",
sec_debug_extra_info_backup->item[INFO_BUG].key,
sec_debug_extra_info_backup->item[INFO_BUG].val);
size += scnprintf((char *)(buf + size), SZ_1K - size,
"%s: %s ",
sec_debug_extra_info_backup->item[INFO_PANIC].key,
sec_debug_extra_info_backup->item[INFO_PANIC].val);
seq_printf(m, buf);
return 0;
}
static int sec_debug_reset_reason_extra_open(struct inode *inode, struct file *file)
{
return single_open(file, sec_debug_reset_reason_extra_show, NULL);
}
static const struct file_operations sec_debug_reset_reason_extra_proc_fops = {
.open = sec_debug_reset_reason_extra_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
static int __init sec_debug_reset_reason_init(void)
{
struct proc_dir_entry *entry;
entry = proc_create("reset_reason", 0400, NULL,
&sec_reset_reason_proc_fops);
if (!entry)
return -ENOMEM;
entry = proc_create("store_lastkmsg", 0400, NULL,
&sec_debug_reset_reason_store_lastkmsg_proc_fops);
if (!entry)
return -ENOMEM;
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
entry = proc_create("extra", 0400, NULL,
&sec_debug_reset_reason_extra_proc_fops);
if (!entry)
return -ENOMEM;
#endif
return 0;
}
device_initcall(sec_debug_reset_reason_init);