| /* |
| * Copyright (c) 2018 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 as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/delay.h> |
| #include <linux/cpu.h> |
| #include <linux/cpu_pm.h> |
| #include <linux/module.h> |
| #include <linux/kallsyms.h> |
| #include <linux/of.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/uaccess.h> |
| #include <linux/ctype.h> |
| #include <linux/nmi.h> |
| #include <linux/workqueue.h> |
| #include <linux/spinlock.h> |
| #include <linux/debug-snapshot.h> |
| #include <linux/debug-snapshot-helper.h> |
| #include <asm-generic/io.h> |
| |
| #include <soc/samsung/exynos-pmu.h> |
| #include <soc/samsung/exynos-debug.h> |
| |
| #define EXYNOS_DEBUG_TEST_END (0xCAFEDB9) |
| #define ALL_FORCE_ERRORS 31 |
| |
| typedef void (*force_error_func)(char *arg); |
| |
| static void simulate_KP(char *arg); |
| static void simulate_DP(char *arg); |
| static void simulate_QDP(char *arg); |
| static void simulate_SVC(char *arg); |
| static void simulate_SFR(char *arg); |
| static void simulate_WP(char *arg); |
| static void simulate_TP(char *arg); |
| static void simulate_PANIC(char *arg); |
| static void simulate_BUG(char *arg); |
| static void simulate_WARN(char *arg); |
| static void simulate_DABRT(char *arg); |
| static void simulate_PABRT(char *arg); |
| static void simulate_UNDEF(char *arg); |
| static void simulate_DFREE(char *arg); |
| static void simulate_DREF(char *arg); |
| static void simulate_MCRPT(char *arg); |
| static void simulate_LOMEM(char *arg); |
| static void simulate_SOFT_LOCKUP(char *arg); |
| static void simulate_HARD_LOCKUP(char *arg); |
| static void simulate_BAD_SCHED(char *arg); |
| static void simulate_SPIN_LOCKUP(char *arg); |
| static void simulate_PC_ABORT(char *arg); |
| static void simulate_SP_ABORT(char *arg); |
| static void simulate_JUMP_ZERO(char *arg); |
| static void simulate_BUSMON_ERROR(char *arg); |
| static void simulate_UNALIGNED(char *arg); |
| static void simulate_WRITE_RO(char *arg); |
| static void simulate_OVERFLOW(char *arg); |
| static void simulate_test_start(char *arg); |
| |
| static int exynos_debug_test_desc_init(struct device_node *np); |
| |
| enum { |
| FORCE_KERNEL_PANIC = 0, /* KP */ |
| FORCE_WATCHDOG, /* DP */ |
| FORCE_QUICKWATCHDOG, /* QDP */ |
| FORCE_SVC, /* SVC */ |
| FORCE_SFR, /* SFR */ |
| FORCE_WARM_RESET, /* WP */ |
| FORCE_HW_TRIPPING, /* TP */ |
| FORCE_PANIC, /* PANIC */ |
| FORCE_BUG, /* BUG */ |
| FORCE_WARN, /* WARN */ |
| FORCE_DATA_ABORT, /* DABRT */ |
| FORCE_PREFETCH_ABORT, /* PABRT */ |
| FORCE_UNDEFINED_INSTRUCTION, /* UNDEF */ |
| FORCE_DOUBLE_FREE, /* DFREE */ |
| FORCE_DANGLING_REFERENCE, /* DREF */ |
| FORCE_MEMORY_CORRUPTION, /* MCRPT */ |
| FORCE_LOW_MEMEMORY, /* LOMEM */ |
| FORCE_SOFT_LOCKUP, /* SOFT LOCKUP */ |
| FORCE_HARD_LOCKUP, /* HARD LOCKUP */ |
| FORCE_SPIN_LOCKUP, /* SPIN LOCKUP */ |
| FORCE_PC_ABORT, /* PC ABORT */ |
| FORCE_SP_ABORT, /* SP ABORT */ |
| FORCE_JUMP_ZERO, /* JUMP TO ZERO */ |
| FORCE_BUSMON_ERROR, /* BUSMON ERROR */ |
| FORCE_UNALIGNED, /* UNALIGNED WRITE */ |
| FORCE_WRITE_RO, /* WRITE RODATA */ |
| FORCE_OVERFLOW, /* STACK OVERFLOW */ |
| FORCE_BAD_SCHEDULING, /* BAD SCHED */ |
| FORCE_TEST_START, /* START TEST */ |
| NR_FORCE_ERROR, |
| }; |
| |
| enum { |
| REBOOT_REASON_WTSR = 0x1, |
| REBOOT_REASON_SMPL, |
| REBOOT_REASON_WDT, |
| REBOOT_REASON_PANIC, |
| REBOOT_REASON_NA, |
| }; |
| |
| struct debug_test_desc { |
| int enabled; |
| int nr_cpu; |
| int nr_little_cpu; |
| int nr_mid_cpu; |
| int nr_big_cpu; |
| int little_cpu_start; |
| int mid_cpu_start; |
| int big_cpu_start; |
| unsigned int ps_hold_control_offset; |
| unsigned int *null_pointer_ui; |
| int *null_pointer_si; |
| void (*null_function)(void); |
| struct dentry *exynos_debug_test_debugfs_root; |
| struct device_node *np; |
| spinlock_t debug_test_lock; |
| struct delayed_work test_work; |
| }; |
| |
| struct force_error_item { |
| char errname[SZ_32]; |
| force_error_func errfunc; |
| }; |
| |
| struct force_error_test_item { |
| char arg[SZ_128]; |
| int enabled; |
| int reason; |
| }; |
| |
| struct force_error { |
| struct force_error_item item[NR_FORCE_ERROR]; |
| }; |
| |
| static struct debug_test_desc exynos_debug_desc = { 0, }; |
| |
| static struct force_error force_error_vector = { |
| .item = { |
| {"KP", &simulate_KP}, |
| {"DP", &simulate_DP}, |
| {"QDP", &simulate_QDP}, |
| {"SVC", &simulate_SVC}, |
| {"SFR", &simulate_SFR}, |
| {"WP", &simulate_WP}, |
| {"TP", &simulate_TP}, |
| {"panic", &simulate_PANIC}, |
| {"bug", &simulate_BUG}, |
| {"warn", &simulate_WARN}, |
| {"dabrt", &simulate_DABRT}, |
| {"pabrt", &simulate_PABRT}, |
| {"undef", &simulate_UNDEF}, |
| {"dfree", &simulate_DFREE}, |
| {"danglingref", &simulate_DREF}, |
| {"memcorrupt", &simulate_MCRPT}, |
| {"lowmem", &simulate_LOMEM}, |
| {"softlockup", &simulate_SOFT_LOCKUP}, |
| {"hardlockup", &simulate_HARD_LOCKUP}, |
| {"spinlockup", &simulate_SPIN_LOCKUP}, |
| {"pcabort", &simulate_PC_ABORT}, |
| {"spabort", &simulate_SP_ABORT}, |
| {"jumpzero", &simulate_JUMP_ZERO}, |
| {"busmon", &simulate_BUSMON_ERROR}, |
| {"unaligned", &simulate_UNALIGNED}, |
| {"writero", &simulate_WRITE_RO}, |
| {"overflow", &simulate_OVERFLOW}, |
| {"badsched", &simulate_BAD_SCHED}, |
| {"all", &simulate_test_start}, |
| } |
| }; |
| |
| static unsigned int test_state; |
| |
| static struct force_error_test_item test_vector[] = { |
| {"KP", 1, REBOOT_REASON_PANIC}, |
| {"SVC", 1, REBOOT_REASON_PANIC}, |
| {"SFR 0x1ffffff0", 1, REBOOT_REASON_PANIC}, |
| {"SFR 0x1ffffff0 0x12345678", 1, REBOOT_REASON_PANIC}, |
| {"WP", 1, REBOOT_REASON_WTSR}, |
| {"panic", 1, REBOOT_REASON_PANIC}, |
| {"bug", 1, REBOOT_REASON_PANIC}, |
| {"dabrt", 1, REBOOT_REASON_PANIC}, |
| {"pabrt", 1, REBOOT_REASON_PANIC}, |
| {"undef", 1, REBOOT_REASON_PANIC}, |
| {"memcorrupt", 1, REBOOT_REASON_PANIC}, |
| {"softlockup", 1, REBOOT_REASON_PANIC}, |
| {"hardlockup 0", 1, REBOOT_REASON_WDT}, |
| {"hardlockup LITTLE", 1, REBOOT_REASON_WDT}, |
| {"hardlockup MID", 1, REBOOT_REASON_WDT}, |
| {"hardlockup BIG", 1, REBOOT_REASON_WDT}, |
| {"spinlockup", 1, REBOOT_REASON_PANIC}, |
| {"pcabort", 1, REBOOT_REASON_PANIC}, |
| {"jumpzero", 1, REBOOT_REASON_PANIC}, |
| {"writero", 1, REBOOT_REASON_PANIC}, |
| {"danglingref", 0, REBOOT_REASON_PANIC}, |
| {"dfree", 0, REBOOT_REASON_PANIC}, |
| {"badsched", 0, REBOOT_REASON_PANIC}, |
| {"QDP", 0, REBOOT_REASON_WDT}, |
| {"spabort", 0, REBOOT_REASON_NA}, |
| {"overflow", 0, REBOOT_REASON_NA}, |
| }; |
| |
| static int str_to_num(char *s) |
| { |
| int ret = -1; |
| |
| if (s) { |
| switch (s[0]) { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| ret = s[0] - '0'; |
| break; |
| default: |
| ret = -1; |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| static void clear_dbg_snapshot_test_regs(void) |
| { |
| dbg_snapshot_set_debug_test_case(0); |
| dbg_snapshot_set_debug_test_next(0); |
| dbg_snapshot_set_debug_test_panic(0); |
| dbg_snapshot_set_debug_test_wdt(0); |
| dbg_snapshot_set_debug_test_wtsr(0); |
| dbg_snapshot_set_debug_test_smpl(0); |
| dbg_snapshot_set_debug_test_curr(0); |
| dbg_snapshot_set_debug_test_total(0); |
| dbg_snapshot_clear_debug_test_runflag(); |
| test_state = 0; |
| } |
| |
| static int debug_force_error(const char *val) |
| { |
| int i; |
| char *temp; |
| char *ptr; |
| int test_vector_id = -1; |
| |
| for (i = 0; i < (int)ARRAY_SIZE(test_vector); i++) { |
| if (test_vector[i].enabled && |
| !strncmp(val, test_vector[i].arg, |
| max(strlen(val), strlen(test_vector[i].arg)))) { |
| test_vector_id = i; |
| break; |
| } |
| } |
| |
| for (i = 0; i < NR_FORCE_ERROR; i++) { |
| if (!strncmp(val, force_error_vector.item[i].errname, |
| strlen(force_error_vector.item[i].errname))) { |
| if (test_vector_id >= 0 && |
| !dbg_snapshot_get_debug_test_run(ALL_FORCE_ERRORS)) { |
| dbg_snapshot_set_debug_test_reg(1); |
| dbg_snapshot_set_debug_test_run(test_vector_id, 1); |
| dbg_snapshot_set_debug_test_case(test_vector_id); |
| dbg_snapshot_set_debug_test_next(test_vector_id + 1); |
| test_state = 1 << test_vector_id; |
| } |
| temp = (char *)val; |
| ptr = strsep(&temp, " "); /* ignore the first token */ |
| ptr = strsep(&temp, " "); /* take the second token */ |
| force_error_vector.item[i].errfunc(ptr); |
| } |
| } |
| return 0; |
| } |
| |
| static void exynos_debug_test_print(unsigned int cnt) |
| { |
| int i; |
| int pass; |
| |
| if (!cnt) |
| return; |
| |
| if (cnt >= ARRAY_SIZE(test_vector)) { |
| pr_info("=============== DEBUG TEST RESULT ===============\n"); |
| cnt = ARRAY_SIZE(test_vector); |
| } else { |
| pr_info("================ DEBUG TEST LOG =================\n"); |
| } |
| |
| for (i = 0; i < cnt; i++) { |
| if (!test_vector[i].enabled) { |
| pr_info("TestCase%3d: [%30s] disabled\n", i, |
| test_vector[i].arg); |
| continue; |
| } |
| switch (test_vector[i].reason) { |
| case REBOOT_REASON_WTSR: |
| if ((1 << i) & dbg_snapshot_get_debug_test_wtsr()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| case REBOOT_REASON_SMPL: |
| if ((1 << i) & dbg_snapshot_get_debug_test_smpl()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| case REBOOT_REASON_WDT: |
| if ((1 << i) & dbg_snapshot_get_debug_test_wdt()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| case REBOOT_REASON_PANIC: |
| if ((1 << i) & dbg_snapshot_get_debug_test_panic()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| default: |
| pass = 0; |
| break; |
| } |
| |
| pr_info("TestCase%3d: [%30s] result: [%s]\n", |
| i, test_vector[i].arg, pass ? "PASS" : "FAIL"); |
| } |
| pr_info("reg info: panic[0x%x] wdt[0x%x] wtsr[0x%x] smpl[0x%x]\n", |
| dbg_snapshot_get_debug_test_panic(), |
| dbg_snapshot_get_debug_test_wdt(), |
| dbg_snapshot_get_debug_test_wtsr(), |
| dbg_snapshot_get_debug_test_smpl()); |
| pr_info("=================================================\n"); |
| } |
| |
| static void exynos_debug_test_run_test(struct work_struct *work) |
| { |
| int i = dbg_snapshot_get_debug_test_next(); |
| |
| /* find test and do test */ |
| for (; i < (int)ARRAY_SIZE(test_vector); i++) { |
| if (test_vector[i].enabled) { |
| char *temp; |
| |
| temp = kmalloc(SZ_128, GFP_KERNEL); |
| if (!temp) |
| return; |
| exynos_debug_test_print(i); |
| dbg_snapshot_set_debug_test_next(i + 1); |
| dbg_snapshot_set_debug_test_case(i); |
| dbg_snapshot_set_debug_test_run(i, 1); |
| test_state = 1 << i; |
| pr_info("DEBUG TEST: test case%d\t:\t%s\n", i, test_vector[i].arg); |
| memcpy(temp, test_vector[i].arg, SZ_128); |
| debug_force_error(temp); |
| kfree(temp); |
| return; |
| } |
| } |
| |
| /* end of test */ |
| exynos_debug_test_print(i); |
| dbg_snapshot_set_debug_test_reg(0); |
| dbg_snapshot_set_debug_test_case(EXYNOS_DEBUG_TEST_END); |
| } |
| |
| /* timeout for dog bark/bite */ |
| #define DELAY_TIME 30000 |
| |
| static void pull_down_other_cpus(void) |
| { |
| #ifdef CONFIG_HOTPLUG_CPU |
| int cpu, ret; |
| |
| for (cpu = exynos_debug_desc.nr_cpu - 1; cpu > 0 ; cpu--) { |
| ret = cpu_down(cpu); |
| if (ret) |
| pr_crit("DEBUG TEST: %s() CORE%d ret: %x\n", |
| __func__, cpu, ret); |
| } |
| #endif |
| } |
| |
| static int find_blank(char *arg) |
| { |
| int i; |
| |
| /* if parameter is not one, a space between parameters is 0 |
| * End of parameter is lf(10) |
| */ |
| for (i = 0; !isspace(arg[i]) && arg[i]; i++) |
| continue; |
| |
| return i; |
| } |
| |
| static void simulate_test_start(char *arg) |
| { |
| int i; |
| int test_cnt; |
| |
| test_cnt = ARRAY_SIZE(test_vector); |
| for (i = 0; i < (int)ARRAY_SIZE(test_vector); i++) { |
| if (!test_vector[i].enabled) |
| test_cnt--; |
| } |
| |
| pr_info("DEBUG TEST: TEST START!(total test case = %d)\n", test_cnt); |
| clear_dbg_snapshot_test_regs(); |
| dbg_snapshot_set_debug_test_reg(1); |
| dbg_snapshot_set_debug_test_total(test_cnt); |
| dbg_snapshot_set_debug_test_run(ALL_FORCE_ERRORS, 1); |
| exynos_debug_test_run_test(&exynos_debug_desc.test_work.work); |
| } |
| |
| static void simulate_KP(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| *exynos_debug_desc.null_pointer_ui = 0x0; /* SVACE: intended */ |
| } |
| |
| static void simulate_DP(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| pull_down_other_cpus(); |
| pr_crit("DEBUG TEST: %s() start to hanging\n", __func__); |
| local_irq_disable(); |
| mdelay(DELAY_TIME); |
| local_irq_enable(); |
| /* should not reach here */ |
| } |
| |
| static void simulate_QDP(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| s3c2410wdt_set_emergency_reset(10, 0); |
| mdelay(DELAY_TIME); |
| /* should not reach here */ |
| } |
| |
| static void simulate_SVC(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| asm("svc #0x0"); |
| /* should not reach here */ |
| } |
| |
| static void simulate_SFR(char *arg) |
| { |
| int ret, index = 0; |
| unsigned long reg, val; |
| char *tmp, *tmparg; |
| void __iomem *addr; |
| |
| pr_crit("DEBUG TEST: %s() start\n", __func__); |
| |
| index = find_blank(arg); |
| if (index > PAGE_SIZE) |
| return; |
| |
| tmp = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| memcpy(tmp, arg, index); |
| tmp[index] = '\0'; |
| |
| ret = kstrtoul(tmp, 16, ®); |
| addr = ioremap(reg, 0x10); |
| if (!addr) { |
| pr_crit("DEBUG TEST: %s() failed to remap 0x%lx, quit\n", __func__, reg); |
| kfree(tmp); |
| return; |
| } |
| pr_crit("DEBUG TEST: %s() 1st parameter: 0x%lx\n", __func__, reg); |
| |
| tmparg = &arg[index + 1]; |
| index = find_blank(tmparg); |
| if (index == 0) { |
| pr_crit("DEBUG TEST: %s() there is no 2nd parameter\n", __func__); |
| pr_crit("DEBUG TEST: %s() try to read 0x%lx\n", __func__, reg); |
| |
| ret = __raw_readl(addr); |
| pr_crit("%s() result : 0x%x\n", __func__, ret); |
| |
| } else { |
| if (index > PAGE_SIZE) { |
| kfree(tmp); |
| return; |
| } |
| memcpy(tmp, tmparg, index); |
| tmp[index] = '\0'; |
| ret = kstrtoul(tmp, 16, &val); |
| pr_crit("DEBUG TEST: %s() 2nd parameter: 0x%lx\n", __func__, val); |
| pr_crit("DEBUG TEST: %s() try to write 0x%lx to 0x%lx\n", __func__, val, reg); |
| |
| __raw_writel(val, addr); |
| } |
| kfree(tmp); |
| /* should not reach here */ |
| } |
| |
| static void simulate_WP(char *arg) |
| { |
| unsigned int ps_hold_control; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| exynos_pmu_read(exynos_debug_desc.ps_hold_control_offset, &ps_hold_control); |
| exynos_pmu_write(exynos_debug_desc.ps_hold_control_offset, ps_hold_control & 0xFFFFFEFF); |
| } |
| |
| static void simulate_TP(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| } |
| |
| static void simulate_PANIC(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| panic("simulate_panic"); |
| } |
| |
| static void simulate_BUG(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| BUG(); |
| } |
| |
| static void simulate_WARN(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| WARN_ON(1); |
| } |
| |
| static void simulate_DABRT(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| *exynos_debug_desc.null_pointer_si = 0; /* SVACE: intended */ |
| } |
| |
| static void simulate_PABRT(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s() call address=[%llx]\n", __func__, |
| (unsigned long long)exynos_debug_desc.null_function); |
| |
| exynos_debug_desc.null_function(); /* SVACE: intended */ |
| } |
| |
| static void simulate_UNDEF(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| asm volatile(".word 0xe7f001f2\n\t"); |
| unreachable(); |
| } |
| |
| static void simulate_DFREE(char *arg) |
| { |
| void *p; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| p = kmalloc(sizeof(unsigned int), GFP_KERNEL); |
| if (p) { |
| *(unsigned int *)p = 0x0; |
| kfree(p); |
| msleep(1000); |
| kfree(p); /* SVACE: intended */ |
| } |
| } |
| |
| static void simulate_DREF(char *arg) |
| { |
| unsigned int *p; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| p = kmalloc(sizeof(int), GFP_KERNEL); |
| if (p) { |
| kfree(p); |
| *p = 0x1234; /* SVACE: intended */ |
| } |
| } |
| |
| static void simulate_MCRPT(char *arg) |
| { |
| int *ptr; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| ptr = kmalloc(sizeof(int), GFP_KERNEL); |
| if (ptr) { |
| *ptr++ = 4; |
| *ptr = 2; |
| panic("MEMORY CORRUPTION"); |
| } |
| } |
| |
| static void simulate_LOMEM(char *arg) |
| { |
| int i = 0; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| pr_crit("Allocating memory until failure!\n"); |
| while (kmalloc(128 * 1024, GFP_KERNEL)) /* SVACE: intended */ |
| i++; |
| pr_crit("Allocated %d KB!\n", i * 128); |
| } |
| |
| static void simulate_SOFT_LOCKUP(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| #ifdef CONFIG_SOFTLOCKUP_DETECTOR |
| softlockup_panic = 1; |
| #endif |
| local_irq_disable(); |
| preempt_disable(); |
| local_irq_enable(); |
| asm("b ."); |
| preempt_enable(); |
| } |
| |
| static void simulate_HARD_LOCKUP_handler(void *info) |
| { |
| asm("b ."); |
| } |
| |
| static void simulate_HARD_LOCKUP(char *arg) |
| { |
| int cpu; |
| int start = -1; |
| int end; |
| int curr_cpu; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| if (arg) { |
| if (!strncmp(arg, "LITTLE", strlen("LITTLE"))) { |
| if (exynos_debug_desc.little_cpu_start < 0 || |
| exynos_debug_desc.nr_little_cpu < 0) { |
| pr_info("DEBUG TEST: %s() no little cpu info\n", __func__); |
| return; |
| } |
| start = exynos_debug_desc.little_cpu_start; |
| end = start + exynos_debug_desc.nr_little_cpu - 1; |
| } else if (!strncmp(arg, "MID", strlen("MID"))) { |
| if (exynos_debug_desc.mid_cpu_start < 0 || |
| exynos_debug_desc.nr_mid_cpu < 0) { |
| pr_info("DEBUG TEST: %s() no mid cpu info\n", __func__); |
| return; |
| } |
| start = exynos_debug_desc.mid_cpu_start; |
| end = start + exynos_debug_desc.nr_mid_cpu - 1; |
| } else if (!strncmp(arg, "BIG", strlen("BIG"))) { |
| if (exynos_debug_desc.big_cpu_start < 0 || |
| exynos_debug_desc.nr_big_cpu < 0) { |
| pr_info("DEBUG TEST: %s() no big cpu info\n", __func__); |
| return; |
| } |
| start = exynos_debug_desc.big_cpu_start; |
| end = start + exynos_debug_desc.nr_big_cpu - 1; |
| } |
| |
| if (start >= 0) { |
| preempt_disable(); |
| curr_cpu = smp_processor_id(); |
| for (cpu = start; cpu <= end; cpu++) { |
| if (cpu == curr_cpu) |
| continue; |
| smp_call_function_single(cpu, |
| simulate_HARD_LOCKUP_handler, 0, 0); |
| } |
| if (curr_cpu >= start && curr_cpu <= end) { |
| local_irq_disable(); |
| asm("b ."); |
| } |
| preempt_enable(); |
| return; |
| } |
| |
| cpu = str_to_num(arg); |
| if (cpu == -1) { |
| pr_info("DEBUG TEST: %s() input is invalid\n", __func__); |
| return; |
| } |
| smp_call_function_single(cpu, simulate_HARD_LOCKUP_handler, 0, 0); |
| } else { |
| start = 0; |
| end = exynos_debug_desc.nr_cpu; |
| |
| local_irq_disable(); |
| curr_cpu = smp_processor_id(); |
| |
| for (cpu = start; cpu < end; cpu++) { |
| if (cpu == curr_cpu) |
| continue; |
| smp_call_function_single(cpu, simulate_HARD_LOCKUP_handler, 0, 0); |
| } |
| asm("b ."); |
| } |
| } |
| |
| static void simulate_SPIN_LOCKUP(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| spin_lock(&exynos_debug_desc.debug_test_lock); |
| spin_lock(&exynos_debug_desc.debug_test_lock); |
| } |
| |
| static void simulate_PC_ABORT(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| asm("add x30, x30, #0x1\n\t" |
| "ret"); |
| } |
| |
| static void simulate_SP_ABORT(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| asm("mov x29, #0xff00\n\t" |
| "mov sp, #0xff00\n\t" |
| "ret"); |
| } |
| |
| static void simulate_JUMP_ZERO(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| asm("mov x0, #0x0\n\t" |
| "br x0"); |
| } |
| |
| static void simulate_BUSMON_ERROR(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| } |
| |
| static void simulate_UNALIGNED(char *arg) |
| { |
| static u8 data[5] __aligned(4) = {1, 2, 3, 4, 5}; |
| u32 *p; |
| u32 val = 0x12345678; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| p = (u32 *)(data + 1); |
| pr_crit("DEBUG TEST: data=[0x%llx] p=[0x%llx]\n", |
| (unsigned long long)data, (unsigned long long)p); |
| if (*p == 0) |
| val = 0x87654321; |
| *p = val; |
| pr_crit("DEBUG TEST: val = [0x%x] *p = [0x%x]\n", val, *p); |
| } |
| |
| static void simulate_WRITE_RO(char *arg) |
| { |
| unsigned long *ptr; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| // Write to function addr will triger a warning by JOPP compiler |
| #ifdef CONFIG_RKP_CFP_JOPP |
| ptr = (unsigned long *)__start_rodata; |
| #else |
| ptr = (unsigned long *)simulate_WRITE_RO; |
| #endif |
| *ptr ^= 0x12345678; |
| } |
| |
| #define BUFFER_SIZE SZ_1K |
| |
| static int recursive_loop(int remaining) |
| { |
| char buf[BUFFER_SIZE]; |
| |
| pr_crit("DEBUG TEST: %s() remainig = [%d]\n", __func__, remaining); |
| |
| /* Make sure compiler does not optimize this away. */ |
| memset(buf, (remaining & 0xff) | 0x1, BUFFER_SIZE); |
| if (!remaining) |
| return 0; |
| else |
| return recursive_loop(remaining - 1); |
| } |
| |
| static void simulate_OVERFLOW(char *arg) |
| { |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| recursive_loop(600); |
| } |
| |
| static void simulate_BAD_SCHED_handler(void *info) |
| { |
| if (idle_cpu(smp_processor_id())) { |
| *(int *)info = 1; |
| msleep(1000); |
| } |
| } |
| |
| static void simulate_BAD_SCHED(char *arg) |
| { |
| int cpu; |
| int ret = 0; |
| int tries = 0; |
| |
| pr_crit("DEBUG TEST: %s()\n", __func__); |
| |
| while (true) { |
| tries++; |
| pr_crit("%dth try.\n", tries); |
| for_each_online_cpu(cpu) { |
| if (idle_cpu(cpu)) |
| smp_call_function_single(cpu, |
| simulate_BAD_SCHED_handler, &ret, 1); |
| if (ret) |
| return; /* success */ |
| } |
| mdelay(100); |
| } |
| } |
| |
| static ssize_t exynos_debug_test_write(struct file *file, |
| const char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| char *buf; |
| size_t buf_size; |
| int i; |
| |
| buf_size = ((count + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; |
| if (buf_size <= 0) |
| return 0; |
| |
| buf = kmalloc(buf_size, GFP_KERNEL); |
| if (!buf) |
| return 0; |
| |
| if (copy_from_user(buf, user_buf, count)) { |
| kfree(buf); |
| return -EFAULT; |
| } |
| buf[count] = '\0'; |
| |
| for (i = 0; buf[i] != '\0'; i++) |
| if (buf[i] == '\n') { |
| buf[i] = '\0'; |
| break; |
| } |
| |
| if (!strncmp("clear", buf, 5)) { |
| clear_dbg_snapshot_test_regs(); |
| } else { |
| pr_info("DEBUG TEST: %s() user_buf=[%s]\n", __func__, buf); |
| debug_force_error(buf); |
| } |
| |
| kfree(buf); |
| return count; |
| } |
| |
| static ssize_t exynos_debug_test_read(struct file *file, |
| char __user *user_buf, size_t count, |
| loff_t *ppos) |
| { |
| char *buf; |
| size_t copy_cnt; |
| int ret = 0; |
| int i, pass; |
| |
| buf = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| if (!buf) |
| return ret; |
| |
| if (!dbg_snapshot_get_debug_test_runflag()) |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, "TEST NOT PERFORMED\n"); |
| else if (dbg_snapshot_get_debug_test_run(ALL_FORCE_ERRORS)) |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, "ALL TEST PERFORMED\n"); |
| else |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, "INDIVIDUAL TESTS PERFORMED\n"); |
| |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "=============== DEBUG TEST RESULT ===============\n"); |
| |
| for (i = 0; i < (int)ARRAY_SIZE(test_vector); i++) { |
| if (!test_vector[i].enabled) { |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "TestCase%3d: [%30s] disabled.\n", i, |
| test_vector[i].arg); |
| continue; |
| } |
| if (!dbg_snapshot_get_debug_test_run(i)) { |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "TestCase%3d: [%30s] not performed.\n", i, |
| test_vector[i].arg); |
| continue; |
| } |
| if (test_state & (1 << i)) { |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "TestCase%3d: [%30s] testing...\n", i, |
| test_vector[i].arg); |
| continue; |
| } |
| switch (test_vector[i].reason) { |
| case REBOOT_REASON_WTSR: |
| if ((1 << i) & dbg_snapshot_get_debug_test_wtsr()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| case REBOOT_REASON_SMPL: |
| if ((1 << i) & dbg_snapshot_get_debug_test_smpl()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| case REBOOT_REASON_WDT: |
| if ((1 << i) & dbg_snapshot_get_debug_test_wdt()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| case REBOOT_REASON_PANIC: |
| if ((1 << i) & dbg_snapshot_get_debug_test_panic()) |
| pass = 1; |
| else |
| pass = 0; |
| break; |
| default: |
| pass = 0; |
| break; |
| } |
| |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "TestCase%3d: [%30s] result: [%s]\n", |
| i, test_vector[i].arg, pass ? "PASS" : "FAIL"); |
| } |
| |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "reg info: panic[0x%x] wdt[0x%x] wtsr[0x%x] smpl[0x%x] run[0x%x]\n", |
| dbg_snapshot_get_debug_test_panic(), |
| dbg_snapshot_get_debug_test_wdt(), |
| dbg_snapshot_get_debug_test_wtsr(), |
| dbg_snapshot_get_debug_test_smpl(), |
| dbg_snapshot_get_debug_test_runflag()); |
| ret += snprintf(buf + ret, PAGE_SIZE - ret, |
| "=================================================\n"); |
| copy_cnt = ret; |
| ret = simple_read_from_buffer(user_buf, count, ppos, buf, copy_cnt); |
| |
| kfree(buf); |
| return ret; |
| } |
| |
| static const struct file_operations exynos_debug_test_file_fops = { |
| .open = simple_open, |
| .read = exynos_debug_test_read, |
| .write = exynos_debug_test_write, |
| .llseek = default_llseek, |
| }; |
| |
| static ssize_t exynos_debug_test_enable_write(struct file *file, |
| const char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| char *buf; |
| ssize_t ret_val = 0; |
| size_t copy_cnt; |
| int ret; |
| |
| buf = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| if (!buf) |
| return ret_val; |
| |
| if (count > PAGE_SIZE) |
| copy_cnt = PAGE_SIZE; |
| else |
| copy_cnt = count; |
| |
| ret = copy_from_user(buf, user_buf, copy_cnt); |
| ret_val = copy_cnt - ret; |
| |
| if (ret_val && (buf[0] == '1')) { |
| ret = 0; |
| if (exynos_debug_desc.np) |
| ret = exynos_debug_test_desc_init(exynos_debug_desc.np); |
| else |
| pr_info("DEBUG TEST: %s() no dvice tree entry\n", __func__); |
| |
| if (ret) |
| pr_info("DEBUG TEST: %s() fail to enable debug test[0x%x]\n", |
| __func__, ret); |
| } else { |
| pr_info("DEBUG TEST: %s() copy count[%lu], input value[%c]\n", |
| __func__, ret_val, ret_val ? buf[0] : '0'); |
| } |
| |
| kfree(buf); |
| return ret_val; |
| } |
| |
| static ssize_t exynos_debug_test_enable_read(struct file *file, |
| char __user *user_buf, size_t count, |
| loff_t *ppos) |
| { |
| char *buf; |
| size_t copy_cnt; |
| int ret = 0; |
| |
| buf = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| if (!buf) |
| return ret; |
| |
| snprintf(buf, PAGE_SIZE, "%sABLED\n", exynos_debug_desc.enabled ? "EN" : "DIS"); |
| copy_cnt = strlen(buf); |
| |
| ret = simple_read_from_buffer(user_buf, count, ppos, buf, copy_cnt); |
| |
| kfree(buf); |
| return ret; |
| } |
| |
| static const struct file_operations exynos_debug_test_enable_fops = { |
| .open = simple_open, |
| .read = exynos_debug_test_enable_read, |
| .write = exynos_debug_test_enable_write, |
| .llseek = default_llseek, |
| }; |
| |
| static int exynos_debug_test_desc_init(struct device_node *np) |
| { |
| int ret = 0; |
| |
| /* get data from device tree */ |
| ret = of_property_read_u32(np, "ps_hold_control_offset", |
| &exynos_debug_desc.ps_hold_control_offset); |
| if (ret) { |
| pr_err("DEBUG TEST: %s() no data(ps_hold_control offset)\n", __func__); |
| goto edt_desc_init_out; |
| } |
| |
| ret = of_property_read_u32(np, "nr_cpu", |
| &exynos_debug_desc.nr_cpu); |
| if (ret) { |
| pr_err("DEBUG TEST: %s() no data(nr_cpu)\n", __func__); |
| goto edt_desc_init_out; |
| } |
| |
| ret = of_property_read_u32(np, "little_cpu_start", |
| &exynos_debug_desc.little_cpu_start); |
| if (ret) { |
| pr_info("DEBUG TEST: %s() no data(little_cpu_start)\n", __func__); |
| exynos_debug_desc.little_cpu_start = -1; |
| } |
| |
| ret = of_property_read_u32(np, "nr_little_cpu", |
| &exynos_debug_desc.nr_little_cpu); |
| if (ret) { |
| pr_info("DEBUG TEST: %s() no data(nr_little_cpu)\n", __func__); |
| exynos_debug_desc.nr_little_cpu = -1; |
| } |
| |
| ret = of_property_read_u32(np, "mid_cpu_start", |
| &exynos_debug_desc.mid_cpu_start); |
| if (ret) { |
| pr_info("DEBUG TEST: %s() no data(mid_cpu_start)\n", __func__); |
| exynos_debug_desc.mid_cpu_start = -1; |
| } |
| |
| ret = of_property_read_u32(np, "nr_mid_cpu", |
| &exynos_debug_desc.nr_mid_cpu); |
| if (ret) { |
| pr_info("DEBUG TEST: %s() no data(nr_mid_cpu)\n", __func__); |
| exynos_debug_desc.nr_mid_cpu = -1; |
| } |
| |
| ret = of_property_read_u32(np, "big_cpu_start", |
| &exynos_debug_desc.big_cpu_start); |
| if (ret) { |
| pr_info("DEBUG TEST: %s() no data(big_cpu_start)\n", __func__); |
| exynos_debug_desc.big_cpu_start = -1; |
| } |
| |
| ret = of_property_read_u32(np, "nr_big_cpu", |
| &exynos_debug_desc.nr_big_cpu); |
| if (ret) { |
| pr_info("DEBUG TEST: %s() no data(nr_big_cpu)\n", __func__); |
| exynos_debug_desc.nr_big_cpu = -1; |
| } |
| |
| exynos_debug_desc.null_function = (void (*)(void))0x1234; |
| spin_lock_init(&exynos_debug_desc.debug_test_lock); |
| |
| /* create debugfs test file */ |
| debugfs_create_file("test", 0644, |
| exynos_debug_desc.exynos_debug_test_debugfs_root, |
| NULL, &exynos_debug_test_file_fops); |
| ret = 0; |
| exynos_debug_desc.enabled = 1; |
| |
| edt_desc_init_out: |
| return ret; |
| } |
| |
| static const struct of_device_id of_exynos_debug_test_matches[] __initconst = { |
| {.compatible = "samsung,exynos-debug-test"}, |
| {}, |
| }; |
| |
| static int __init exynos_debug_test_init(void) |
| { |
| struct device_node *np = NULL; |
| const char *enable_str; |
| int ret = 0; |
| |
| pr_info("DEBUG TEST: %s() called\n", __func__); |
| |
| /* find device tree */ |
| np = of_find_matching_node(NULL, of_exynos_debug_test_matches); |
| exynos_debug_desc.np = np; |
| if (!np) { |
| pr_err("DEBUG TEST: %s() no device tree\n", __func__); |
| ret = -ENODEV; |
| goto edt_out; |
| } |
| |
| /* create debugfs dir */ |
| exynos_debug_desc.exynos_debug_test_debugfs_root = |
| debugfs_create_dir("exynos-debug-test", NULL); |
| if (!exynos_debug_desc.exynos_debug_test_debugfs_root) { |
| pr_err("DEBUG TEST: %s() cannot create debugfs dir\n", __func__); |
| ret = -ENOMEM; |
| goto edt_out; |
| } |
| |
| /* create debugfs enable file */ |
| debugfs_create_file("enable", 0644, |
| exynos_debug_desc.exynos_debug_test_debugfs_root, |
| NULL, &exynos_debug_test_enable_fops); |
| |
| /* checking debug test is enabled */ |
| ret = of_property_read_string(np, "enabled", &enable_str); |
| if (ret) { |
| pr_err("DEBUG TEST: %s() no data(enabled)\n", __func__); |
| goto edt_out; |
| } |
| |
| if (strncmp(enable_str, "dbg_test", strlen("dbg_test")) && |
| !dbg_snapshot_debug_test_enabled()) { |
| pr_info("DEBUG TEST: %s() debug test is not enabled\n", __func__); |
| goto edt_out; |
| } |
| |
| /* initializing desc data */ |
| ret = exynos_debug_test_desc_init(np); |
| if (ret) |
| goto edt_out; |
| |
| /* checking debug test is on going */ |
| if (dbg_snapshot_debug_test_enabled()) { |
| if (dbg_snapshot_get_debug_test_run(ALL_FORCE_ERRORS)) { |
| INIT_DELAYED_WORK(&exynos_debug_desc.test_work, |
| exynos_debug_test_run_test); |
| schedule_delayed_work(&exynos_debug_desc.test_work, HZ * 20); |
| } else { |
| dbg_snapshot_set_debug_test_reg(0); |
| } |
| } |
| |
| edt_out: |
| pr_info("DEBUG TEST: %s() ret=[0x%x]\n", __func__, ret); |
| return ret; |
| } |
| late_initcall(exynos_debug_test_init); |