// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2015 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for SMC ID table */ #include #include #include #include #include "aed.h" #include "mrdump_helper.h" #define BUFSIZE 128 static int test_case; static int test_cpu; static struct task_struct *wk_tsk[AEE_MTK_CPU_NUMS]; static int force_panic_hang(struct notifier_block *this, unsigned long event, void *ptr) { pr_notice("\n ==> force panic flow hang\n"); while (1) ; return 0; } static struct notifier_block panic_test = { .notifier_call = force_panic_hang, .priority = INT_MAX, }; void notrace wdt_atf_hang(void) { int cpu = get_HW_cpuid(); pr_notice(" CPU %d : %s\n", cpu, __func__); preempt_disable(); local_irq_disable(); while (1) ; } static int kwdt_thread_test(void *arg) { struct sched_param param = {.sched_priority = 99 }; int cpu = get_HW_cpuid(); sched_setscheduler(current, SCHED_FIFO, ¶m); set_current_state(TASK_INTERRUPTIBLE); pr_notice("\n ==> %s on CPU %d, test_case = %d\n", __func__, cpu, test_case); msleep(1000); if (test_case == 1) { if (cpu == test_cpu) { pr_notice("\n CPU %d : disable preemption and local IRQ forever" , cpu); preempt_disable(); local_irq_disable(); while (1) ; } else { pr_notice("\n CPU %d : Do nothing and exit\n ", cpu); } } else if (test_case == 2) { if (cpu == test_cpu) { msleep(1000); pr_notice("\n CPU %d : disable preemption and local IRQ forever" , cpu); preempt_disable(); local_irq_disable(); while (1) ; } else { pr_notice("\n CPU %d : disable irq\n ", cpu); local_irq_disable(); while (1) ; } } else if (test_case == 3) { if (cpu == test_cpu) { pr_notice("\n CPU %d : register panic notifier and force hang\n" , cpu); atomic_notifier_chain_register(&panic_notifier_list, &panic_test); preempt_disable(); local_irq_disable(); while (1) ; } else { pr_notice("\n CPU %d : Do nothing and exit\n ", cpu); } } else if (test_case == 4) { pr_notice("\n CPU %d : disable preemption and local IRQ forever\n " , cpu); preempt_disable(); local_irq_disable(); while (1) ; } else if (test_case == 5) { pr_notice("\n CPU %d : disable preemption and local IRQ/FIQ forever\n " , cpu); preempt_disable(); local_irq_disable(); while (1) ; } else if (test_case == 6) { pr_notice("\n CPU %d : disable preemption and local IRQ/FIQ forever\n " , cpu); preempt_disable(); local_irq_disable(); while (1) ; } return 0; } static ssize_t proc_generate_wdt_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { unsigned int i; char msg[4]; unsigned char name[20] = { 0 }; struct arm_smccc_res res; if ((size < 2) || (size > sizeof(msg))) { pr_notice("\n size = %zx\n", size); return -EINVAL; } if (!buf) { pr_notice("\n buf = NULL\n"); return -EINVAL; } if (copy_from_user(msg, buf, size)) { pr_notice("copy_from_user error"); return -EFAULT; } test_case = (unsigned int)msg[0] - '0'; test_cpu = (unsigned int)msg[2] - '0'; pr_notice("test_case = %d, test_cpu = %d", test_case, test_cpu); if ((msg[1] != ':') || (test_case < 1) || (test_case > 6) || (test_cpu < 0) || (test_cpu > nr_cpu_ids)) { pr_notice("WDT test - Usage: [test case number(1~6):test cpu(0~%d)]\n" , nr_cpu_ids); return -EINVAL; } if (test_case == 1) { pr_notice("Test 1 : One CPU WDT timeout (smp_send_stop succeed)\n"); } else if (test_case == 2) { pr_notice("Test 2 : One CPU WDT timeout, other CPU disable irq (smp_send_stop fail in old design)\n"); } else if (test_case == 3) { pr_notice("Test 3 : WDT timeout and loop in panic flow\n"); } else if (test_case == 4) { pr_notice("Test 4 : All CPU WDT timeout (other CPU stop in the loop)\n"); } else if (test_case == 5) { pr_notice("Test 5 : Disable ALL CPU IRQ/FIQ (FIQ : HW_reboot, ATF : HWT\n"); } else if (test_case == 6) { pr_notice("Test 6 : (For ATF) HW_REBOOT : change SMC call back function and while loop\n"); #ifdef CONFIG_ARM64 arm_smccc_smc(MTK_SIP_KERNEL_WDT, (u64) &wdt_atf_hang, 0, 0, 0, 0, 0, 0, &res); #endif #ifdef CONFIG_ARM_PSCI arm_smccc_smc(MTK_SIP_KERNEL_WDT, (u32) &wdt_atf_hang, 0, 0, 0, 0, 0, 0, &res); #endif } else { pr_notice("\n Unknown test_case %d\n", test_case); return -EINVAL; } /* create kernel threads and bind on every cpu */ for (i = 0; i < nr_cpu_ids; i++) { sprintf(name, "wd-test-%d", i); pr_notice("[WDK]thread name: %s\n", name); wk_tsk[i] = kthread_create(kwdt_thread_test, NULL, name); if (IS_ERR(wk_tsk[i])) { int ret = PTR_ERR(wk_tsk[i]); wk_tsk[i] = NULL; return ret; } kthread_bind(wk_tsk[i], i); } for (i = 0; i < nr_cpu_ids; i++) { pr_notice(" wake_up_process(wk_tsk[%d])\n", i); wake_up_process(wk_tsk[i]); } return size; } static ssize_t proc_generate_wdt_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { char buffer[BUFSIZE]; int len = snprintf(buffer, BUFSIZE, "WDT test - Usage: [test case number:test cpu]\n"); if (len < 0) pr_notice("%s: snprintf failed\n", __func__); if (*ppos) return 0; if (copy_to_user(buf, buffer, len)) { pr_notice("%s fail to output info.\n", __func__); return -EFAULT; } *ppos += len; return len; } /*****************************BEGIN OOPS***************************/ /**********BEGIN ISR trigger HWT**********/ /* kprobe pre_handler: called just before the probed instruction is executed */ static int handler_pre(struct kprobe *p, struct pt_regs *regs) { pr_info("process_name:[%s], pid = %d.\n", current->comm, current->pid); return 0; } /* kprobe post_handler: called after the probed instruction is executed */ int flag = 1; void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { if (flag) { flag = 0; mdelay(30 * 1000); } } static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr); static struct kprobe kp_kpd_irq_handler = { .symbol_name = "kpd_irq_handler", .pre_handler = handler_pre, .post_handler = handler_post, .fault_handler = handler_fault, }; /* * fault_handler: this is called if an exception is generated for any * instruction within the pre- or post-handler, or when Kprobes * single-steps the probed instruction. */ static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) { pr_notice("fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr); unregister_kprobe(&kp_kpd_irq_handler); pr_notice("kprobe at %p unregistered\n", kp_kpd_irq_handler.addr); /* Return 0 because we don't handle the fault. */ return 0; } static int register_kprobe_kpd_irq_handler(void) { int ret; /* All set to register with Kprobes */ ret = register_kprobe(&kp_kpd_irq_handler); if (ret < 0) pr_info("register_kprobe failed, returned %d\n", ret); else pr_info("Planted kprobe at %p, press Vol+/- to trigger.\n", kp_kpd_irq_handler.addr); return ret; } /**********END ISR trigger HWT**********/ /**********BEGIN panic case**********/ static noinline int stack_overflow_routine(int x, int y, int z) { char a[4]; char *p = a; int i; for (i = 0; i < (x + y + z) * 2; i++) *(p + i) = i; /* stack overflow */ return a[0] + a[3]; } static noinline void buffer_over_flow(void) { int n; pr_info("test case : buffer overflow\n"); n = stack_overflow_routine(10, 1, 22); pr_info("%s: %d\n", __func__, n); } static noinline void access_null_pointer(void) { void *p = NULL; pr_info("test case : derefence Null pointer\n"); *((unsigned int *)p) = 0xDEAD; } static noinline void double_free(void) { char *p = kmalloc(32, GFP_KERNEL); int i; if (p == NULL) return; pr_info("test case : double free\n"); for (i = 0; i < 32; i++) p[i] = (char)i; pr_info("aee_ut_ke: call free\n"); kfree(p); pr_info("aee_ut_ke: call free again\n"); kfree(p); } static noinline void devide_by_0(void) { int ZERO = 0; int number; pr_info("test case: division by %d\n", ZERO); number = 100 / ZERO; pr_info("%s: %d\n", __func__, number); } /**********END panic case**********/ static ssize_t proc_generate_oops_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int len; char buffer[BUFSIZE]; len = snprintf(buffer, BUFSIZE, "Oops Generated!\n"); if (len <= 0) pr_debug("%s: snprintf error\n", __func__); if (copy_to_user(buf, buffer, len)) pr_notice("%s fail to output info.\n", __func__); BUG(); return len; } static ssize_t proc_generate_oops_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { char msg[6]; int test_case, test_subcase, test_cpu; if ((size < 2) || (size > sizeof(msg))) { pr_notice("%s: count = %zx\n", __func__, size); return -EINVAL; } if (!buf) { pr_notice("%s: buf = NULL\n", __func__); return -EINVAL; } if (copy_from_user(msg, buf, size)) { pr_notice("%s: error\n", __func__); return -EFAULT; } test_case = (unsigned int)msg[0] - '0'; test_subcase = (unsigned int)msg[2] - '0'; test_cpu = (unsigned int)msg[4] - '0'; pr_notice("test_case = %d-%d, test_cpu = %d\n", test_case, test_subcase, test_cpu); switch (test_case) { case 1: switch (test_subcase) { case 1: buffer_over_flow(); break; case 2: access_null_pointer(); break; case 3: double_free(); break; case 4: devide_by_0(); break; default: break; } break; case 2: register_kprobe_kpd_irq_handler(); break; case 3: panic("aee test"); break; default: break; } return size; } static int nested_panic(struct notifier_block *this, unsigned long event, void *ptr) { pr_notice("\n => force nested panic\n"); BUG(); return 0; } static struct notifier_block panic_blk = { .notifier_call = nested_panic, .priority = INT_MAX - 100, }; static ssize_t proc_generate_nested_ke_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); pr_notice("\n => panic_notifier_list registered\n"); BUG(); return 0; } static ssize_t proc_generate_nested_ke_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { char msg[6]; int test_case, test_subcase, test_cpu; if ((size < 2) || (size > sizeof(msg))) { pr_notice("%s: count = %zx\n", __func__, size); return -EINVAL; } if (!buf) { pr_notice("%s: buf = NULL\n", __func__); return -EINVAL; } if (copy_from_user(msg, buf, size)) { pr_notice("%s: error\n", __func__); return -EFAULT; } test_case = (unsigned int)msg[0] - '0'; test_subcase = (unsigned int)msg[2] - '0'; test_cpu = (unsigned int)msg[4] - '0'; pr_notice("test_case = %d-%d, test_cpu = %d\n", test_case, test_subcase, test_cpu); switch (test_case) { case 1: register_die_notifier(&panic_blk); break; } BUG(); return 0; } static ssize_t proc_generate_ee_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { #define TEST_EE_LOG_SIZE 2048 #define TEST_EE_PHY_SIZE 65536 char buffer[BUFSIZE]; char *ptr, *log; int i, len; if ((*ppos)++) return 0; ptr = vmalloc(TEST_EE_PHY_SIZE); if (!ptr) return 0; log = kmalloc(TEST_EE_LOG_SIZE, GFP_KERNEL); if (!log) { vfree(ptr); return 0; } for (i = 0; i < TEST_EE_PHY_SIZE; i++) ptr[i] = (i % 26) + 'A'; for (i = 0; i < TEST_EE_LOG_SIZE; i++) log[i] = i % 255; aed_md_exception_api((int *)log, TEST_EE_LOG_SIZE, (int *)ptr, TEST_EE_PHY_SIZE, __FILE__, DB_OPT_FTRACE); vfree(ptr); kfree(log); len = snprintf(buffer, BUFSIZE, "Modem EE Generated\n"); if (len <= 0) pr_debug("%s: snprintf error\n", __func__); if (copy_to_user(buf, buffer, len)) { pr_notice("%s fail to output info.\n", __func__); return -EFAULT; } *ppos += len; return len; } static ssize_t proc_generate_ee_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { return 0; } static ssize_t proc_generate_combo_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { #define TEST_COMBO_PHY_SIZE 65536 char buffer[BUFSIZE]; int i, len; char *ptr; if ((*ppos)++) return 0; ptr = vmalloc(TEST_COMBO_PHY_SIZE); if (!ptr) return sprintf(buffer, "kmalloc fail\n"); for (i = 0; i < TEST_COMBO_PHY_SIZE; i++) ptr[i] = (i % 26) + 'A'; aed_combo_exception(NULL, 0, (int *)ptr, TEST_COMBO_PHY_SIZE, __FILE__); vfree(ptr); len = snprintf(buffer, BUFSIZE, "Combo EE Generated\n"); if (len <= 0) pr_debug("%s: snprintf error\n", __func__); if (copy_to_user(buf, buffer, len)) { pr_notice("%s fail to output info.\n", __func__); return -EFAULT; } *ppos += len; return len; } static ssize_t proc_generate_combo_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { return 0; } static ssize_t proc_generate_md32_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { #define TEST_MD32_PHY_SIZE 65536 char buffer[BUFSIZE]; int i, len; char *ptr; if ((*ppos)++) return 0; ptr = vmalloc(TEST_MD32_PHY_SIZE); if (!ptr) return 0; for (i = 0; i < TEST_MD32_PHY_SIZE; i++) ptr[i] = (i % 26) + 'a'; if (sprintf(buffer, "MD32 EE log here\n") < 0) pr_info("%s: sprintf failed\n", __func__); aed_md32_exception((int *)buffer, (int)sizeof(buffer), (int *)ptr, TEST_MD32_PHY_SIZE, __FILE__); vfree(ptr); len = snprintf(buffer, BUFSIZE, "MD32 EE Generated\n"); if (len < 0) pr_info("%s: snprintf failed\n", __func__); if (copy_to_user(buf, buffer, len)) { pr_notice("%s fail to output info.\n", __func__); return -EFAULT; } *ppos += len; return len; } static ssize_t proc_generate_md32_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { return 0; } static ssize_t proc_generate_scp_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { #define TEST_SCP_PHY_SIZE 65536 char buffer[BUFSIZE]; int i, len; char *ptr; if ((*ppos)++) return 0; ptr = vmalloc(TEST_SCP_PHY_SIZE); if (!ptr) return 0; for (i = 0; i < TEST_SCP_PHY_SIZE; i++) ptr[i] = (i % 26) + 'a'; if (sprintf(buffer, "SCP EE log here\n") < 0) pr_info("%s: sprintf failed\n", __func__); aed_scp_exception((int *)buffer, (int)sizeof(buffer), (int *)ptr, TEST_SCP_PHY_SIZE, __FILE__); vfree(ptr); len = snprintf(buffer, BUFSIZE, "SCP EE Generated\n"); if (len < 0) pr_info("%s: snprintf failed\n", __func__); if (copy_to_user(buf, buffer, len)) { pr_notice("%s fail to output info.\n", __func__); return -EFAULT; } *ppos += len; return len; } static ssize_t proc_generate_scp_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { return 0; } static ssize_t proc_generate_kernel_notify_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { char buffer[BUFSIZE]; int len = snprintf(buffer, BUFSIZE, "Usage: write message with format \"R|W|E:Tag:You Message\" into this file to generate kernel warning\n"); if (len <= 0) pr_debug("%s: snprintf error\n", __func__); if (*ppos) return 0; if (copy_to_user(buf, buffer, len)) { pr_notice("%s fail to output info.\n", __func__); return -EFAULT; } *ppos += len; return len; } static ssize_t proc_generate_kernel_notify_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { char msg[164], *colon_ptr; if (size == 0) return -EINVAL; if ((size < 5) || (size >= sizeof(msg))) { pr_notice("aed: %s size sould be >= 5 and <= %zx bytes.\n", __func__, sizeof(msg)); return -EINVAL; } if (!buf) { pr_notice("aed: %s buf = NULL\n", __func__); return -EINVAL; } if (copy_from_user(msg, buf, size)) { pr_notice("aed: %s unable to read message\n", __func__); return -EFAULT; } /* Be safe */ msg[size] = 0; if (msg[1] != ':') return -EINVAL; colon_ptr = strchr(&msg[2], ':'); if (!colon_ptr || ((colon_ptr - msg) > 32)) { pr_notice("aed: %s cannot find valid module name\n", __func__); return -EINVAL; } *colon_ptr = 0; switch (msg[0]) { case 'R': aee_kernel_reminding(&msg[2], "Hello World[Error]"); break; case 'W': aee_kernel_warning(&msg[2], "Hello World[Error]"); break; case 'E': aee_kernel_exception(&msg[2], "Hello World[Error]"); break; default: return -EINVAL; } return size; } AED_FILE_OPS(generate_oops); AED_FILE_OPS(generate_nested_ke); AED_FILE_OPS(generate_kernel_notify); AED_FILE_OPS(generate_wdt); AED_FILE_OPS(generate_ee); AED_FILE_OPS(generate_combo); AED_FILE_OPS(generate_md32); AED_FILE_OPS(generate_scp); int aed_proc_debug_init(struct proc_dir_entry *aed_proc_dir) { /* 0600: S_IRUSR | S_IWUSR */ AED_PROC_ENTRY(generate-oops, generate_oops, 0600); /* 0400: S_IRUSR */ AED_PROC_ENTRY(generate-nested-ke, generate_nested_ke, 0400); /* 0600: S_IRUSR | S_IWUSR */ AED_PROC_ENTRY(generate-kernel-notify, generate_kernel_notify, 0600); AED_PROC_ENTRY(generate-wdt, generate_wdt, 0600); /* 0400: S_IRUSR */ AED_PROC_ENTRY(generate-ee, generate_ee, 0400); AED_PROC_ENTRY(generate-combo, generate_combo, 0400); AED_PROC_ENTRY(generate-md32, generate_md32, 0400); AED_PROC_ENTRY(generate-scp, generate_scp, 0400); return 0; } int aed_proc_debug_done(struct proc_dir_entry *aed_proc_dir) { remove_proc_entry("generate-oops", aed_proc_dir); remove_proc_entry("generate-nested-ke", aed_proc_dir); remove_proc_entry("generate-kernel-notify", aed_proc_dir); remove_proc_entry("generate-ee", aed_proc_dir); remove_proc_entry("generate-combo", aed_proc_dir); remove_proc_entry("generate-md32", aed_proc_dir); remove_proc_entry("generate-scp", aed_proc_dir); remove_proc_entry("generate-wdt", aed_proc_dir); return 0; }