/* * Copyright (C) 2022 Huawei Technologies Co., Ltd. * Decription: for tzdriver debug. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "tzdebug.h" #include <linux/workqueue.h> #include <linux/kthread.h> #include <linux/list.h> #include <linux/sched.h> #include <linux/delay.h> #include <linux/mutex.h> #include <linux/timer.h> #include <linux/kernel.h> #include <linux/uaccess.h> #include <linux/debugfs.h> #include <linux/module.h> #include <linux/seq_file.h> #include <stdarg.h> #include <linux/mm.h> #include <asm/tlbflush.h> #include <asm/cacheflush.h> #include <securec.h> #include <asm/io.h> #include "tc_ns_log.h" #include "tc_ns_client.h" #include "tc_client_driver.h" #include "teek_ns_client.h" #include "smc_smp.h" #include "teek_client_constants.h" #include "mailbox_mempool.h" #include "tlogger.h" #include "cmdmonitor.h" #include "session_manager.h" #include "internal_functions.h" #define DEBUG_OPT_LEN 128 #ifdef CONFIG_TA_MEM_INUSE_ONLY #define TA_MEMSTAT_ALL 0 #else #define TA_MEMSTAT_ALL 1 #endif static struct dentry *g_tz_dbg_dentry; typedef void (*tzdebug_opt_func)(const char *param); struct opt_ops { char *name; tzdebug_opt_func func; }; static DEFINE_MUTEX(g_meminfo_lock); static struct tee_mem g_tee_meminfo; static void tzmemdump(const char *param); static int send_dump_mem(int flag, int history, const struct tee_mem *statmem) { struct tc_ns_smc_cmd smc_cmd = { {0}, 0 }; struct mb_cmd_pack *mb_pack = NULL; int ret = 0; if (!statmem) { tloge("statmem is NULL\n"); return -EINVAL; } mb_pack = mailbox_alloc_cmd_pack(); if (!mb_pack) return -ENOMEM; smc_cmd.cmd_id = GLOBAL_CMD_ID_DUMP_MEMINFO; smc_cmd.cmd_type = CMD_TYPE_GLOBAL; mb_pack->operation.paramtypes = teec_param_types( TEE_PARAM_TYPE_MEMREF_INOUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); mb_pack->operation.params[0].memref.buffer = (unsigned int)mailbox_virt_to_phys((uintptr_t)statmem); mb_pack->operation.params[0].memref.size = sizeof(*statmem); mb_pack->operation.buffer_h_addr[0] = (unsigned int)((uint64_t)mailbox_virt_to_phys((uintptr_t)statmem) >> ADDR_TRANS_NUM); mb_pack->operation.params[1].value.a = (unsigned int)flag; mb_pack->operation.params[1].value.b = (unsigned int)history; smc_cmd.operation_phys = (unsigned int)mailbox_virt_to_phys((uintptr_t)&mb_pack->operation); smc_cmd.operation_h_phys = (unsigned int)((uint64_t)mailbox_virt_to_phys((uintptr_t)&mb_pack->operation) >> ADDR_TRANS_NUM); livepatch_down_read_sem(); if (tc_ns_smc(&smc_cmd) != 0) { ret = -EPERM; tloge("send dump mem failed\n"); } livepatch_up_read_sem(); tz_log_write(); mailbox_free(mb_pack); return ret; } void tee_dump_mem(void) { tzmemdump(NULL); if (tlogger_store_msg(CONFIG_TEE_LOG_ACHIVE_PATH, sizeof(CONFIG_TEE_LOG_ACHIVE_PATH)) < 0) tloge("[cmd_monitor_tick]tlogger store lastmsg failed\n"); } /* get meminfo (tee_mem + N * ta_mem < 4Kbyte) from tee */ static int get_tee_meminfo_cmd(void) { int ret; struct tee_mem *mem = NULL; mem = mailbox_alloc(sizeof(*mem), MB_FLAG_ZERO); if (!mem) return -ENOMEM; ret = send_dump_mem(0, TA_MEMSTAT_ALL, mem); if (ret != 0) { tloge("send dump failed\n"); mailbox_free(mem); return ret; } mutex_lock(&g_meminfo_lock); ret = memcpy_s(&g_tee_meminfo, sizeof(g_tee_meminfo), mem, sizeof(*mem)); if (ret != 0) tloge("memcpy failed\n"); mutex_unlock(&g_meminfo_lock); mailbox_free(mem); return ret; } static atomic_t g_cmd_send = ATOMIC_INIT(1); void set_cmd_send_state(void) { atomic_set(&g_cmd_send, 1); } int get_tee_meminfo(struct tee_mem *meminfo) { errno_t s_ret; if (!get_tz_init_flag()) return EFAULT; if (!meminfo) return -EINVAL; if (atomic_read(&g_cmd_send) != 0) { if (get_tee_meminfo_cmd() != 0) return -EFAULT; } else { atomic_set(&g_cmd_send, 0); } mutex_lock(&g_meminfo_lock); s_ret = memcpy_s(meminfo, sizeof(*meminfo), &g_tee_meminfo, sizeof(g_tee_meminfo)); mutex_unlock(&g_meminfo_lock); if (s_ret != 0) return -1; return 0; } EXPORT_SYMBOL(get_tee_meminfo); static void tzdump(const char *param) { (void)param; show_cmd_bitmap(); wakeup_tc_siq(SIQ_DUMP_SHELL); } static void tzmemdump(const char *param) { struct tee_mem *mem = NULL; (void)param; mem = mailbox_alloc(sizeof(*mem), MB_FLAG_ZERO); if (!mem) { tloge("mailbox alloc failed\n"); return; } if (send_dump_mem(1, 1, mem) != 0) tloge("send dump mem failed\n"); mailbox_free(mem); } static struct opt_ops g_opt_arr[] = { {"dump", tzdump}, {"memdump", tzmemdump}, {"dump_service", dump_services_status}, }; static ssize_t tz_dbg_opt_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { char *obuf = NULL; char *p = NULL; ssize_t ret; uint32_t oboff = 0; uint32_t i; (void)(filp); obuf = kzalloc(DEBUG_OPT_LEN, GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)obuf)) return -ENOMEM; p = obuf; for (i = 0; i < ARRAY_SIZE(g_opt_arr); i++) { int len = snprintf_s(p, DEBUG_OPT_LEN - oboff, DEBUG_OPT_LEN -oboff -1, "%s ", g_opt_arr[i].name); if (len < 0) { kfree(obuf); tloge("snprintf opt name of idx %d failed\n", i); return -EINVAL; } p += len; oboff += (uint32_t)len; } obuf[oboff - 1] = '\n'; ret = simple_read_from_buffer(ubuf, cnt, ppos, obuf, oboff); kfree(obuf); return ret; } static ssize_t tz_dbg_opt_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[128] = {0}; char *value = NULL; char *p = NULL; uint32_t i = 0; if (!ubuf || !filp || !ppos) return -EINVAL; if (cnt >= sizeof(buf)) return -EINVAL; if (cnt == 0) return -EINVAL; if (copy_from_user(buf, ubuf, cnt) != 0) return -EFAULT; buf[cnt] = 0; if (cnt > 0 && buf[cnt -1] == '\n') buf[cnt - 1] = 0; value = buf; p = strsep(&value, ":"); /* when buf has no :, value may be NULL */ if (!p) return -EINVAL; for (i = 0; i < ARRAY_SIZE(g_opt_arr); i++) { if ((strncmp(p, g_opt_arr[i].name, strlen(g_opt_arr[i].name)) ==0) && strlen(p) == strlen(g_opt_arr[i].name)) { g_opt_arr[i].func(value); return (ssize_t)cnt; } } return -EFAULT; } static const struct file_operations g_tz_dbg_opt_fops = { .owner = THIS_MODULE, .read = tz_dbg_opt_read, .write = tz_dbg_opt_write, }; #ifdef CONFIG_MEMSTAT_DEBUGFS static int memstat_debug_show(struct seq_file *m, void *v) { struct tee_mem *mem_stat = NULL; int ret; uint32_t i; (void)v; mem_stat = kzalloc(sizeof(*mem_stat), GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)mem_stat)) return -ENOMEM; ret = get_tee_meminfo(mem_stat); if (ret != 0) { tloge("get tee meminfo failed\n"); kfree(mem_stat); mem_stat = NULL; return -EINVAL; } seq_printf(m, "TotalMem:%u Pmem:%u Free_Mem:%u Free_Mem_Min:%u TA_Num:%u\n", mem_stat->total_mem, mem_stat->pmem, mem_stat->free_mem, mem_stat->free_mem_min, mem_stat->ta_num); for (i = 0; i < mem_stat->ta_num; i++) seq_printf(m, "ta_name:%s ta_pmem:%u pmem_max:%u pmem_limit:%u\n", mem_stat->ta_mem_info[i].ta_name, mem_stat->ta_mem_info[i].pmem, mem_stat->ta_mem_info[i].pmem_max, mem_stat->ta_mem_info[i].pmem_limit); kfree(mem_stat); mem_stat = NULL; return 0; } static int tz_memstat_open(struct inode *inode, struct file *file) { (void)inode; return single_open(file, memstat_debug_show, NULL); } static const struct file_operations g_tz_dbg_memstat_fops = { .owner = THIS_MODULE, .open = tz_memstat_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #endif #ifdef CONFIG_TEE_TRACE static int tee_trace_event_show(struct seq_file *m, void *v) { struct tee_trace_view_t view = { 0, 0, 0, 0, { 0 }, { 0 } }; struct trace_log_info log_info; (void)v; get_tee_trace_start(&view); if (view.buffer_is_full == 1) seq_printf(m, "Total Trace Events: %u (Notice: Buffer is full)\n", view.total); else seq_printf(m, "Total Trace Events: %u\n", view.total); if (view.total > 0) { uint32_t i = 0; while (get_tee_trace_next(&view, &log_info, false) != -1) { uint32_t task_ca = (uint32_t)(log_info.add_info); uint32_t task_idx = (uint32_t)(log_info.add_info >> 32); if (log_info.event_id == SCHED_IN || log_info.event_id == SCHED_OUT) { seq_printf(m, "[%4u][cpu%3u][ca-%5u] %10llu : %s %u %s\n", i++, log_info.cpu, log_info.ca_pid, log_info.time, log_info.event_name, task_ca, get_tee_trace_task_name(task_idx)); } else { seq_printf(m, "[%4u][cpu%3u][ca-%5u] %10llu : %s %llu\n", i++, log_info.cpu, log_info.ca_pid, log_info.time, log_info.event_name, log_info.add_info); } } } return 0; } static int tee_trace_event_open(struct inode *inode, struct file *file) { return single_open(file, tee_trace_event_show, NULL); } struct tee_trace_cmd_t { const char *cmd; int (*func)(void); } tee_trace_cmd[] = { {"start", tee_trace_event_start}, {"loop_record", tee_trace_event_start_loop_record}, {"stop", tee_trace_event_stop} }; static ssize_t tee_trace_event_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[32] = {0}; uint32_t i = 0; if (cnt >= sizeof(buf)) return -EINVAL; if (copy_from_user(buf, ubuf, cnt)) return -EINVAL; buf[cnt] = 0; if (cnt > 0 && buf[cnt - 1] == '\n') buf[cnt - 1] = 0; for (i = 0; i < ARRAY_SIZE(tee_trace_cmd); i++) { if (!strncmp(buf, tee_trace_cmd[i].cmd, strlen(tee_trace_cmd[i].cmd))) { tee_trace_cmd[i].func(); return cnt; } } return -EINVAL; } static const struct file_operations tee_trace_event_fops = { .owner = THIS_MODULE, .open = tee_trace_event_open, .read = seq_read, .write = tee_trace_event_write, .llseek = seq_lseek, .release = single_release, }; #endif int tzdebug_init(void) { #if defined(DEF_ENG) || defined(CONFIG_TZDRIVER_MODULE) g_tz_dbg_dentry = debugfs_create_dir("tzdebug", NULL); if (!g_tz_dbg_dentry) return -1; debugfs_create_file("opt", 0660, g_tz_dbg_dentry, NULL, &g_tz_dbg_opt_fops); #ifdef CONFIG_MEMSTAT_DEBUGFS debugfs_create_file("memstat", 0444, g_tz_dbg_dentry, NULL, &g_tz_dbg_memstat_fops); #endif #ifdef CONFIG_TEE_TRACE debugfs_create_file("tee_trace", 0660, g_tz_dbg_dentry, NULL, &tee_trace_event_fops); tee_trace_event_enable(); #endif #else (void)g_tz_dbg_dentry; (void)g_tz_dbg_opt_fops; #endif return 0; } void free_tzdebug(void) { #if defined(DEF_ENG) || defined(CONFIG_TZDRIVER_MODULE) if (!g_tz_dbg_dentry) return; debugfs_remove_recursive(g_tz_dbg_dentry); g_tz_dbg_dentry = NULL; #endif }