/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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(""); 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);