* 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 <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
#include <stdarg.h>
#else
#include <linux/stdarg.h>
#endif
#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");
}
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, ":");
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
}