| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2015 MediaTek Inc. |
| */ |
| |
| #include <linux/cdev.h> |
| #include <linux/compat.h> |
| #include <linux/completion.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> |
| #include <linux/hardirq.h> |
| #include <linux/highmem.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/kallsyms.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/miscdevice.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/poll.h> |
| #include <linux/proc_fs.h> |
| #if IS_ENABLED(CONFIG_RTC_LIB) |
| #include <linux/rtc.h> |
| #endif |
| #include <linux/sched.h> |
| #include <linux/sched/task_stack.h> |
| #include <linux/sched/mm.h> |
| #include <linux/semaphore.h> |
| #include <linux/seq_file.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/stacktrace.h> |
| #include <linux/string.h> |
| #include <linux/vmalloc.h> |
| #include <linux/wait.h> |
| #include <linux/workqueue.h> |
| |
| #include <mt-plat/aee.h> |
| #include <mt-plat/slog.h> |
| |
| #include "aed.h" |
| #include "mrdump_helper.h" |
| |
| struct aee_req_queue { |
| struct list_head list; |
| spinlock_t lock; |
| }; |
| |
| static struct aee_req_queue ke_queue; |
| static struct work_struct ke_work; |
| static DECLARE_COMPLETION(aed_ke_com); |
| |
| static struct aee_req_queue ee_queue; |
| static struct work_struct ee_work; |
| static DECLARE_COMPLETION(aed_ee_com); |
| /* |
| * may be accessed from irq |
| */ |
| static spinlock_t aed_device_lock; |
| int aee_mode = AEE_MODE_NOT_INIT; |
| static int force_red_screen = AEE_FORCE_NOT_SET; |
| static int aee_force_exp = AEE_FORCE_EXP_NOT_SET; |
| |
| /* use ke_log_available to control aed_ke_poll */ |
| static int ke_log_available = 1; |
| |
| static struct proc_dir_entry *aed_proc_dir; |
| |
| #define MaxStackSize 8100 |
| #define MaxMapsSize 65536 |
| #define MAX_PROCTITLE_AUDIT_LEN 128 |
| |
| static int ee_num; |
| static int kernelapi_num; |
| |
| /****************************************************************************** |
| * DEBUG UTILITIES |
| *****************************************************************************/ |
| |
| void msg_show(const char *prefix, struct AE_Msg *msg) |
| { |
| const char *cmd_type; |
| const char *cmd_id; |
| |
| if (!msg) { |
| pr_info("%s: EMPTY msg\n", prefix); |
| return; |
| } |
| |
| switch (msg->cmdType) { |
| case AE_REQ: |
| cmd_type = "REQ"; |
| break; |
| case AE_RSP: |
| cmd_type = "RESPONSE"; |
| break; |
| case AE_IND: |
| cmd_type = "IND"; |
| break; |
| default: |
| cmd_type = "UNKNOWN"; |
| break; |
| } |
| |
| switch (msg->cmdId) { |
| case AE_REQ_IDX: |
| cmd_id = "IDX"; |
| break; |
| case AE_REQ_CLASS: |
| cmd_id = "CLASS"; |
| break; |
| case AE_REQ_TYPE: |
| cmd_id = "TYPE"; |
| break; |
| case AE_REQ_MODULE: |
| cmd_id = "MODULE"; |
| break; |
| case AE_REQ_PROCESS: |
| cmd_id = "PROCESS"; |
| break; |
| case AE_REQ_DETAIL: |
| cmd_id = "DETAIL"; |
| break; |
| case AE_REQ_BACKTRACE: |
| cmd_id = "BACKTRACE"; |
| break; |
| case AE_REQ_COREDUMP: |
| cmd_id = "COREDUMP"; |
| break; |
| case AE_IND_EXP_RAISED: |
| cmd_id = "EXP_RAISED"; |
| break; |
| case AE_IND_WRN_RAISED: |
| cmd_id = "WARN_RAISED"; |
| break; |
| case AE_IND_REM_RAISED: |
| cmd_id = "REMIND_RAISED"; |
| break; |
| case AE_IND_FATAL_RAISED: |
| cmd_id = "FATAL_RAISED"; |
| break; |
| case AE_IND_LOG_CLOSE: |
| cmd_id = "CLOSE"; |
| break; |
| case AE_REQ_USERSPACEBACKTRACE: |
| cmd_id = "USERBACKTRACE"; |
| break; |
| case AE_REQ_USER_REG: |
| cmd_id = "USERREG"; |
| break; |
| default: |
| cmd_id = "UNKNOWN"; |
| break; |
| } |
| |
| pr_debug("%s: cmdType=%s[%d] cmdId=%s[%d] seq=%d arg=%x len=%d\n", |
| prefix, |
| cmd_type, msg->cmdType, cmd_id, msg->cmdId, msg->seq, msg->arg, |
| msg->len); |
| } |
| |
| |
| int aee_get_mode(void) |
| { |
| return aee_mode; |
| } |
| EXPORT_SYMBOL(aee_get_mode); |
| |
| |
| /****************************************************************************** |
| * CONSTANT DEFINITIONS |
| *****************************************************************************/ |
| #define CURRENT_EE_COREDUMP "current-ee-coredump" |
| |
| #define MAX_EE_COREDUMP 0x800000 |
| |
| /****************************************************************************** |
| * STRUCTURE DEFINITIONS |
| *****************************************************************************/ |
| |
| struct aed_eerec { /* external exception record */ |
| struct list_head list; |
| char assert_type[32]; |
| char exp_filename[512]; |
| unsigned int exp_linenum; |
| unsigned int fatal1; |
| unsigned int fatal2; |
| |
| int *ee_log; |
| int ee_log_size; |
| int *ee_phy; |
| int ee_phy_size; |
| char *msg; |
| int db_opt; |
| }; |
| |
| struct aed_kerec { /* TODO: kernel exception record */ |
| char *msg; |
| struct aee_oops *lastlog; |
| }; |
| |
| struct aed_dev { |
| struct aed_eerec *eerec; |
| wait_queue_head_t eewait; |
| |
| struct aed_kerec kerec; |
| wait_queue_head_t kewait; |
| }; |
| |
| |
| /****************************************************************************** |
| * FUNCTION PROTOTYPES |
| *****************************************************************************/ |
| static long aed_ioctl(struct file *file, unsigned int cmd, unsigned long arg); |
| |
| |
| /****************************************************************************** |
| * GLOBAL DATA |
| *****************************************************************************/ |
| static struct aed_dev aed_dev; |
| |
| |
| /****************************************************************************** |
| * Message Utilities |
| *****************************************************************************/ |
| |
| inline void msg_destroy(char **ppmsg) |
| { |
| if (*ppmsg) { |
| vfree(*ppmsg); |
| *ppmsg = NULL; |
| } |
| } |
| |
| inline struct AE_Msg *msg_create(char **ppmsg, int extra_size) |
| { |
| int size; |
| |
| msg_destroy(ppmsg); |
| size = sizeof(struct AE_Msg) + extra_size; |
| |
| *ppmsg = vzalloc(size); |
| if (!*ppmsg) { |
| pr_info("%s : kzalloc() fail\n", __func__); |
| return NULL; |
| } |
| |
| ((struct AE_Msg *) (*ppmsg))->len = extra_size; |
| |
| return (struct AE_Msg *) *ppmsg; |
| } |
| |
| static ssize_t msg_copy_to_user(const char *prefix, char *msg, char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| ssize_t ret = 0; |
| int len; |
| char *msg_tmp; |
| |
| if (!msg) |
| return ret; |
| |
| msg_show(prefix, (struct AE_Msg *) msg); |
| |
| msg_tmp = kzalloc(((struct AE_Msg *)msg)->len + sizeof(struct AE_Msg), |
| GFP_KERNEL); |
| if (msg_tmp) { |
| memcpy(msg_tmp, msg, |
| ((struct AE_Msg *)msg)->len + sizeof(struct AE_Msg)); |
| } else { |
| pr_info("%s : kzalloc() fail!\n", __func__); |
| msg_tmp = msg; |
| } |
| |
| if (!msg_tmp || ((struct AE_Msg *)msg_tmp)->cmdType < AE_REQ |
| || ((struct AE_Msg *)msg_tmp)->cmdType > AE_CMD_TYPE_END) |
| goto out; |
| |
| len = ((struct AE_Msg *) msg_tmp)->len + sizeof(struct AE_Msg); |
| |
| if ((*f_pos < 0) || (*f_pos >= len)) { |
| ret = 0; |
| goto out; |
| } |
| /* TODO: semaphore */ |
| if ((*f_pos + count) > len) { |
| pr_info("read size overflow, count=%zx, *f_pos=%llx\n", |
| count, *f_pos); |
| count = len - *f_pos; |
| ret = -EFAULT; |
| goto out; |
| } |
| |
| if (copy_to_user(buf, msg_tmp + *f_pos, count)) { |
| pr_info("copy_to_user failed\n"); |
| ret = -EFAULT; |
| goto out; |
| } |
| *f_pos += count; |
| ret = count; |
| out: |
| if (msg_tmp != msg) |
| kfree(msg_tmp); |
| return ret; |
| } |
| |
| /****************************************************************************** |
| * Kernel message handlers |
| *****************************************************************************/ |
| static struct aee_oops *aee_oops_create(enum AE_DEFECT_ATTR attr, |
| enum AE_EXP_CLASS clazz, const char *module) |
| { |
| struct aee_oops *oops = kzalloc(sizeof(struct aee_oops), GFP_ATOMIC); |
| |
| if (!oops) |
| return NULL; |
| oops->attr = attr; |
| oops->clazz = clazz; |
| if (module) |
| strlcpy(oops->module, module, sizeof(oops->module)); |
| else |
| strlcpy(oops->module, "N/A", sizeof(oops->module)); |
| strlcpy(oops->backtrace, "N/A", sizeof(oops->backtrace)); |
| |
| return oops; |
| } |
| |
| static void aee_oops_free(struct aee_oops *oops) |
| { |
| vfree(oops->userthread_stack.Userthread_Stack); |
| vfree(oops->userthread_maps.Userthread_maps); |
| kfree(oops); |
| pr_notice("%s\n", __func__); |
| } |
| |
| static void ke_gen_notavail_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, 0); |
| if (!rep_msg) |
| return; |
| |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->arg = AE_NOT_AVAILABLE; |
| rep_msg->len = 0; |
| } |
| |
| static void ke_gen_class_msg(void) |
| { |
| #define KE_CLASS_STR "Kernel (KE)" |
| #define KE_CLASS_SIZE 12 |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, KE_CLASS_SIZE); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_CLASS; |
| rep_msg->len = KE_CLASS_SIZE; |
| strncpy(data, KE_CLASS_STR, KE_CLASS_SIZE); |
| } |
| |
| static void ke_gen_type_msg(void) |
| { |
| #define KE_TYPE_STR "PANIC" |
| #define KE_TYPE_SIZE 6 |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, KE_TYPE_SIZE); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_TYPE; |
| rep_msg->len = KE_TYPE_SIZE; |
| strncpy(data, KE_TYPE_STR, KE_TYPE_SIZE); |
| } |
| |
| static void ke_gen_module_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, |
| strlen(aed_dev.kerec.lastlog->module) + 1); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_MODULE; |
| rep_msg->len = strlen(aed_dev.kerec.lastlog->module) + 1; |
| strlcpy(data, aed_dev.kerec.lastlog->module, |
| sizeof(aed_dev.kerec.lastlog->module)); |
| } |
| |
| static void ke_gen_detail_msg(const struct AE_Msg *req_msg) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, |
| aed_dev.kerec.lastlog->detail_len + 1); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_DETAIL; |
| rep_msg->len = aed_dev.kerec.lastlog->detail_len + 1; |
| if (aed_dev.kerec.lastlog->detail) |
| strlcpy(data, aed_dev.kerec.lastlog->detail, |
| aed_dev.kerec.lastlog->detail_len); |
| data[aed_dev.kerec.lastlog->detail_len] = 0; |
| |
| } |
| |
| static void ke_gen_backtrace_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, AEE_BACKTRACE_LENGTH); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_BACKTRACE; |
| |
| strlcpy(data, aed_dev.kerec.lastlog->backtrace, AEE_BACKTRACE_LENGTH); |
| /* Count into the NUL byte at end of string */ |
| rep_msg->len = strlen(data) + 1; |
| } |
| |
| |
| static void ke_gen_userbacktrace_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| int userinfo_len; |
| |
| if (aed_dev.kerec.lastlog->userthread_stack.StackLength < 0) |
| return; |
| userinfo_len = aed_dev.kerec.lastlog->userthread_stack.StackLength + |
| sizeof(pid_t)+sizeof(int); |
| rep_msg = msg_create(&aed_dev.kerec.msg, MaxStackSize); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_USERSPACEBACKTRACE; |
| |
| rep_msg->len = userinfo_len; |
| pr_debug("%s rep_msg->len:%lx,\n", __func__, (long)rep_msg->len); |
| |
| memcpy(data, (char *) &(aed_dev.kerec.lastlog->userthread_stack), |
| sizeof(pid_t) + sizeof(int)); |
| pr_debug("len(pid+int):%lx\n", (long)(sizeof(pid_t)+sizeof(int))); |
| pr_debug("des :%lx\n", (long)(data + sizeof(pid_t)+sizeof(int))); |
| pr_debug("src addr :%lx\n", (long)((char *) |
| (aed_dev.kerec.lastlog->userthread_stack.Userthread_Stack))); |
| |
| memcpy((data + sizeof(pid_t)+sizeof(int)), (char *) |
| (aed_dev.kerec.lastlog->userthread_stack.Userthread_Stack), |
| aed_dev.kerec.lastlog->userthread_stack.StackLength); |
| |
| } |
| |
| static void ke_gen_usermaps_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| int userinfo_len; |
| |
| if (aed_dev.kerec.lastlog->userthread_maps.Userthread_mapsLength < 0) |
| return; |
| userinfo_len = |
| aed_dev.kerec.lastlog->userthread_maps.Userthread_mapsLength + |
| sizeof(pid_t)+sizeof(int); |
| rep_msg = msg_create(&aed_dev.kerec.msg, MaxMapsSize); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_USER_MAPS; |
| |
| rep_msg->len = userinfo_len; |
| pr_debug("%s rep_msg->len:%lx,\n", __func__, (long)rep_msg->len); |
| |
| memcpy(data, (char *) &(aed_dev.kerec.lastlog->userthread_maps), |
| sizeof(pid_t) + sizeof(int)); |
| pr_debug("len(pid+int):%lx\n", (long)(sizeof(pid_t)+sizeof(int))); |
| pr_debug("des :%lx\n", (long)(data + sizeof(pid_t)+sizeof(int))); |
| pr_debug("src addr :%lx\n", (long)((char *) |
| (aed_dev.kerec.lastlog->userthread_maps.Userthread_maps))); |
| |
| memcpy((data + sizeof(pid_t)+sizeof(int)), (char *) |
| (aed_dev.kerec.lastlog->userthread_maps.Userthread_maps), |
| aed_dev.kerec.lastlog->userthread_maps.Userthread_mapsLength); |
| |
| } |
| |
| static void ke_gen_user_reg_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, sizeof(struct aee_thread_reg)); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_USER_REG; |
| |
| /* Count into the NUL byte at end of string */ |
| rep_msg->len = sizeof(struct aee_thread_reg); |
| memcpy(data, (char *) &(aed_dev.kerec.lastlog->userthread_reg), |
| sizeof(struct aee_thread_reg)); |
| } |
| |
| static int ke_gen_ind_msg(struct aee_oops *oops) |
| { |
| unsigned long flags; |
| long ret; |
| unsigned long expire; |
| unsigned long timeout; |
| |
| if (!oops) |
| return -1; |
| |
| spin_lock_irqsave(&aed_device_lock, flags); |
| if (!aed_dev.kerec.lastlog) { |
| aed_dev.kerec.lastlog = oops; |
| } else { |
| /* |
| * waaa.. Two ke api at the same time |
| * or ke api during aed process is still busy at ke |
| * discard the new oops! |
| * Code should NEVER come here now!!! |
| */ |
| |
| pr_info( |
| "%s: BUG!!! More than one kernel message queued, AEE does not support concurrent KE dump\n" |
| , __func__); |
| aee_oops_free(oops); |
| spin_unlock_irqrestore(&aed_device_lock, flags); |
| |
| return -1; |
| } |
| spin_unlock_irqrestore(&aed_device_lock, flags); |
| |
| if (aed_dev.kerec.lastlog) { |
| struct AE_Msg *rep_msg; |
| |
| rep_msg = msg_create(&aed_dev.kerec.msg, 0); |
| if (!rep_msg) |
| return 0; |
| |
| rep_msg->cmdType = AE_IND; |
| switch (oops->attr) { |
| case AE_DEFECT_REMINDING: |
| rep_msg->cmdId = AE_IND_REM_RAISED; |
| break; |
| case AE_DEFECT_WARNING: |
| rep_msg->cmdId = AE_IND_WRN_RAISED; |
| break; |
| case AE_DEFECT_EXCEPTION: |
| rep_msg->cmdId = AE_IND_EXP_RAISED; |
| break; |
| case AE_DEFECT_FATAL: |
| rep_msg->cmdId = AE_IND_FATAL_RAISED; |
| break; |
| default: |
| /* Huh... something wrong, just go to exception */ |
| rep_msg->cmdId = AE_IND_EXP_RAISED; |
| break; |
| } |
| |
| rep_msg->arg = oops->clazz; |
| rep_msg->len = 0; |
| rep_msg->dbOption = oops->dump_option; |
| |
| init_completion(&aed_ke_com); |
| /* kernel api log is safe to access by child debuggerd from |
| * here |
| */ |
| ke_log_available = 1; |
| wake_up(&aed_dev.kewait); |
| /* |
| * wait until current ke work is done, then aed_dev is |
| * available, add a 60s timeout in case of debuggerd quit |
| * abnormally |
| */ |
| timeout = msecs_to_jiffies(5 * 60 * 1000); |
| expire = jiffies + timeout; |
| for (;;) { |
| ret = wait_for_completion_interruptible_timeout(&aed_ke_com, timeout); |
| if (ret == 0) { |
| pr_info("%s: TIMEOUT, not receive close event, skip\n", |
| __func__); |
| break; |
| } |
| if (ret > 0) |
| break; |
| if ((ret == -ERESTARTSYS) && (time_before(jiffies, expire))) { |
| pr_info("%s: INTERRUPTED, continue waiting for completion\n", |
| __func__); |
| timeout = expire - jiffies; |
| continue; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static void ke_destroy_log(void) |
| { |
| struct aee_oops *lastlog = aed_dev.kerec.lastlog; |
| |
| msg_destroy(&aed_dev.kerec.msg); |
| |
| if (aed_dev.kerec.lastlog) { |
| aed_dev.kerec.lastlog = NULL; |
| aee_oops_free(lastlog); |
| } |
| } |
| |
| static int ke_log_avail(void) |
| { |
| if (aed_dev.kerec.lastlog) |
| return 1; |
| return 0; |
| } |
| |
| static void aee_kapi_tasklet_handler(struct tasklet_struct *data) |
| { |
| int ret; |
| |
| ret = queue_work(system_wq, &ke_work); |
| if (!ret) |
| pr_info("%s: ke work was already on a queue\n", __func__); |
| } |
| DECLARE_TASKLET(aee_kapi_tasklet, aee_kapi_tasklet_handler); |
| |
| static void ke_queue_request(struct aee_oops *oops) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ke_queue.lock, flags); |
| list_add_tail(&oops->list, &ke_queue.list); |
| spin_unlock_irqrestore(&ke_queue.lock, flags); |
| tasklet_schedule(&aee_kapi_tasklet); |
| } |
| |
| static void ke_worker(struct work_struct *work) |
| { |
| int ret; |
| struct aee_oops *oops, *n; |
| unsigned long flags; |
| |
| list_for_each_entry_safe(oops, n, &ke_queue.list, list) { |
| if (!oops) { |
| pr_info("%s:Invalid aee_oops struct\n", __func__); |
| return; |
| } |
| |
| ret = ke_gen_ind_msg(oops); |
| spin_lock_irqsave(&ke_queue.lock, flags); |
| if (!ret) |
| list_del(&oops->list); |
| spin_unlock_irqrestore(&ke_queue.lock, flags); |
| ke_destroy_log(); |
| } |
| } |
| |
| /****************************************************************************** |
| * EE message handlers |
| *****************************************************************************/ |
| static void ee_gen_notavail_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| |
| rep_msg = msg_create(&aed_dev.eerec->msg, 0); |
| if (!rep_msg) |
| return; |
| |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->arg = AE_NOT_AVAILABLE; |
| rep_msg->len = 0; |
| } |
| |
| static void ee_gen_class_msg(void) |
| { |
| #define EX_CLASS_EE_STR "External (EE)" |
| #define EX_CLASS_EE_SIZE 14 |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| |
| rep_msg = msg_create(&aed_dev.eerec->msg, EX_CLASS_EE_SIZE); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_CLASS; |
| rep_msg->len = EX_CLASS_EE_SIZE; |
| strncpy(data, EX_CLASS_EE_STR, EX_CLASS_EE_SIZE); |
| } |
| |
| static void ee_gen_type_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| struct aed_eerec *eerec = aed_dev.eerec; |
| |
| rep_msg = |
| msg_create(&eerec->msg, |
| strlen((char const *)&eerec->assert_type) + 1); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_TYPE; |
| rep_msg->len = strlen((char const *)&eerec->assert_type) + 1; |
| strncpy(data, (char const *)&eerec->assert_type, |
| strlen((char const *)&eerec->assert_type)); |
| } |
| |
| static void ee_gen_process_msg(void) |
| { |
| #define PROCESS_STRLEN 512 |
| |
| int n = 0; |
| struct AE_Msg *rep_msg; |
| char *data; |
| struct aed_eerec *eerec = aed_dev.eerec; |
| |
| rep_msg = msg_create(&eerec->msg, PROCESS_STRLEN); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| |
| if (eerec->exp_linenum != 0) { |
| /* for old aed_md_exception1() */ |
| n = snprintf(data, sizeof(eerec->assert_type), "%s", |
| eerec->assert_type); |
| if (eerec->exp_filename[0] != 0) { |
| n += snprintf(data + n, (PROCESS_STRLEN - n), |
| ", filename=%s,line=%d", eerec->exp_filename, |
| eerec->exp_linenum); |
| } else if (eerec->fatal1 != 0 && eerec->fatal2 != 0) { |
| n += snprintf(data + n, (PROCESS_STRLEN - n), |
| ", err1=%d,err2=%d", eerec->fatal1, |
| eerec->fatal2); |
| } |
| } else { |
| n = snprintf(data, PROCESS_STRLEN, "%s", eerec->exp_filename); |
| if (n < 0) { |
| n = 0; |
| pr_info("%s: snprintf failed\n", __func__); |
| } |
| } |
| |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_PROCESS; |
| rep_msg->len = n + 1; |
| } |
| |
| __weak int aee_dump_ccci_debug_info(int md_id, void **addr, int *size) |
| { |
| return -1; |
| } |
| |
| static void ee_gen_detail_msg(void) |
| { |
| int i, l, n = 0; |
| struct AE_Msg *rep_msg; |
| char *data; |
| int *mem; |
| int md_id; |
| int msgsize; |
| char *ccci_log = NULL; |
| int ccci_log_size = 0; |
| struct aed_eerec *eerec = aed_dev.eerec; |
| |
| if (strncmp(eerec->assert_type, "md32", 4) == 0) { |
| msgsize = eerec->ee_log_size + 128; |
| rep_msg = msg_create(&eerec->msg, msgsize); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| l = snprintf(data + n, msgsize - n, |
| "== EXTERNAL EXCEPTION LOG ==\n%s\n", |
| (char *)eerec->ee_log); |
| if (l >= msgsize - n) |
| pr_info("ee_log may overflow! %d >= %d\n", |
| l, msgsize - n); |
| n += min(l, msgsize - n); |
| } else { |
| if (strncmp(eerec->assert_type, "modem", 5) == 0) { |
| if (sscanf(eerec->exp_filename, "md%d:", &md_id) == 1) { |
| if (aee_dump_ccci_debug_info(md_id, |
| (void **)&ccci_log, &ccci_log_size)) { |
| ccci_log = NULL; |
| ccci_log_size = 0; |
| } |
| } |
| } |
| msgsize = (eerec->ee_log_size + ccci_log_size) * 4 + 128; |
| rep_msg = msg_create(&eerec->msg, msgsize); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| n += snprintf(data + n, msgsize - n, |
| "== EXTERNAL EXCEPTION LOG ==\n"); |
| mem = (int *)eerec->ee_log; |
| if (mem) { |
| for (i = 0; i < eerec->ee_log_size / 4; i += 4) { |
| n += snprintf(data + n, msgsize - n, |
| "0x%08X 0x%08X 0x%08X 0x%08X\n", |
| mem[i], mem[i + 1], |
| mem[i + 2], mem[i + 3]); |
| } |
| } else { |
| n += snprintf(data + n, msgsize - n, |
| "kmalloc fail, no log available\n"); |
| } |
| } |
| l = snprintf(data + n, msgsize - n, "== MEM DUMP(%d) ==\n", |
| eerec->ee_phy_size); |
| n += min(l, msgsize - n); |
| if (ccci_log) { |
| n += snprintf(data + n, msgsize - n, "== CCCI LOG ==\n"); |
| mem = (int *)ccci_log; |
| for (i = 0; i < ccci_log_size / 4; i += 4) { |
| n += snprintf(data + n, msgsize - n, |
| "0x%08X 0x%08X 0x%08X 0x%08X\n", |
| mem[i], mem[i + 1], |
| mem[i + 2], mem[i + 3]); |
| } |
| n += snprintf(data + n, msgsize - n, "== MEM DUMP(%d) ==\n", |
| ccci_log_size); |
| } |
| |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_DETAIL; |
| rep_msg->arg = AE_PASS_BY_MEM; |
| rep_msg->len = n + 1; |
| } |
| |
| static void ee_gen_coredump_msg(void) |
| { |
| struct AE_Msg *rep_msg; |
| char *data; |
| |
| rep_msg = msg_create(&aed_dev.eerec->msg, 256); |
| if (!rep_msg) |
| return; |
| |
| data = (char *)rep_msg + sizeof(struct AE_Msg); |
| rep_msg->cmdType = AE_RSP; |
| rep_msg->cmdId = AE_REQ_COREDUMP; |
| rep_msg->arg = 0; |
| if (snprintf(data, 256, "/proc/aed/%s", CURRENT_EE_COREDUMP) < 0) |
| pr_info("%s: snprintf failed\n", __func__); |
| rep_msg->len = strlen(data) + 1; |
| } |
| |
| static void ee_destroy_log(void) |
| { |
| struct aed_eerec *eerec = aed_dev.eerec; |
| |
| if (!eerec) |
| return; |
| |
| aed_dev.eerec = NULL; |
| msg_destroy(&eerec->msg); |
| |
| if (eerec->ee_phy) { |
| vfree(eerec->ee_phy); |
| eerec->ee_phy = NULL; |
| } |
| eerec->ee_log_size = 0; |
| eerec->ee_phy_size = 0; |
| |
| kfree(eerec->ee_log); |
| /*after this, another ee can enter */ |
| eerec->ee_log = NULL; |
| kfree(eerec); |
| } |
| |
| static int ee_log_avail(void) |
| { |
| return (aed_dev.eerec != NULL); |
| } |
| |
| static char *ee_msg_avail(void) |
| { |
| if (aed_dev.eerec) |
| return aed_dev.eerec->msg; |
| return NULL; |
| } |
| |
| static void ee_gen_ind_msg(struct aed_eerec *eerec) |
| { |
| unsigned long flags; |
| struct AE_Msg *rep_msg; |
| long ret; |
| unsigned long expire; |
| unsigned long timeout; |
| |
| if (!eerec) |
| return; |
| |
| /* |
| * Don't lock the whole function for the time is uncertain. |
| * we rely on the fact that ee_rec is not null if race here! |
| */ |
| spin_lock_irqsave(&aed_device_lock, flags); |
| |
| if (!aed_dev.eerec) { |
| aed_dev.eerec = eerec; |
| } else { |
| /* should never come here, skip*/ |
| spin_unlock_irqrestore(&aed_device_lock, flags); |
| pr_info("%s: More than one EE message queued\n", __func__); |
| return; |
| } |
| spin_unlock_irqrestore(&aed_device_lock, flags); |
| |
| rep_msg = msg_create(&aed_dev.eerec->msg, 0); |
| if (!rep_msg) |
| return; |
| |
| rep_msg->cmdType = AE_IND; |
| rep_msg->cmdId = AE_IND_EXP_RAISED; |
| rep_msg->arg = AE_EE; |
| rep_msg->len = 0; |
| rep_msg->dbOption = eerec->db_opt; |
| |
| init_completion(&aed_ee_com); |
| wake_up(&aed_dev.eewait); |
| timeout = msecs_to_jiffies(5 * 60 * 1000); |
| expire = jiffies + timeout; |
| for (;;) { |
| ret = wait_for_completion_interruptible_timeout(&aed_ee_com, timeout); |
| if (ret == 0) { |
| pr_info("%s: TIMEOUT, not receive close event, skip\n", |
| __func__); |
| break; |
| } |
| if (ret > 0) |
| break; |
| if ((ret == -ERESTARTSYS) && (time_before(jiffies, expire))) { |
| pr_info("%s: INTERRUPTED, continue waiting for completion\n", |
| __func__); |
| timeout = expire - jiffies; |
| continue; |
| } |
| } |
| } |
| |
| static void ee_queue_request(struct aed_eerec *eerec) |
| { |
| int ret; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ee_queue.lock, flags); |
| list_add_tail(&eerec->list, &ee_queue.list); |
| spin_unlock_irqrestore(&ee_queue.lock, flags); |
| ret = queue_work(system_wq, &ee_work); |
| pr_debug("%s: add new ee work, status %d\n", __func__, ret); |
| } |
| |
| static void ee_worker(struct work_struct *work) |
| { |
| struct aed_eerec *eerec, *tmp; |
| unsigned long flags; |
| |
| list_for_each_entry_safe(eerec, tmp, &ee_queue.list, list) { |
| if (!eerec) { |
| pr_info("%s:null eerec\n", __func__); |
| return; |
| } |
| |
| ee_gen_ind_msg(eerec); |
| spin_lock_irqsave(&ee_queue.lock, flags); |
| list_del(&eerec->list); |
| spin_unlock_irqrestore(&ee_queue.lock, flags); |
| ee_destroy_log(); |
| } |
| } |
| |
| /****************************************************************************** |
| * aee_get_cmdline is a copy of get_cmdline in mm/util.c, please pay attension to |
| *whether this function has changed when the kernel version is upgraded. |
| *****************************************************************************/ |
| static int aee_get_cmdline(struct task_struct *task, char *buffer, int buflen) |
| { |
| int res = 0; |
| unsigned int len; |
| struct mm_struct *mm = get_task_mm(task); |
| unsigned long arg_start, arg_end, env_start, env_end; |
| |
| if (!mm) |
| goto out; |
| if (!mm->arg_end) |
| goto out_mm; /* Shh! No looking before we're done */ |
| |
| spin_lock(&mm->arg_lock); |
| arg_start = mm->arg_start; |
| arg_end = mm->arg_end; |
| env_start = mm->env_start; |
| env_end = mm->env_end; |
| spin_unlock(&mm->arg_lock); |
| |
| len = arg_end - arg_start; |
| |
| if (len > buflen) |
| len = buflen; |
| |
| res = access_process_vm(task, arg_start, buffer, len, FOLL_FORCE); |
| |
| /* |
| * If the nul at the end of args has been overwritten, then |
| * assume application is using setproctitle(3). |
| */ |
| if (res > 0 && buffer[res-1] != '\0' && len < buflen) { |
| len = strnlen(buffer, res); |
| if (len < res) { |
| res = len; |
| } else { |
| len = env_end - env_start; |
| if (len > buflen - res) |
| len = buflen - res; |
| res += access_process_vm(task, env_start, |
| buffer+res, len, |
| FOLL_FORCE); |
| res = strnlen(buffer, res); |
| } |
| } |
| out_mm: |
| mmput(mm); |
| out: |
| return res; |
| } |
| |
| static int compare_cmdline(void) |
| { |
| int len = 0; |
| char buf[MAX_PROCTITLE_AUDIT_LEN] = {0}; |
| |
| len = aee_get_cmdline(current, buf, MAX_PROCTITLE_AUDIT_LEN); |
| if (len == 0) |
| return -1; |
| |
| if (strncmp(buf, "/system_ext/bin/aee_aed", 23) && |
| strncmp(buf, "/system/system_ext/bin/aee_aed", 30) && |
| strncmp(buf, "/vendor/bin/aee_aed", 19)) { |
| pr_debug("%s:open failed!\n", __func__); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /****************************************************************************** |
| * AED EE File operations |
| *****************************************************************************/ |
| static int aed_ee_open(struct inode *inode, struct file *filp) |
| { |
| if (compare_cmdline() != 0) |
| return -1; |
| pr_debug("%s:%d:%d\n", __func__, MAJOR(inode->i_rdev), |
| MINOR(inode->i_rdev)); |
| return 0; |
| } |
| |
| static int aed_ee_release(struct inode *inode, struct file *filp) |
| { |
| pr_debug("%s:%d:%d\n", __func__, MAJOR(inode->i_rdev), |
| MINOR(inode->i_rdev)); |
| return 0; |
| } |
| |
| static unsigned int aed_ee_poll(struct file *file, |
| struct poll_table_struct *ptable) |
| { |
| if (ee_log_avail() && ee_msg_avail()) |
| return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; |
| poll_wait(file, &aed_dev.eewait, ptable); |
| return 0; |
| } |
| |
| static ssize_t aed_ee_read(struct file *filp, char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| if (!aed_dev.eerec) { |
| pr_info("%s fail for invalid kerec\n", __func__); |
| return 0; |
| } |
| return msg_copy_to_user(__func__, aed_dev.eerec->msg, buf, count, |
| f_pos); |
| } |
| |
| static ssize_t aed_ee_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| struct AE_Msg msg; |
| int rsize; |
| struct aed_eerec *eerec = aed_dev.eerec; |
| |
| /* received a new request means the previous response is unavilable */ |
| /* 1. set position to be zero */ |
| /* 2. destroy the previous response message */ |
| *f_pos = 0; |
| |
| if (!eerec) |
| return -1; |
| |
| msg_destroy(&eerec->msg); |
| |
| /* the request must be an *struct AE_Msg buffer */ |
| if (count != sizeof(struct AE_Msg)) { |
| pr_info("%s: ERR, aed_write count=%zx\n", __func__, count); |
| return -1; |
| } |
| if (!buf) { |
| pr_info("%s: ERR, aed_write buf=NULL\n", __func__); |
| return -1; |
| } |
| rsize = copy_from_user(&msg, buf, count); |
| if (rsize) { |
| pr_info("%s: ERR, copy_from_user rsize=%d\n", __func__, rsize); |
| return -1; |
| } |
| |
| /* the same reason removing "AEE api log available". |
| * msg_show(__func__, &msg); |
| */ |
| |
| if (msg.cmdType == AE_REQ) { |
| if (!ee_log_avail()) { |
| ee_gen_notavail_msg(); |
| return count; |
| } |
| switch (msg.cmdId) { |
| case AE_REQ_CLASS: |
| ee_gen_class_msg(); |
| break; |
| case AE_REQ_TYPE: |
| ee_gen_type_msg(); |
| break; |
| case AE_REQ_DETAIL: |
| ee_gen_detail_msg(); |
| break; |
| case AE_REQ_PROCESS: |
| ee_gen_process_msg(); |
| break; |
| case AE_REQ_BACKTRACE: |
| ee_gen_notavail_msg(); |
| break; |
| case AE_REQ_COREDUMP: |
| ee_gen_coredump_msg(); |
| break; |
| default: |
| pr_info("Unknown command id %d\n", msg.cmdId); |
| ee_gen_notavail_msg(); |
| break; |
| } |
| } else if (msg.cmdType == AE_IND) { |
| switch (msg.cmdId) { |
| case AE_IND_LOG_CLOSE: |
| complete(&aed_ee_com); |
| break; |
| default: |
| /* IGNORE */ |
| break; |
| } |
| } else if (msg.cmdType == AE_RSP) { /* IGNORE */ |
| } |
| |
| return count; |
| } |
| |
| /****************************************************************************** |
| * AED KE File operations |
| *****************************************************************************/ |
| static int aed_ke_open(struct inode *inode, struct file *filp) |
| { |
| int major; |
| int minor; |
| unsigned char *devname; |
| |
| if (compare_cmdline() != 0) |
| return -1; |
| |
| major = MAJOR(inode->i_rdev); |
| minor = MINOR(inode->i_rdev); |
| devname = filp->f_path.dentry->d_iname; |
| pr_debug("%s:(%s)%d:%d\n", __func__, devname, major, minor); |
| return 0; |
| } |
| |
| static int aed_ke_release(struct inode *inode, struct file *filp) |
| { |
| pr_debug("%s:%d:%d\n", __func__, MAJOR(inode->i_rdev), |
| MINOR(inode->i_rdev)); |
| return 0; |
| } |
| |
| static unsigned int aed_ke_poll(struct file *file, |
| struct poll_table_struct *ptable) |
| { |
| if (ke_log_available && ke_log_avail()) |
| return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM; |
| poll_wait(file, &aed_dev.kewait, ptable); |
| return 0; |
| } |
| |
| |
| struct current_ke_buffer { |
| void *data; |
| ssize_t size; |
| }; |
| |
| static void *current_ke_start(struct seq_file *m, loff_t *pos) |
| { |
| struct current_ke_buffer *ke_buffer; |
| int index; |
| |
| ke_buffer = m->private; |
| if (!ke_buffer) |
| return NULL; |
| index = *pos * (PAGE_SIZE - 1); |
| if (index < ke_buffer->size) |
| return ke_buffer->data + index; |
| return NULL; |
| } |
| |
| static void *current_ke_next(struct seq_file *m, void *p, loff_t *pos) |
| { |
| struct current_ke_buffer *ke_buffer; |
| int index; |
| |
| ke_buffer = m->private; |
| if (!ke_buffer) |
| return NULL; |
| ++*pos; |
| index = *pos * (PAGE_SIZE - 1); |
| if (index < ke_buffer->size) |
| return ke_buffer->data + index; |
| return NULL; |
| } |
| |
| static void current_ke_stop(struct seq_file *m, void *p) |
| { |
| } |
| |
| static int current_ke_show(struct seq_file *m, void *p) |
| { |
| unsigned long len; |
| struct current_ke_buffer *ke_buffer; |
| |
| ke_buffer = m->private; |
| if (!ke_buffer) |
| return 0; |
| if (ke_buffer->size < 0) |
| return 0; |
| if ((unsigned long)p >= |
| (unsigned long)ke_buffer->data + ke_buffer->size) |
| return 0; |
| len = (unsigned long)ke_buffer->data + ke_buffer->size - |
| (unsigned long)p; |
| len = len < PAGE_SIZE ? len : (PAGE_SIZE - 1); |
| if (seq_write(m, p, len)) { |
| len = 0; |
| return -1; |
| } |
| return 0; |
| } |
| |
| static const struct seq_operations current_ke_op = { |
| .start = current_ke_start, |
| .next = current_ke_next, |
| .stop = current_ke_stop, |
| .show = current_ke_show |
| }; |
| |
| #define AED_CURRENT_KE_OPEN(ENTRY) \ |
| static int current_ke_##ENTRY##_open(struct inode *inode, struct file *file) \ |
| { \ |
| int ret; \ |
| struct aee_oops *oops; \ |
| struct seq_file *m; \ |
| struct current_ke_buffer *ke_buffer; \ |
| ret = seq_open_private(file, ¤t_ke_op, \ |
| sizeof(struct current_ke_buffer)); \ |
| if (!ret) { \ |
| oops = aed_dev.kerec.lastlog; \ |
| m = file->private_data; \ |
| if (!oops) \ |
| return ret; \ |
| ke_buffer = (struct current_ke_buffer *)m->private; \ |
| ke_buffer->data = oops->ENTRY; \ |
| ke_buffer->size = oops->ENTRY##_len;\ |
| } \ |
| return ret; \ |
| } |
| |
| #define AED_PROC_CURRENT_KE_FOPS(ENTRY) \ |
| static const struct proc_ops proc_current_ke_##ENTRY##_fops = { \ |
| .proc_open = current_ke_##ENTRY##_open, \ |
| .proc_read = seq_read, \ |
| .proc_lseek = seq_lseek, \ |
| .proc_release = seq_release_private, \ |
| } |
| |
| |
| static ssize_t aed_ke_read(struct file *filp, char __user *buf, size_t count, |
| loff_t *f_pos) |
| { |
| return msg_copy_to_user(__func__, aed_dev.kerec.msg, buf, count, f_pos); |
| } |
| |
| static ssize_t aed_ke_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| struct AE_Msg msg; |
| int rsize; |
| |
| /* received a new request means the previous response is unavilable */ |
| /* 1. set position to be zero */ |
| /* 2. destroy the previous response message */ |
| *f_pos = 0; |
| msg_destroy(&aed_dev.kerec.msg); |
| |
| /* the request must be an * AE_Msg buffer */ |
| if (count != sizeof(struct AE_Msg)) { |
| pr_info("ERR: aed_write count=%zx\n", count); |
| return -1; |
| } |
| if (!buf) { |
| pr_info("ERR: aed_write buf=NULL\n"); |
| return -1; |
| } |
| rsize = copy_from_user(&msg, buf, count); |
| if (rsize) { |
| pr_info("copy_from_user rsize=%d\n", rsize); |
| return -1; |
| } |
| |
| /* the same reason removing "AEE api log available". |
| * msg_show(__func__, &msg); |
| */ |
| |
| if (msg.cmdType == AE_REQ) { |
| if (!ke_log_avail()) { |
| ke_gen_notavail_msg(); |
| |
| return count; |
| } |
| |
| switch (msg.cmdId) { |
| case AE_REQ_CLASS: |
| ke_gen_class_msg(); |
| break; |
| case AE_REQ_TYPE: |
| ke_gen_type_msg(); |
| break; |
| case AE_REQ_MODULE: |
| ke_gen_module_msg(); |
| break; |
| case AE_REQ_DETAIL: |
| ke_gen_detail_msg(&msg); |
| break; |
| case AE_REQ_BACKTRACE: |
| ke_gen_backtrace_msg(); |
| break; |
| case AE_REQ_USERSPACEBACKTRACE: |
| ke_gen_userbacktrace_msg(); |
| break; |
| case AE_REQ_USER_REG: |
| ke_gen_user_reg_msg(); |
| break; |
| case AE_REQ_USER_MAPS: |
| ke_gen_usermaps_msg(); |
| break; |
| default: |
| ke_gen_notavail_msg(); |
| break; |
| } |
| } else if (msg.cmdType == AE_IND) { |
| switch (msg.cmdId) { |
| case AE_IND_LOG_CLOSE: |
| /* real release operation move to ke_worker(): |
| * ke_destroy_log(); |
| */ |
| ke_log_available = 0; |
| complete(&aed_ke_com); |
| break; |
| default: |
| /* IGNORE */ |
| break; |
| } |
| } else if (msg.cmdType == AE_RSP) { /* IGNORE */ |
| } |
| |
| return count; |
| } |
| |
| #if IS_ENABLED(CONFIG_MTK_AVOID_TRUNCATE_COREDUMP) |
| void Maps2Buffer(unsigned char *Userthread_maps, int *Userthread_mapsLength, |
| const char *fmt, ...) |
| { |
| int max = 256; |
| int n; |
| int len; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| len = strlen(Userthread_maps); |
| |
| if ((len + max) < MaxMapsSize) { |
| n = vsnprintf(&Userthread_maps[len], max, fmt, ap); |
| if (n > 0) |
| *Userthread_mapsLength = len + n; |
| } |
| va_end(ap); |
| } |
| #if 0 |
| static void print_vma_name(unsigned char *Userthread_maps, |
| int *Userthread_mapsLength, struct vm_area_struct *vma, char *str) |
| { |
| const char __user *name = vma_get_anon_name(vma); |
| struct mm_struct *mm = vma->vm_mm; |
| |
| unsigned long page_start_vaddr; |
| unsigned long page_offset; |
| unsigned long num_pages; |
| unsigned long max_len = NAME_MAX; |
| int i; |
| |
| page_start_vaddr = (unsigned long)name & PAGE_MASK; |
| page_offset = (unsigned long)name - page_start_vaddr; |
| num_pages = DIV_ROUND_UP(page_offset + max_len, PAGE_SIZE); |
| |
| for (i = 0; i < num_pages; i++) { |
| int len; |
| int write_len; |
| const char *kaddr; |
| long pages_pinned; |
| struct page *page = NULL; |
| |
| pages_pinned = get_user_pages_remote(mm, |
| page_start_vaddr, 1, 0, &page, NULL, NULL); |
| if (pages_pinned < 1) |
| return; |
| |
| if (!page) { |
| pr_info("%s: page is null\n", __func__); |
| return; |
| } |
| |
| kaddr = (const char *)kmap(page); |
| len = min(max_len, PAGE_SIZE - page_offset); |
| write_len = strnlen(kaddr + page_offset, len); |
| if (strnstr((kaddr + page_offset), "signal stack", write_len)) { |
| char *name = vmalloc(write_len + 1); |
| |
| if (!name) { |
| Maps2Buffer(Userthread_maps, |
| Userthread_mapsLength, |
| "%s[anon:%s]\n", str, "NULL"); |
| } else { |
| memcpy(name, kaddr + page_offset, write_len); |
| name[write_len] = '\0'; |
| Maps2Buffer(Userthread_maps, |
| Userthread_mapsLength, |
| "%s[anon:%s]\n", str, name); |
| vfree(name); |
| } |
| } |
| |
| kunmap(page); |
| put_page(page); |
| |
| /* if strnlen hit a null terminator then we're done */ |
| if (write_len != len) |
| break; |
| |
| max_len -= len; |
| page_offset = 0; |
| page_start_vaddr += PAGE_SIZE; |
| } |
| } |
| #endif |
| static int is_stack(struct vm_area_struct *vma) |
| { |
| return vma->vm_start <= vma->vm_mm->start_stack && |
| vma->vm_end >= vma->vm_mm->start_stack; |
| } |
| |
| static void show_map_vma(unsigned char *Userthread_maps, |
| int *Userthread_mapsLength, struct vm_area_struct *vma) |
| { |
| struct mm_struct *mm = vma->vm_mm; |
| struct file *file = vma->vm_file; |
| vm_flags_t flags = vma->vm_flags; |
| unsigned long ino = 0; |
| unsigned long long pgoff = 0; |
| unsigned long start, end; |
| dev_t dev = 0; |
| const char *name = NULL; |
| struct path base_path; |
| char tpath[512]; |
| char *path_p = NULL; |
| //char str[512]; |
| //int len; |
| |
| if (file) { |
| struct inode *inode = file_inode(vma->vm_file); |
| |
| dev = inode->i_sb->s_dev; |
| ino = inode->i_ino; |
| pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT; |
| } |
| |
| // We don't show the stack guard page in /proc/maps |
| start = vma->vm_start; |
| end = vma->vm_end; |
| |
| // |
| // * Print the dentry name for named mappings, and a |
| // * special [heap] marker for the heap: |
| // |
| if (file) { |
| base_path = file->f_path; |
| path_p = d_path(&base_path, tpath, 512); |
| goto done; |
| } |
| |
| if (vma->vm_ops && vma->vm_ops->name) { |
| name = vma->vm_ops->name(vma); |
| if (name) |
| goto done; |
| } |
| name = aee_arch_vma_name(vma); |
| if (!name) { |
| if (!mm) { |
| name = "[vdso]"; |
| goto done; |
| } |
| |
| if (vma->vm_start <= mm->brk && |
| vma->vm_end >= mm->start_brk) { |
| name = "[heap]"; |
| goto done; |
| } |
| |
| if (is_stack(vma)) { |
| name = "[stack]"; |
| goto done; |
| } |
| |
| /*if (vma_get_anon_name(vma)) { |
| len = snprintf(str, sizeof(str), |
| "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu ", |
| start, end, flags & VM_READ ? 'r' : '-', |
| flags & VM_WRITE ? 'w' : '-', |
| flags & VM_EXEC ? 'x' : '-', |
| flags & VM_MAYSHARE ? 's' : 'p', |
| pgoff, MAJOR(dev), MINOR(dev), ino); |
| if (len < 0) { |
| pr_info("%s: snprintf failed\n", __func__); |
| return; |
| } |
| print_vma_name(Userthread_maps, Userthread_mapsLength, |
| vma, str); |
| return; |
| }*/ |
| } |
| |
| done: |
| |
| if (file && (flags & VM_EXEC)) { |
| Maps2Buffer(Userthread_maps, Userthread_mapsLength, |
| "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu %s\n", |
| start, end, flags & VM_READ ? 'r' : '-', |
| flags & VM_WRITE ? 'w' : '-', |
| flags & VM_EXEC ? 'x' : '-', |
| flags & VM_MAYSHARE ? 's' : 'p', |
| pgoff, |
| MAJOR(dev), MINOR(dev), ino, path_p); |
| } |
| |
| if (name && (flags & VM_WRITE)) { |
| Maps2Buffer(Userthread_maps, Userthread_mapsLength, |
| "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu %s\n", |
| start, end, flags & VM_READ ? 'r' : '-', |
| flags & VM_WRITE ? 'w' : '-', |
| flags & VM_EXEC ? 'x' : '-', |
| flags & VM_MAYSHARE ? 's' : 'p', |
| pgoff, MAJOR(dev), MINOR(dev), ino, name); |
| } |
| } |
| |
| #endif |
| /* |
| * aed process daemon and other command line may access me |
| * concurrently |
| */ |
| DEFINE_SEMAPHORE(aed_dal_sem); |
| static long aed_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| int ret = 0; |
| int aee_mode_tmp = 0; |
| int aee_force_exp_tmp = 0; |
| int pid; |
| struct aee_siginfo aee_si; |
| #if IS_ENABLED(CONFIG_MTK_AVOID_TRUNCATE_COREDUMP) |
| struct aee_thread_reg *tmp; |
| #endif |
| |
| if (!arg && (cmd != AEEIOCTL_DAL_CLEAN)) { |
| pr_info("ERR: %s arg=NULL\n", __func__); |
| return -EINVAL; |
| } |
| if (down_interruptible(&aed_dal_sem) < 0) |
| return -ERESTARTSYS; |
| |
| switch (cmd) { |
| case AEEIOCTL_SET_AEE_MODE: |
| if (strncmp(current->comm, "aee_aed", 7)) { |
| pr_info("unexpected user: %s", current->comm); |
| goto EXIT; |
| } |
| |
| if (copy_from_user(&aee_mode_tmp, (void __user *)arg, |
| sizeof(aee_mode_tmp))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if ((aee_mode_tmp >= AEE_MODE_MTK_ENG) && |
| (aee_mode_tmp <= AEE_MODE_CUSTOMER_USER)) { |
| aee_mode = aee_mode_tmp; |
| } else { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| pr_debug("set aee mode = %d\n", aee_mode); |
| break; |
| case AEEIOCTL_SET_AEE_FORCE_EXP: |
| if (copy_from_user(&aee_force_exp_tmp, |
| (void __user *)arg, |
| sizeof(aee_force_exp_tmp))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if ((aee_force_exp_tmp == AEE_FORCE_EXP_DISABLE) || |
| (aee_force_exp_tmp == AEE_FORCE_EXP_ENABLE)) { |
| aee_force_exp = aee_force_exp_tmp; |
| } else { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| pr_debug("set aee force_exp = %d\n", aee_force_exp); |
| break; |
| #if IS_ENABLED(CONFIG_MTK_AVOID_TRUNCATE_COREDUMP) |
| case AEEIOCTL_GET_THREAD_REG: |
| pr_debug("%s: get thread registers ioctl\n", __func__); |
| |
| tmp = kzalloc(sizeof(struct aee_thread_reg), |
| GFP_KERNEL); |
| if (!tmp) { |
| ret = -ENOMEM; |
| goto EXIT; |
| } |
| |
| if (copy_from_user( |
| tmp, (struct aee_thread_reg __user *)arg, |
| sizeof(struct aee_thread_reg))) { |
| kfree(tmp); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if (tmp->tid > 0) { |
| struct task_struct *task; |
| struct pt_regs *user_ret; |
| |
| rcu_read_lock(); |
| task = pid_task( |
| find_pid_ns(tmp->tid, |
| task_active_pid_ns(current)), |
| PIDTYPE_PID); |
| if (!task || !task->stack) { |
| kfree(tmp); |
| rcu_read_unlock(); |
| ret = -EINVAL; |
| goto EXIT; |
| } |
| |
| user_ret = task_pt_regs(task); |
| memcpy(&(tmp->regs), user_ret, |
| sizeof(struct pt_regs)); |
| rcu_read_unlock(); |
| if (copy_to_user((struct aee_thread_reg __user *)arg, |
| tmp, sizeof(struct aee_thread_reg))) { |
| kfree(tmp); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| } else { |
| pr_info("%s: get thread registers ioctl tid invalid\n", |
| __func__); |
| kfree(tmp); |
| ret = -EINVAL; |
| goto EXIT; |
| } |
| |
| kfree(tmp); |
| |
| break; |
| case AEEIOCTL_GET_THREAD_STACK_RAW: |
| { |
| struct unwind_info_stack stack_raw; |
| struct task_struct *task; |
| struct vm_area_struct *vma; |
| unsigned long start = 0; |
| unsigned long end = 0, length = 0; |
| unsigned char *stack; |
| int copied; |
| |
| pr_info("Get direct unwind backtrace stack"); |
| |
| if (copy_from_user((void *)(&stack_raw), |
| (struct unwind_info_stack __user *)arg, |
| sizeof(struct unwind_info_stack))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| rcu_read_lock(); |
| task = pid_task( |
| find_pid_ns(stack_raw.tid, |
| task_active_pid_ns(current)), |
| PIDTYPE_PID); |
| if (!task || !task->mm) { |
| rcu_read_unlock(); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| rcu_read_unlock(); |
| |
| start = stack_raw.sp; |
| down_read(&task->mm->mmap_lock); |
| vma = task->mm->mmap; |
| while (vma) { |
| if (vma->vm_start <= start && |
| vma->vm_end >= start) { |
| end = vma->vm_end; |
| break; |
| } |
| vma = vma->vm_next; |
| if (vma == task->mm->mmap) |
| break; |
| } |
| up_read(&task->mm->mmap_lock); |
| |
| if (end == 0) { |
| pr_info("Dump native stack failed:\n"); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| length = ((end - start) < (MaxStackSize-1)) |
| ? (end - start) : (MaxStackSize-1); |
| stack_raw.StackLength = length; |
| |
| stack = vmalloc(MaxStackSize); |
| if (!stack) { |
| ret = -ENOMEM; |
| goto EXIT; |
| } |
| |
| copied = access_process_vm(task, start, stack, |
| length, 0); |
| if (copied != length) { |
| pr_info("Access stack error"); |
| vfree(stack); |
| ret = -EIO; |
| goto EXIT; |
| } |
| |
| if (copy_to_user(stack_raw.Userthread_Stack, stack, length)) { |
| vfree(stack); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if (copy_to_user((struct unwind_info_stack __user *)arg, |
| &stack_raw, sizeof(struct unwind_info_stack))) { |
| vfree(stack); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| vfree(stack); |
| break; |
| } |
| case AEEIOCTL_GET_THREAD_RMS: |
| { |
| struct unwind_info_rms thread_info; |
| struct vm_area_struct *vma; |
| int mapcount = 0; |
| unsigned long start = 0; |
| unsigned long end = 0, length = 0; |
| unsigned char *maps; |
| int mapsLength = 0; |
| unsigned char *stack; |
| int copied; |
| struct mm_struct *rms_mm; |
| |
| pr_info("Get direct unwind backtrace info"); |
| |
| if (copy_from_user(&thread_info, |
| (struct unwind_info_rms __user *)arg, |
| sizeof(struct unwind_info_rms))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if (thread_info.tid > 0) { |
| struct task_struct *task; |
| struct pt_regs *user_ret; |
| |
| rcu_read_lock(); |
| task = pid_task( |
| find_pid_ns(thread_info.tid, |
| task_active_pid_ns(current)), |
| PIDTYPE_PID); |
| if (!task || !task->stack) { |
| rcu_read_unlock(); |
| ret = -EINVAL; |
| goto EXIT; |
| } |
| |
| get_task_struct(task); |
| |
| rcu_read_unlock(); |
| |
| // 1. get registers |
| user_ret = task_pt_regs(task); |
| |
| if (copy_to_user((void *)thread_info.regs, user_ret, |
| sizeof(struct pt_regs))) { |
| ret = -EFAULT; |
| put_task_struct(task); |
| goto EXIT; |
| } |
| |
| // 2. get maps |
| if ((!user_mode(user_ret))) { |
| ret = -EFAULT; |
| put_task_struct(task); |
| goto EXIT; |
| } |
| |
| rms_mm = get_task_mm(task); |
| if (!rms_mm) { |
| ret = -EFAULT; |
| put_task_struct(task); |
| goto EXIT; |
| } |
| |
| maps = vmalloc(MaxMapsSize); |
| if (!maps) { |
| ret = -ENOMEM; |
| mmput(rms_mm); |
| put_task_struct(task); |
| goto EXIT; |
| } |
| memset(maps, 0, MaxMapsSize); |
| |
| mmap_read_lock(rms_mm); |
| vma = rms_mm->mmap; |
| while (vma && (mapcount < rms_mm->map_count)) { |
| show_map_vma(maps, &mapsLength, vma); |
| vma = vma->vm_next; |
| mapcount++; |
| } |
| |
| // 3. get stack |
| #ifndef __aarch64__ //K32+U32 |
| start = (ulong)user_ret->ARM_sp; |
| #else |
| if (is_compat_task()) //K64+U32 |
| start = (ulong)user_ret->user_regs.regs[13]; |
| else //K64+U64 |
| start = (ulong)user_ret->user_regs.sp; |
| #endif |
| vma = rms_mm->mmap; |
| while (vma) { |
| if (vma->vm_start <= start && |
| vma->vm_end >= start) { |
| end = vma->vm_end; |
| break; |
| } |
| vma = vma->vm_next; |
| if (vma == rms_mm->mmap) |
| break; |
| } |
| |
| mmap_read_unlock(rms_mm); |
| mmput(rms_mm); |
| put_task_struct(task); |
| if (copy_to_user(thread_info.Userthread_maps, |
| maps, mapsLength)) { |
| vfree(maps); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| vfree(maps); |
| thread_info.Userthread_mapsLength = mapsLength; |
| if (end == 0) { |
| pr_info("Dump native stack failed:\n"); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| length = ((end - start) < (MaxStackSize-1)) ? |
| (end - start) : (MaxStackSize-1); |
| thread_info.StackLength = length; |
| |
| stack = vmalloc(MaxStackSize); |
| if (!stack) { |
| ret = -ENOMEM; |
| goto EXIT; |
| } |
| |
| copied = access_process_vm(task, start, |
| stack, length, 0); |
| if (copied != length) { |
| pr_info("Access stack error"); |
| vfree(stack); |
| ret = -EIO; |
| goto EXIT; |
| } |
| |
| if (copy_to_user(thread_info.Userthread_Stack, |
| stack, length)) { |
| vfree(stack); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if (copy_to_user((struct unwind_info_rms __user *)arg, |
| &thread_info, sizeof(struct unwind_info_rms))) { |
| vfree(stack); |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| vfree(stack); |
| } |
| break; |
| } |
| #endif |
| case AEEIOCTL_CHECK_SUID_DUMPABLE: |
| pr_debug("%s: check suid dumpable ioctl\n", __func__); |
| |
| if (copy_from_user(&pid, (void __user *)arg, sizeof(int))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if (pid > 0) { |
| struct task_struct *task; |
| int dumpable = -1; |
| |
| rcu_read_lock(); |
| task = pid_task( |
| find_pid_ns(pid, |
| task_active_pid_ns(current)), |
| PIDTYPE_PID); |
| if (!task) { |
| pr_info("%s: process:%d task null\n", |
| __func__, pid); |
| rcu_read_unlock(); |
| ret = -EINVAL; |
| goto EXIT; |
| } |
| |
| task_lock(task); |
| if (!task->mm) { |
| pr_info("%s: process:%d task mm null\n", |
| __func__, pid); |
| task_unlock(task); |
| rcu_read_unlock(); |
| ret = -EINVAL; |
| goto EXIT; |
| } |
| |
| dumpable = get_dumpable(task->mm); |
| if (!dumpable) { |
| pr_info("%s: %d no need set dumpable\n", |
| __func__, pid); |
| } else { |
| pr_info("%s: get process:%d dumpable:%d\n", |
| __func__, pid, dumpable); |
| } |
| task_unlock(task); |
| rcu_read_unlock(); |
| } else { |
| pr_info("%s: check suid dumpable ioctl pid invalid\n", |
| __func__); |
| ret = -EINVAL; |
| } |
| |
| break; |
| case AEEIOCTL_SET_FORECE_RED_SCREEN: |
| if (copy_from_user( |
| &force_red_screen, (void __user *)arg, |
| sizeof(force_red_screen))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| pr_debug("force aee red screen = %d\n", force_red_screen); |
| break; |
| case AEEIOCTL_GET_AEE_SIGINFO: |
| pr_debug("%s: get aee_siginfo ioctl\n", __func__); |
| |
| if (copy_from_user(&aee_si, |
| (struct aee_siginfo __user *)arg, |
| sizeof(aee_si))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| |
| if (aee_si.tid > 0) { |
| struct task_struct *task; |
| kernel_siginfo_t *psi; |
| |
| rcu_read_lock(); |
| task = pid_task( |
| find_pid_ns(aee_si.tid, |
| task_active_pid_ns(current)), |
| PIDTYPE_PID); |
| if (!task) { |
| rcu_read_unlock(); |
| ret = -EINVAL; |
| goto EXIT; |
| } |
| rcu_read_unlock(); |
| |
| psi = task->last_siginfo; |
| if (psi) { |
| aee_si.si_signo = psi->si_signo; |
| aee_si.si_errno = psi->si_errno; |
| aee_si.si_code = psi->si_code; |
| aee_si.fault_addr = (uintptr_t)psi->si_addr; |
| if (copy_to_user( |
| (struct aee_siginfo __user *)arg |
| , &aee_si, sizeof(aee_si))) { |
| ret = -EFAULT; |
| goto EXIT; |
| } |
| } |
| } else { |
| pr_info("%s: get aee_siginfo ioctl tid invalid\n", |
| __func__); |
| ret = -EINVAL; |
| goto EXIT; |
| } |
| |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| |
| EXIT: |
| up(&aed_dal_sem); |
| return ret; |
| } |
| |
| static void aed_get_traces(char *msg) |
| { |
| #ifdef CONFIG_STACKTRACE |
| unsigned long stacks[32]; |
| int nr_entries; |
| int i; |
| int offset; |
| |
| nr_entries = stack_trace_save(stacks, ARRAY_SIZE(stacks), 2); |
| offset = strlen(msg); |
| for (i = 0; i < nr_entries; i++) { |
| offset += snprintf(msg + offset, AEE_BACKTRACE_LENGTH - offset, |
| "[<%px>] %pS\n", (void *)stacks[i], |
| (void *)stacks[i]); |
| } |
| #else |
| pr_info("kernel config of STACKTRACE is disabled\n"); |
| #endif |
| } |
| |
| static void kernel_reportAPI(const enum AE_DEFECT_ATTR attr, const int db_opt, |
| const char *module, const char *msg) |
| { |
| struct aee_oops *oops; |
| int n = 0; |
| #ifdef CONFIG_RTC_LIB |
| struct rtc_time tm; |
| struct timespec64 tv; |
| #endif |
| |
| if ((attr == AE_DEFECT_EXCEPTION) && (strstr(msg, "GPUHS") || |
| strstr(module, "cache parity") || strstr(module, "DEVMPU"))) |
| slog("#$#kernel#@#%s#:%s", module, msg); |
| |
| if ((aee_mode >= AEE_MODE_CUSTOMER_USER || (aee_mode == |
| AEE_MODE_CUSTOMER_ENG && attr == AE_DEFECT_WARNING)) |
| && (attr != AE_DEFECT_FATAL)) { |
| if (!aed_get_status() && (kernelapi_num < 5)) |
| kernelapi_num++; |
| else |
| return; |
| } |
| oops = aee_oops_create(attr, AE_KERNEL_PROBLEM_REPORT, module); |
| if (oops) { |
| n += snprintf(oops->backtrace, AEE_BACKTRACE_LENGTH, msg); |
| #ifdef CONFIG_RTC_LIB |
| ktime_get_real_ts64(&tv); |
| rtc_time64_to_tm(tv.tv_sec - sys_tz.tz_minuteswest * 60, &tm); |
| n += snprintf(oops->backtrace + n, AEE_BACKTRACE_LENGTH - n, |
| "\nTrigger time:[%d-%02d-%02d %02d:%02d:%02d.%03d]\n", |
| tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, |
| tm.tm_hour, tm.tm_min, tm.tm_sec, |
| (unsigned int)(tv.tv_nsec / 1000)); |
| #endif |
| n += snprintf(oops->backtrace + n, AEE_BACKTRACE_LENGTH - n, |
| "\nBacktrace:\n"); |
| if (n < 0) |
| pr_info("%s: snprintf failed\n", __func__); |
| aed_get_traces(oops->backtrace); |
| oops->detail = (char *)(oops->backtrace); |
| oops->detail_len = strlen(oops->backtrace) + 1; |
| oops->dump_option = db_opt; |
| |
| pr_debug("%s,%s,%s,0x%x\n", __func__, module, msg, db_opt); |
| ke_queue_request(oops); |
| } |
| } |
| |
| static void external_exception(const char *assert_type, const int *log, |
| int log_size, const int *phy, int phy_size, |
| const char *detail, const int db_opt) |
| { |
| int *ee_log; |
| struct aed_eerec *eerec; |
| #ifdef CONFIG_RTC_LIB |
| struct rtc_time tm; |
| struct timespec64 tv = { 0 }; |
| char trigger_time[60]; |
| int n; |
| #endif |
| |
| if (strstr(assert_type, "combo_bt") || strstr(assert_type, "combo_wifi")) |
| slog("#$#external#@#%s#%s", assert_type, detail); |
| |
| if ((aee_mode >= AEE_MODE_CUSTOMER_USER) && |
| (aee_force_exp == AEE_FORCE_EXP_NOT_SET)) { |
| if (!aed_get_status() && (ee_num < 5)) |
| ee_num++; |
| else |
| return; |
| } |
| eerec = kzalloc(sizeof(struct aed_eerec), GFP_ATOMIC); |
| if (!eerec) |
| return; |
| |
| if ((log_size > 0) && log) { |
| eerec->ee_log_size = log_size; |
| ee_log = kmalloc(log_size, GFP_ATOMIC); |
| if (ee_log) { |
| eerec->ee_log = ee_log; |
| memcpy(ee_log, log, log_size); |
| } |
| } else { |
| eerec->ee_log_size = 16; |
| ee_log = kzalloc(eerec->ee_log_size, GFP_ATOMIC); |
| eerec->ee_log = ee_log; |
| } |
| |
| if (!ee_log) { |
| pr_info("%s : memory alloc() fail\n", __func__); |
| kfree(eerec); |
| return; |
| } |
| |
| memset(eerec->assert_type, 0, sizeof(eerec->assert_type)); |
| strncpy(eerec->assert_type, assert_type, |
| sizeof(eerec->assert_type) - 1); |
| memset(eerec->exp_filename, 0, sizeof(eerec->exp_filename)); |
| #ifdef CONFIG_RTC_LIB |
| ktime_get_real_ts64(&tv); |
| rtc_time64_to_tm(tv.tv_sec - sys_tz.tz_minuteswest * 60, &tm); |
| n = snprintf(trigger_time, sizeof(trigger_time), |
| "Trigger time:[%d-%02d-%02d %02d:%02d:%02d.%03d]\n", |
| tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, |
| tm.tm_hour, tm.tm_min, tm.tm_sec, |
| (unsigned int)(tv.tv_nsec / 1000)); |
| if (n < 0) |
| pr_info("%s: snprintf failed\n", __func__); |
| strncpy(eerec->exp_filename, trigger_time, |
| sizeof(eerec->exp_filename) - 1); |
| strncat(eerec->exp_filename, detail, |
| sizeof(eerec->exp_filename) - 1 - strlen(trigger_time)); |
| #else |
| strncpy(eerec->exp_filename, detail, sizeof(eerec->exp_filename) - 1); |
| #endif |
| pr_debug("EE %s\n", eerec->assert_type); |
| |
| eerec->exp_linenum = 0; |
| eerec->fatal1 = 0; |
| eerec->fatal2 = 0; |
| |
| /* Check if we can dump memory */ |
| if (in_interrupt()) { |
| /* kernel vamlloc cannot be used in interrupt context */ |
| pr_info( |
| "External exception occur in interrupt context, no coredump"); |
| phy_size = 0; |
| } else if (!phy || (phy_size > MAX_EE_COREDUMP)) { |
| pr_info("EE Physical memory size(%d) too large or invalid", |
| phy_size); |
| phy_size = 0; |
| } |
| |
| if (phy_size > 0) { |
| eerec->ee_phy = (int *)vmalloc_user(phy_size); |
| if (eerec->ee_phy) { |
| memcpy(eerec->ee_phy, phy, phy_size); |
| eerec->ee_phy_size = phy_size; |
| } else { |
| pr_info("Losing ee phy mem due to vmalloc return NULL\n"); |
| eerec->ee_phy_size = 0; |
| } |
| } else { |
| eerec->ee_phy = NULL; |
| eerec->ee_phy_size = 0; |
| } |
| eerec->db_opt = db_opt; |
| ee_queue_request(eerec); |
| pr_debug("%s out\n", __func__); |
| } |
| |
| static bool rr_reported; |
| /* 0600: S_IRUSR | S_IWUSR */ |
| module_param(rr_reported, bool, 0600); |
| |
| static struct aee_kernel_api kernel_api = { |
| .kernel_reportAPI = kernel_reportAPI, |
| .md_exception = external_exception, |
| .md32_exception = external_exception, |
| .scp_exception = external_exception, |
| .combo_exception = external_exception, |
| .common_exception = external_exception |
| }; |
| |
| static int current_ke_ee_coredump_open(struct inode *inode, struct file *file) |
| { |
| int ret = seq_open_private(file, ¤t_ke_op, |
| sizeof(struct current_ke_buffer)); |
| |
| if (!ret) { |
| struct aed_eerec *eerec = aed_dev.eerec; |
| struct seq_file *m = file->private_data; |
| struct current_ke_buffer *ee_buffer; |
| |
| if (!eerec) |
| return ret; |
| ee_buffer = (struct current_ke_buffer *)m->private; |
| ee_buffer->data = eerec->ee_phy; |
| ee_buffer->size = eerec->ee_phy_size; |
| } |
| return ret; |
| } |
| |
| /* AED_CURRENT_KE_OPEN(ee_coredump); */ |
| AED_PROC_CURRENT_KE_FOPS(ee_coredump); |
| |
| |
| static int aed_proc_init(void) |
| { |
| aed_proc_dir = proc_mkdir("aed", NULL); |
| if (!aed_proc_dir) { |
| pr_info("aed proc_mkdir failed\n"); |
| return -ENOMEM; |
| } |
| /* 0400: S_IRUSR */ |
| AED_PROC_ENTRY(current-ee-coredump, current_ke_ee_coredump, 0400); |
| |
| aee_rr_proc_init(aed_proc_dir); |
| #if IS_ENABLED(CONFIG_MTK_AEE_UT) |
| aed_proc_debug_init(aed_proc_dir); |
| #endif |
| |
| return 0; |
| } |
| |
| static int aed_proc_done(void) |
| { |
| remove_proc_entry(CURRENT_EE_COREDUMP, aed_proc_dir); |
| |
| aee_rr_proc_done(aed_proc_dir); |
| #if IS_ENABLED(CONFIG_MTK_AEE_UT) |
| aed_proc_debug_done(aed_proc_dir); |
| #endif |
| |
| remove_proc_entry("aed", NULL); |
| return 0; |
| } |
| |
| /****************************************************************************** |
| * Module related |
| *****************************************************************************/ |
| static const struct file_operations aed_ee_fops = { |
| .owner = THIS_MODULE, |
| .open = aed_ee_open, |
| .release = aed_ee_release, |
| .poll = aed_ee_poll, |
| .read = aed_ee_read, |
| .write = aed_ee_write, |
| .unlocked_ioctl = aed_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = aed_ioctl, |
| #endif |
| }; |
| |
| static const struct file_operations aed_ke_fops = { |
| .owner = THIS_MODULE, |
| .open = aed_ke_open, |
| .release = aed_ke_release, |
| .poll = aed_ke_poll, |
| .read = aed_ke_read, |
| .write = aed_ke_write, |
| .unlocked_ioctl = aed_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = aed_ioctl, |
| #endif |
| }; |
| |
| /* QHQ RT Monitor end */ |
| static struct miscdevice aed_ee_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "aed0", |
| .fops = &aed_ee_fops, |
| }; |
| |
| |
| |
| static struct miscdevice aed_ke_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "aed1", |
| .fops = &aed_ke_fops, |
| }; |
| |
| static int aee_is_enable(void) |
| { |
| struct device_node *node; |
| const char *aee_enable; |
| int ret = 0; |
| |
| node = of_find_node_by_path("/chosen"); |
| if (node) { |
| if (of_property_read_string(node, "aee,enable", &aee_enable) == 0) { |
| if (strnstr(aee_enable, "mini", 4)) |
| ret = 1; |
| else if (strnstr(aee_enable, "full", 4)) |
| ret = 2; |
| } |
| of_node_put(node); |
| } else { |
| pr_notice("%s: Can't find chosen node\n", __func__); |
| } |
| |
| return ret; |
| } |
| |
| static int __init aed_init(void) |
| { |
| int err; |
| |
| if (!aee_is_enable()) { |
| pr_info("%s: aee is disable\n", __func__); |
| return 0; |
| } |
| |
| err = aed_proc_init(); |
| if (err != 0) |
| return err; |
| |
| spin_lock_init(&ke_queue.lock); |
| spin_lock_init(&ee_queue.lock); |
| INIT_LIST_HEAD(&ke_queue.list); |
| INIT_LIST_HEAD(&ee_queue.list); |
| |
| init_waitqueue_head(&aed_dev.eewait); |
| memset(&aed_dev.kerec, 0, sizeof(struct aed_kerec)); |
| init_waitqueue_head(&aed_dev.kewait); |
| |
| INIT_WORK(&ke_work, ke_worker); |
| INIT_WORK(&ee_work, ee_worker); |
| |
| aee_register_api(&kernel_api); |
| |
| spin_lock_init(&aed_device_lock); |
| err = misc_register(&aed_ee_dev); |
| if (unlikely(err)) { |
| pr_info("aee: failed to register aed0(ee) device!\n"); |
| return err; |
| } |
| |
| err = misc_register(&aed_ke_dev); |
| if (unlikely(err)) { |
| pr_info("aee: failed to register aed1(ke) device!\n"); |
| return err; |
| } |
| pr_notice("aee kernel api ready"); |
| |
| mtk_slog_init(); |
| |
| return err; |
| } |
| |
| static void __exit aed_exit(void) |
| { |
| if (!aee_is_enable()) |
| return; |
| |
| misc_deregister(&aed_ee_dev); |
| misc_deregister(&aed_ke_dev); |
| |
| ee_destroy_log(); |
| ke_destroy_log(); |
| |
| aed_proc_done(); |
| |
| mtk_slog_exit(); |
| } |
| module_init(aed_init); |
| module_exit(aed_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("MediaTek AED Driver"); |
| MODULE_AUTHOR("MediaTek Inc."); |