/* * Copyright (C) 2022 Huawei Technologies Co., Ltd. * Decription: TEE Logging Subsystem, read the tee os log from log memory * * 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 "tlogger.h" #include #include #include #include #include #include #include #include #include #include #include "smc_smp.h" #include "mailbox_mempool.h" #include "teek_client_constants.h" #include "tc_ns_client.h" #include "teek_ns_client.h" #include "log_cfg_api.h" #include "tc_ns_log.h" #include "ko_adapt.h" #include "internal_functions.h" #ifdef CONFIG_LOG_POOL #include "shared_mem.h" #endif #ifdef CONFIG_TEE_REBOOT #include "reboot.h" #endif /* for log item ----------------------------------- */ #define LOG_ITEM_MAGIC 0x5A5A #define LOG_ITEM_LEN_ALIGN 64 #define LOG_ITEM_MAX_LEN 1024 #define LOG_READ_STATUS_ERROR 0x000FFFF /* =================================================== */ #define LOGGER_LOG_TEEOS "teelog" /* tee os log */ #define LOGGERIOCTL 0xBE /* for ioctl */ #define DUMP_START_MAGIC "Dump SPI notification" #define DUMP_END_MAGIC "Dump task states END" #define GET_VERSION_BASE 5 #define SET_READERPOS_CUR_BASE 6 #define SET_TLOGCAT_STAT_BASE 7 #define GET_TLOGCAT_STAT_BASE 8 /* get tee verison */ #define MAX_TEE_VERSION_LEN 256 #define TEELOGGER_GET_VERSION \ _IOR(LOGGERIOCTL, GET_VERSION_BASE, char[MAX_TEE_VERSION_LEN]) /* set the log reader pos to current pos */ #define TEELOGGER_SET_READERPOS_CUR \ _IO(LOGGERIOCTL, SET_READERPOS_CUR_BASE) #define TEELOGGER_SET_TLOGCAT_STAT \ _IO(LOGGERIOCTL, SET_TLOGCAT_STAT_BASE) #define TEELOGGER_GET_TLOGCAT_STAT \ _IO(LOGGERIOCTL, GET_TLOGCAT_STAT_BASE) #ifdef CONFIG_LOG_POOL struct teelogger_log_pool { uint64_t addr; uint64_t size; }; #define GET_LOG_POOL_BASE 9 #define LOG_POOL_APPEND_BASE 10 #define TEELOGGER_GET_LOG_POOL \ _IOWR(LOGGERIOCTL, GET_LOG_POOL_BASE, uint64_t) #define TEELOGGER_LOG_POOL_APPEND \ _IOWR(LOGGERIOCTL, LOG_POOL_APPEND_BASE, struct teelogger_log_pool) #endif int g_tlogcat_f = 0; #ifndef CONFIG_TEE_LOG_ACHIVE_PATH #define CONFIG_TEE_LOG_ACHIVE_PATH "/data/log/tee/last_teemsg" #endif #define TEE_LOG_FILE_NAME_MAX 256 uint32_t g_last_read_offset = 0; #define NEVER_USED_LEN 32U #define LOG_ITEM_RESERVED_LEN 1U /* 64 byte head + user log */ struct log_item { unsigned char never_used[NEVER_USED_LEN]; unsigned short magic; unsigned short reserved0; uint32_t serial_no; unsigned short real_len; /* log real len */ unsigned short buffer_len; /* log buffer's len, multiple of 32 bytes */ unsigned char uuid[UUID_LEN]; unsigned char log_source_type; unsigned char reserved[LOG_ITEM_RESERVED_LEN]; unsigned char log_level; unsigned char new_line; /* '\n' char, easy viewing log in bbox.bin file */ unsigned char log_buffer[]; }; /* --- for log mem --------------------------------- */ #define TEMP_LOG_MEM_SIZE (10 * SZ_1K) #define LOG_BUFFER_RESERVED_LEN 11U #define VERSION_INFO_LEN 156U /* * Log's buffer flag info, size: 64 bytes head + 156 bytes's version info. * For filed description: * last_pos : current log's end position, last log's start position. * write_loops: Write cyclically. Init value is 0, when memory is used * up, the value add 1. */ struct log_buffer_flag { uint32_t reserved0; uint32_t last_pos; uint32_t write_loops; uint32_t log_level; /* [0] is magic failed, [1] is serial_no failed, used fior log retention feature */ uint32_t reserved[LOG_BUFFER_RESERVED_LEN]; uint32_t max_len; unsigned char version_info[VERSION_INFO_LEN]; }; struct log_buffer { struct log_buffer_flag flag; unsigned char buffer_start[]; }; static struct log_buffer *g_log_buffer = NULL; struct tlogger_log { unsigned char *buffer_info; /* ring buffer info */ struct mutex mutex_info; /* this mutex protects buffer_info */ wait_queue_head_t wait_queue_head; /* wait queue head for reader */ struct list_head logs; /* log channels list */ struct miscdevice misc_device; /* misc device log */ struct list_head readers; /* log's readers */ }; static LIST_HEAD(m_log_list); struct tlogger_reader { struct tlogger_log *log; /* tlogger_log info data */ struct list_head list; /* log entry in tlogger_log's list */ /* Current reading position, start position of next read again */ uint32_t r_off; uint32_t r_loops; uint32_t r_sn; uint32_t r_failtimes; uint32_t r_from_cur; uint32_t r_is_tlogf; bool r_all; /* whether this reader can read all entries */ uint32_t r_ver; }; static uint32_t g_log_mem_len = 0; static uint32_t g_tlogcat_count = 0; static struct tlogger_log *g_log; static struct tlogger_log *get_reader_log(const struct file *file) { struct tlogger_reader *reader = NULL; reader = file->private_data; if (!reader) return NULL; return reader->log; } static bool check_log_item_validite(const struct log_item *item, uint32_t item_max_size) { bool con = (item && (item->magic == LOG_ITEM_MAGIC) && (item->buffer_len > 0) && (item->real_len > 0) && (item->buffer_len % LOG_ITEM_LEN_ALIGN == 0) && (item->real_len <= item->buffer_len) && ((item->buffer_len - item->real_len) < LOG_ITEM_LEN_ALIGN) && (item->buffer_len + sizeof(*item) <= item_max_size)); return con; } static struct log_item *get_next_log_item(const unsigned char *buffer_start, uint32_t max_len, uint32_t read_pos, uint32_t scope_len, uint32_t *pos) { uint32_t i = 0; struct log_item *item = NULL; uint32_t max_size; if ((read_pos + scope_len) > max_len) return NULL; while ((i + sizeof(*item) + LOG_ITEM_LEN_ALIGN) <= scope_len) { *pos = read_pos + i; item = (struct log_item *)(uintptr_t)(buffer_start + read_pos + i); max_size = (((scope_len - i) > LOG_ITEM_MAX_LEN) ? LOG_ITEM_MAX_LEN : (scope_len - i)); if (check_log_item_validite(item, max_size)) break; i += LOG_ITEM_LEN_ALIGN; item = NULL; } return item; } struct reader_position { const unsigned char *buffer_start; uint32_t max_len; uint32_t start_pos; uint32_t end_pos; }; static uint32_t parse_log_item(char __user *buf, size_t count, struct reader_position *position, uint32_t *read_off, bool *user_buffer_left) { struct log_item *next_item = NULL; size_t buf_left; uint32_t buf_written; uint32_t item_len; bool con = false; uint32_t start_pos = position->start_pos; buf_written = 0; buf_left = count; con = (!read_off || !position->buffer_start); if (con) return buf_written; *user_buffer_left = true; while (start_pos < position->end_pos) { next_item = get_next_log_item(position->buffer_start, position->max_len, start_pos, position->end_pos - start_pos, &start_pos); if (!next_item) break; /* copy to user */ item_len = next_item->buffer_len + sizeof(*next_item); if (buf_left < item_len) { *user_buffer_left = false; break; } start_pos += item_len; if (copy_to_user(buf + buf_written, (void *)next_item, item_len) != 0) tloge("copy failed, item len %u\n", item_len); buf_written += item_len; buf_left -= item_len; } *read_off = start_pos; return buf_written; } static ssize_t get_buffer_info(struct tlogger_reader *reader, struct log_buffer_flag *buffer_flag, struct log_buffer **log_buffer) { struct tlogger_log *log = NULL; errno_t ret; struct log_buffer *buffer_tmp = NULL; log = reader->log; if (!log) return -EINVAL; buffer_tmp = (struct log_buffer*)log->buffer_info; if (!buffer_tmp) return -EINVAL; __asm__ volatile ("isb"); __asm__ volatile ("dsb sy"); mutex_lock(&log->mutex_info); ret = memcpy_s(buffer_flag, sizeof(*buffer_flag), &buffer_tmp->flag, sizeof(buffer_tmp->flag)); mutex_unlock(&log->mutex_info); if (ret != 0) { tloge("memcpy failed %d\n", ret); return -EAGAIN; } *log_buffer = buffer_tmp; return 0; } #define LOG_BUFFER_MAX_LEN 0x100000 static ssize_t get_last_read_pos(struct log_buffer_flag *log_flag, const struct tlogger_reader *reader, uint32_t *log_last_pos, uint32_t *is_read) { uint32_t buffer_max_len = g_log_mem_len - sizeof(*g_log_buffer); *is_read = 0; if (buffer_max_len > LOG_BUFFER_MAX_LEN) return -EINVAL; *log_last_pos = log_flag->last_pos; if (*log_last_pos == reader->r_off && log_flag->write_loops == reader->r_loops) return 0; if (log_flag->max_len < *log_last_pos || log_flag->max_len > buffer_max_len) { tloge("invalid data maxlen %x pos %x\n", log_flag->max_len, *log_last_pos); return -EFAULT; } if (reader->r_off > log_flag->max_len) { tloge("invalid data roff %x maxlen %x\n", reader->r_off, log_flag->max_len); return -EFAULT; } *is_read = 1; return 0; } static void set_reader_position(struct reader_position *position, const unsigned char *buffer_start, uint32_t max_len, uint32_t start_pos, uint32_t end_pos) { position->buffer_start = buffer_start; position->max_len = max_len; position->start_pos = start_pos; position->end_pos = end_pos; } static ssize_t proc_read_ret(uint32_t buf_written, const struct tlogger_reader *reader) { ssize_t ret; if (buf_written == 0) { ret = 0; } else { ret = buf_written; tlogd("read length %u\n", buf_written); g_last_read_offset = reader->r_off; } return ret; } static ssize_t check_read_params(const struct file *file, const char __user *buf, size_t count) { if (count < LOG_ITEM_MAX_LEN) return -EINVAL; if (!file || !buf) return -EINVAL; return 0; } /* * If the sequence number of the last read position is smaller * than the current minimum sequence number, the last read * position is overwritten. And this time read data from * minimum number, or read data from last position. */ static ssize_t trigger_parse_log(char __user *buf, size_t count, uint32_t log_last_pos, struct log_buffer *log_buffer, struct tlogger_reader *reader) { bool user_buffer_left = false; uint32_t buf_written; struct reader_position position = {0}; struct log_buffer_flag *buffer_flag = &(log_buffer->flag); if (buffer_flag->write_loops == reader->r_loops) { set_reader_position(&position, log_buffer->buffer_start, buffer_flag->max_len, reader->r_off, log_last_pos); buf_written = parse_log_item(buf, count, &position, &reader->r_off, &user_buffer_left); return proc_read_ret(buf_written, reader); } if (buffer_flag->write_loops > (reader->r_loops +1) || ((buffer_flag->write_loops == (reader->r_loops + 1)) && (reader->r_off < log_last_pos))) { reader->r_off = log_last_pos; reader->r_loops = buffer_flag->write_loops - 1; } set_reader_position(&position, log_buffer->buffer_start, buffer_flag->max_len, reader->r_off, buffer_flag->max_len); buf_written = parse_log_item(buf, count, &position, &reader->r_off, &user_buffer_left); if (count > buf_written && user_buffer_left) { set_reader_position(&position, log_buffer->buffer_start, buffer_flag->max_len, 0, log_last_pos); buf_written += parse_log_item(buf + buf_written, count - buf_written, &position, &reader->r_off, &user_buffer_left); reader->r_loops = buffer_flag->write_loops; } return proc_read_ret(buf_written, reader); } static ssize_t process_tlogger_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { struct tlogger_reader *reader = NULL; struct log_buffer *log_buffer = NULL; ssize_t ret; uint32_t last_pos; uint32_t is_read; struct log_buffer_flag buffer_flag; (void)pos; ret = check_read_params(file, buf, count); if (ret != 0) return ret; reader = file->private_data; if (!reader) return -EINVAL; ret = get_buffer_info(reader, &buffer_flag, &log_buffer); if (ret != 0) return ret; ret = get_last_read_pos(&buffer_flag, reader, &last_pos, &is_read); if (is_read == 0) return ret; return trigger_parse_log(buf, count, last_pos, log_buffer, reader); } void tz_log_write(void) { struct log_buffer *log_buffer = NULL; if (!g_log) return; log_buffer = (struct log_buffer*)g_log->buffer_info; if (!log_buffer) return; if (g_last_read_offset != log_buffer->flag.last_pos) { tlogd("wake up write tz log\n"); wake_up_interruptible(&g_log->wait_queue_head); } return; } static struct tlogger_log *get_tlogger_log_by_minor(int minor) { struct tlogger_log *log = NULL; list_for_each_entry(log, &m_log_list, logs) { if (log->misc_device.minor == minor) return log; } return NULL; } static int process_tlogger_open(struct inode *inode, struct file *file) { struct tlogger_log *log = NULL; int ret; struct tlogger_reader *reader = NULL; tlogd("open logger open ++\n"); /* not support seek */ ret = nonseekable_open(inode, file); if (ret != 0) return ret; tlogd("Before get log from minor\n"); log = get_tlogger_log_by_minor(MINOR(inode->i_rdev)); if (!log) return -ENODEV; reader = kmalloc(sizeof(*reader), GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)reader)) return -ENOMEM; reader->log = log; reader->r_all = true; reader->r_off = 0; reader->r_loops = 0; reader->r_sn = 0; reader->r_failtimes = 0; reader->r_is_tlogf = 0; reader ->r_from_cur = 0; INIT_LIST_HEAD(&reader->list); mutex_lock(&log->mutex_info); list_add_tail(&reader->list, &log->readers); g_tlogcat_count++; mutex_unlock(&log->mutex_info); file->private_data = reader; tlogd("tlogcat count %u\n", g_tlogcat_count); #ifdef CONFIG_TEE_REBOOT get_tlogcat_pid(); #endif return 0; } static int process_tlogger_release(struct inode *ignored, struct file *file) { struct tlogger_reader *reader = NULL; struct tlogger_log *log = NULL; (void)ignored; tlogd("logger_release ++\n"); if (!file) return -1; reader = file->private_data; if (!reader) { tloge("reader is null\n"); return -1; } log = reader->log; if (!log) { tloge("log is null\n"); return -1; } mutex_lock(&log->mutex_info); list_del(&reader->list); if (g_tlogcat_count >= 1) g_tlogcat_count--; mutex_unlock(&log->mutex_info); tlogd("logger_release r_is_tlogf-%u\n", reader->r_is_tlogf); if (reader->r_is_tlogf != 0) g_tlogcat_f = 0; kfree(reader); tlogd("tlogcat count %u\n", g_tlogcat_count); return 0; } static unsigned int process_tlogger_poll(struct file *file, poll_table *wait) { struct tlogger_reader *reader = NULL; struct tlogger_log *log = NULL; struct log_buffer *buffer = NULL; uint32_t ret = POLLOUT | POLLWRNORM; tlogd("logger_poll ++\n"); if (!file) { tloge("file is null\n"); return ret; } reader = file->private_data; if (!reader) { tloge("the private data is null\n"); return ret; } log = reader->log; if (!log) { tloge("log is null\n"); return ret; } buffer = (struct log_buffer*)log->buffer_info; if (!buffer) { tloge("buffer is null\n"); return ret; } poll_wait(file, &log->wait_queue_head, wait); if (buffer->flag.last_pos != reader->r_off) ret |= POLLIN | POLLRDNORM; return ret; } #define SET_READ_POS 1U static void set_reader_cur_pos(const struct file *file) { struct tlogger_reader *reader = NULL; struct tlogger_log *log = NULL; struct log_buffer *buffer = NULL; reader = file->private_data; if (!reader) return; log = reader->log; if (!log) return; buffer = (struct log_buffer*)log->buffer_info; if (!buffer) return; reader->r_from_cur = SET_READ_POS; reader->r_off = buffer->flag.last_pos; reader->r_loops = buffer->flag.write_loops; } static void set_tlogcat_f_stat(const struct file *file) { struct tlogger_reader *reader = NULL; if (!file) return; reader = file->private_data; if (!reader) return; reader->r_is_tlogf = 1; g_tlogcat_f = 1; tlogi("set tlogcat_f-%d\n", g_tlogcat_f); return; } static int get_tlogcat_f_stat(void) { tlogi("get tlogcat_f-%d\n", g_tlogcat_f); return g_tlogcat_f; } static int check_user_arg(unsigned long arg, size_t arg_len) { #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 18) || \ LINUX_VERSION_CODE == KERNEL_VERSION(4, 19, 71)) return (int)access_ok(VERIFY_READ, (void __user *)(uintptr_t)arg, arg_len); #else return (int)access_ok((void __user *)(uintptr_t)arg, arg_len); #endif } static int get_teeos_version(uint32_t cmd, unsigned long arg) { int ret; if ((_IOC_DIR(cmd) & _IOC_READ) == 0) { tloge("check get version cmd failed\n"); return -1; } ret = check_user_arg(arg, sizeof(g_log_buffer->flag.version_info)); if (ret == 0) { tloge("check version info arg failed\n"); return -1; } if (copy_to_user((void __user *)(uintptr_t)arg, (void *)g_log_buffer->flag.version_info, sizeof(g_log_buffer->flag.version_info)) != 0) { tloge("version info copy failed\n"); return -1; } return 0; } #ifdef CONFIG_LOG_POOL #define LOG_POOL_SIZE 0x80000UL #define LOG_POOL_ITEM_MAX_LEN 384 #define HALF_DIV_NUM 2 static DEFINE_MUTEX(g_log_pool_lock); static uint64_t g_log_pool_va = 0; static uint64_t g_log_pool_size = 0; static uint32_t g_logserialno = 0; static int get_log_pool(void *argp) { uint64_t sz = 0; if (argp == NULL) { tloge("invalid params\n"); return -1; } sz = get_log_mem_size() / HALF_DIV_NUM; if (sz != LOG_POOL_SIZE) { tloge("log pool size error\n"); return -1; } if (copy_to_user(argp, &sz, sizeof(uint64_t)) != 0) { tloge("copy to user failed\n"); return -1; } return 0; } static int log_pool_check(void) { if (g_log_pool_size != LOG_POOL_SIZE) { tloge("log pool size error\n"); g_log_pool_size = 0; return -1; } if (g_log_pool_va == 0 || (UINT64_MAX - g_log_pool_va) < g_log_pool_size) { tloge("log pool addr error\n"); g_log_pool_va = 0; return -1; } return 0; } static int log_pool_item_check(struct log_item *item, struct teelogger_log_pool pool_item) { if (item->magic != LOG_ITEM_MAGIC || item->buffer_len == 0 || item->real_len == 0|| item->buffer_len % LOG_ITEM_LEN_ALIGN != 0 || item->real_len > item->buffer_len || (item->buffer_len - item->real_len) >= (uint16_t)LOG_ITEM_LEN_ALIGN || (uint64_t)(item->buffer_len + sizeof(struct log_item)) > pool_item.size) return -1; return 0; } static int log_pool_append(void *argp) { struct teelogger_log_pool pool_item = {0}; struct log_buffer *pool_buffer = NULL; struct log_item *item = NULL; if (argp == NULL || log_pool_check() != 0) { tloge("invalid params, g_log_pool_va or g_log_pool_size\n"); return -1; } if (copy_from_user((void *)&pool_item, argp, sizeof(struct teelogger_log_pool)) != 0) { tloge("pool_item copy from user error\n"); return -1; } if ((uint64_t)LOG_POOL_ITEM_MAX_LEN < pool_item.size || pool_item.size < (uint64_t)sizeof(struct log_item) || pool_item.addr == 0 || UINT64_MAX - pool_item.addr < pool_item.size) { tloge("pool_item addr or size error\n"); return -1; } mutex_lock(&g_log_pool_lock); pool_buffer = (struct log_buffer *)(uintptr_t)g_log_pool_va; if (pool_buffer == NULL || (uint64_t)(pool_buffer->flag).last_pos > g_log_pool_size || ((uint64_t)sizeof(struct log_buffer) + (uint64_t)(pool_buffer->flag).last_pos) > g_log_pool_size) { tloge("pool_buffer error\n"); mutex_unlock(&g_log_pool_lock); return -1; } /* restart from head */ if (((uint64_t)sizeof(struct log_buffer) + (uint64_t)(pool_buffer->flag).last_pos + pool_item.size) > g_log_pool_size) { pool_buffer->flag.write_loops++; pool_buffer->flag.last_pos = 0; } item = (struct log_item *)(uintptr_t)((uint64_t)(uintptr_t)pool_buffer->buffer_start + (uint64_t)pool_buffer->flag.last_pos); if (copy_from_user((void *)item, (void *)pool_item.addr, pool_item.size) != 0) { tloge("item copy_from_user error\n"); mutex_unlock(&g_log_pool_lock); return -1; } if (log_pool_item_check(item, pool_item) != 0) { tloge("item check error\n"); mutex_unlock(&g_log_pool_lock); return -1; } item->serial_no = ++g_logserialno; /* reset g_logserialno */ if (item->serial_no == 0) item->serial_no = ++g_logserialno; item->new_line = (unsigned char)'\n'; pool_buffer->flag.reserved[1] = g_logserialno; pool_buffer->flag.last_pos += (uint32_t)pool_item.size; mutex_unlock(&g_log_pool_lock); return 0; } static int init_log_pool(void) { struct log_buffer *pool_buffer = NULL; uint64_t paddr = get_log_mem_paddr(0); g_log_pool_size = get_log_mem_size(); if ((UINT64_MAX - paddr) < g_log_pool_size) { tloge("log pool paddr error\n"); return -1; } g_log_pool_size /= HALF_DIV_NUM; g_log_pool_va = (uint64_t)(uintptr_t)ioremap_cache(paddr + g_log_pool_size, g_log_pool_size); if (log_pool_check() != 0) { tloge("log pool addr or size error\n"); return -1; } pool_buffer = (struct log_buffer *)(uintptr_t)g_log_pool_va; /* * the struct log_buffer magic field use for log retention feature, * if hit the magic, will retain the old log before reset in log pool, * or will memset log pool. */ if (pool_buffer->flag.reserved[0] != LOG_ITEM_MAGIC) { (void)memset_s((void *)(uintptr_t)g_log_pool_va, g_log_pool_size, 0, g_log_pool_size); pool_buffer->flag.reserved[0] = LOG_ITEM_MAGIC; pool_buffer->flag.max_len = g_log_pool_size - sizeof(struct log_buffer); } else { g_logserialno = pool_buffer->flag.reserved[1]; } return 0; } static void free_log_pool(void) { if (g_log_pool_va != 0) iounmap((void __iomem *)(uintptr_t)g_log_pool_va); g_log_pool_va = 0; g_log_pool_size = 0; g_logserialno = 0; } #endif static long process_tlogger_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct tlogger_log *log = NULL; long ret = -EINVAL; if (!file) return -1; log = get_reader_log(file); if (!log) { tloge("log is null\n"); return -1; } tlogd("logger_ioctl start ++\n"); mutex_lock(&log->mutex_info); switch (cmd) { case TEELOGGER_GET_VERSION: if (get_teeos_version(cmd, arg) == 0) ret = 0; break; case TEELOGGER_SET_READERPOS_CUR: set_reader_cur_pos(file); ret = 0; break; case TEELOGGER_SET_TLOGCAT_STAT: set_tlogcat_f_stat(file); ret = 0; break; case TEELOGGER_GET_TLOGCAT_STAT: ret = get_tlogcat_f_stat(); break; #ifdef CONFIG_LOG_POOL case TEELOGGER_GET_LOG_POOL: ret = get_log_pool(arg); break; case TEELOGGER_LOG_POOL_APPEND: ret = log_pool_append(arg); break; #endif default: tloge("ioctl error default\n"); break; } mutex_unlock(&log->mutex_info); return ret; } #ifdef CONFIG_COMPAT static long process_tlogger_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { tlogd("logger_compat_ioctl ++\n"); arg = (unsigned long)(uintptr_t)compat_ptr(arg); return process_tlogger_ioctl(file, cmd, arg); } #endif static const struct file_operations g_logger_fops = { .owner = THIS_MODULE, .read = process_tlogger_read, .poll = process_tlogger_poll, .unlocked_ioctl = process_tlogger_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = process_tlogger_compat_ioctl, #endif .open = process_tlogger_open, .release = process_tlogger_release, }; static int __init register_device(const char *log_name, uintptr_t addr, int size) { int ret; struct tlogger_log *log = NULL; unsigned char *buffer = (unsigned char *)addr; (void)size; log = kzalloc(sizeof(*log), GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)log)) { tloge("kzalloc is failed\n"); return -ENOMEM; } log->buffer_info = buffer; log->misc_device.minor = MISC_DYNAMIC_MINOR; log->misc_device.name = kstrdup(log_name, GFP_KERNEL); if (!log->misc_device.name) { ret = -ENOMEM; tloge("kstrdup is failed\n"); goto out_free_log; } log->misc_device.fops = &g_logger_fops; log->misc_device.parent = NULL; init_waitqueue_head(&log->wait_queue_head); INIT_LIST_HEAD(&log->readers); mutex_init(&log->mutex_info); INIT_LIST_HEAD(&log->logs); list_add_tail(&log->logs, &m_log_list); /* register misc device for this log */ ret = misc_register(&log->misc_device); if (unlikely(ret)) { tloge("failed to register misc device:%s\n", log->misc_device.name); goto out_free_log; } g_log = log; return 0; out_free_log: if (log->misc_device.name) kfree(log->misc_device.name); kfree(log); return ret; } static struct log_item *msg_get_next(unsigned char *buffer_start, uint32_t read_pos, uint32_t scope_len, uint32_t max_len) { uint32_t i = 0; struct log_item *item = NULL; uint32_t item_max_size; uint32_t len; while (i <= scope_len && ((read_pos + i + sizeof(*item)) < max_len)) { len = (uint32_t)(scope_len - i); item_max_size = ((len > LOG_ITEM_MAX_LEN) ? LOG_ITEM_MAX_LEN : len); item = (struct log_item *)(buffer_start + read_pos + i); if (check_log_item_validite(item, item_max_size)) { if ((read_pos + i + sizeof(*item) + item->buffer_len) > max_len) { tloge("check item len error\n"); return NULL; } return item; } i += LOG_ITEM_LEN_ALIGN; item = NULL; } return NULL; } #ifdef CONFIG_TZDRIVER_MODULE /* there is no way to chown in kernel-5.10 for ko */ static int tlogger_chown(const char *file_path, uint32_t file_path_len) { (void)file_path; (void)file_path_len; return 0; } #else static int tlogger_chown(const char *file_path, uint32_t file_path_len) { (void)file_path_len; uid_t user = ROOT_UID; gid_t group = ROOT_GID; int ret; #if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) mm_segment_t old_fs; #endif get_log_chown(&user, &group); /* not need modify chown attr */ if (group == ROOT_GID && user == ROOT_UID) return 0; #if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) old_fs = get_fs(); set_fs(KERNEL_DS); #endif #if (KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE) ret = (int)ksys_chown((const char __user *)file_path, user, group); #else ret = (int)sys_chown((const char __user *)file_path, user, group); #endif if (ret != 0) { tloge("sys chown for last teemsg file error\n"); #if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) set_fs(old_fs); #endif return -1; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)) set_fs(old_fs); #endif return 0; } #endif static int write_version_to_msg(struct file *filep, loff_t *pos) { ssize_t write_len; /* first write tee versino info */ write_len = kernel_write(filep, g_log_buffer->flag.version_info, strlen(g_log_buffer->flag.version_info), pos); if (write_len < 0) { tloge("Failed to write to last teemsg version\n"); return -1; } tlogd("Succeed to Write to last teemsg version, len=%zd\n", write_len); return 0; } static int write_part_log_to_msg(struct file *filep, unsigned char *buffer, uint32_t buffer_max_len, loff_t *pos, uint32_t read_off, uint32_t read_off_end) { struct log_item *next_item = NULL; uint32_t item_len; uint32_t total_len = 0; ssize_t write_len; next_item = msg_get_next(buffer, read_off, LOG_ITEM_MAX_LEN, buffer_max_len); while (next_item && read_off <= read_off_end) { item_len = next_item->buffer_len + sizeof(*next_item); write_len = kernel_write(filep, next_item->log_buffer, next_item->real_len, pos); if (write_len < 0) { tloge("Failed to write last teemsg %zd\n", write_len); return -1; } tlogd("Succeed to Write last teemsg, len=%zd\n", write_len); total_len += item_len; read_off = (unsigned char *)next_item - buffer + item_len; if (total_len >= buffer_max_len) break; next_item = msg_get_next(buffer, read_off, LOG_ITEM_MAX_LEN, buffer_max_len); } return 0; } static int write_log_to_msg(struct file *filep, unsigned char *buffer, uint32_t buffer_max_len, loff_t *pos, uint32_t read_off, uint32_t read_off_end) { if (read_off < read_off_end) { return write_part_log_to_msg(filep, buffer, buffer_max_len, pos, read_off, read_off_end); } else { if (write_part_log_to_msg(filep, buffer, buffer_max_len, pos, read_off, buffer_max_len) != 0) return -1; return write_part_log_to_msg(filep, buffer, buffer_max_len, pos, 0, read_off_end); } } #ifdef CONFIG_TEE_LOG_DUMP_PATH static void update_dumpmsg_offset(uint32_t *read_start, uint32_t *read_end, uint32_t read_off, uint32_t read_off_end, uint32_t *dump_start_flag, uint32_t *dump_end_flag) { struct log_item *next_item = NULL; unsigned char *buffer = g_log_buffer->buffer_start; uint32_t buffer_max_len = g_log_mem_len - sizeof(*g_log_buffer); ssize_t item_len; ssize_t total_len = 0; next_item = msg_get_next(buffer, read_off, LOG_ITEM_MAX_LEN, buffer_max_len); while (next_item && read_off <= read_off_end) { item_len = next_item->buffer_len + sizeof(*next_item); if (strstr(next_item->log_buffer, DUMP_START_MAGIC)) { *read_start = read_off; *dump_start_flag = 1; } else if (strstr(next_item->log_buffer, DUMP_END_MAGIC)) { *read_end = read_off; *dump_end_flag = 1; } read_off = (unsigned char *)next_item - buffer + item_len; total_len += item_len; if (total_len >= buffer_max_len) break; next_item = msg_get_next(buffer, read_off, LOG_ITEM_MAX_LEN, buffer_max_len); } } #endif #ifdef CONFIG_TEE_LOG_DUMP_PATH static int get_dumpmsg_offset(uint32_t *read_start, uint32_t *read_end) { uint32_t read_off = *read_start; uint32_t read_off_end = *read_end; uint32_t buffer_max_len = g_log_mem_len - sizeof(*g_log_buffer); uint32_t dump_start_flag = 0; uint32_t dump_end_flag = 0; if (read_off < read_off_end) { update_dumpmsg_offset(read_start, read_end, read_off, read_off_end, &dump_start_flag, &dump_end_flag); } else { update_dumpmsg_offset(read_start, read_end, read_off, buffer_max_len, &dump_start_flag, &dump_end_flag); update_dumpmsg_offset(read_start, read_end, 0, read_off_end, &dump_start_flag, &dump_end_flag); } if (dump_start_flag == 0 || dump_end_flag == 0) { tloge("can't find dump start or end\n"); return -1; } else { return 0; } } #endif static int get_msg_buffer(unsigned char **buffer, uint32_t *buffer_max_len, uint32_t *read_start, uint32_t *read_end, const char *file_path, uint32_t file_path_len) { errno_t rc; int ret; unsigned char *addr = NULL; (void)file_path_len; if (!g_log_buffer) return -1; *buffer_max_len = g_log_mem_len - sizeof(*g_log_buffer); if (*buffer_max_len > LOG_BUFFER_MAX_LEN) return 0; *read_start = 0; *read_end = *buffer_max_len; #ifdef CONFIG_TEE_LOG_DUMP_PATH if (strcmp(file_path, CONFIG_TEE_LOG_DUMP_PATH) == 0) { *read_start = g_last_read_offset; *read_end = ((struct log_buffer*)g_log->buffer_info)->flag.last_pos; if (get_dumpmsg_offset(read_start, read_end) != 0) { tloge("get dump offset failed\n"); return -1; } } #else (void)file_path; #endif addr = kmalloc(*buffer_max_len, GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)addr)) { ret = -ENOMEM; goto free_res; } rc = memcpy_s(addr, *buffer_max_len, g_log_buffer->buffer_start, *buffer_max_len); if (rc) { tloge("memcpy failed %d\n", rc); ret = -EAGAIN; goto free_res; } *buffer = addr; return 0; free_res: if (addr) kfree(addr); return ret; } static int open_msg_file(struct file **file, const char *file_path, uint32_t file_path_len) { struct file *filep = NULL; (void)file_path_len; filep = filp_open(file_path, O_CREAT | O_RDWR | O_TRUNC, OPEN_FILE_MODE); if (!filep || IS_ERR(filep)) { tloge("open last teemsg file err %ld\n", PTR_ERR(filep)); return -1; } *file = filep; return 0; } int tlogger_store_msg(const char *file_path, uint32_t file_path_len) { struct file *filep = NULL; loff_t pos = 0; int ret; uint32_t buffer_max_len = 0; unsigned char *buffer = NULL; uint32_t read_start = 0; uint32_t read_end = 0; if (!file_path || file_path_len > TEE_LOG_FILE_NAME_MAX) { tloge("file path is invalid\n"); return -1; } if (!g_tlogcat_count) { tlogd("tlogcat count %u\n", g_tlogcat_count); return 0; } /* copy logs from log memory, then parse the logs */ ret = get_msg_buffer(&buffer, &buffer_max_len, &read_start, &read_end, file_path, file_path_len); if (ret != 0) return ret; /* exception handling, store trustedcore exception info to file */ ret = open_msg_file(&filep, file_path, file_path_len); if (ret != 0) goto free_res; ret = tlogger_chown(file_path, file_path_len); if (ret != 0) goto free_res; ret = write_version_to_msg(filep, &pos); if (ret != 0) goto free_res; ret = write_log_to_msg(filep, buffer, buffer_max_len, &pos, read_start, read_end); free_res: if (buffer) { kfree(buffer); buffer = NULL; } if (filep != NULL) { vfs_fsync(filep, 0); filp_close(filep, 0); } /* trigger write teeos log */ tz_log_write(); return ret; } #ifdef DEF_ENG #define KERNEL_IMG_IS_ENG 1 #endif int register_mem_to_teeos(uint64_t mem_addr, uint32_t mem_len, bool is_cache_mem) { struct tc_ns_smc_cmd smc_cmd = { {0}, 0 }; struct mb_cmd_pack *mb_pack = NULL; int ret; mb_pack = mailbox_alloc_cmd_pack(); if (!mb_pack) { tloge("mailbox alloc failed\n"); return -ENOMEM; } smc_cmd.cmd_type = CMD_TYPE_GLOBAL; smc_cmd.cmd_id = GLOBAL_CMD_ID_REGISTER_LOG_MEM; mb_pack->operation.paramtypes = teec_param_types( TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_NONE); mb_pack->operation.params[0].value.a = mem_addr; mb_pack->operation.params[0].value.b = mem_addr >> ADDR_TRANS_NUM; mb_pack->operation.params[1].value.a = mem_len; #ifdef DEF_ENG mb_pack->operation.params[1].value.b = KERNEL_IMG_IS_ENG; #endif /* * is_cache_mem: true, teeos map this memory for cache * style; or else map to no cache style */ mb_pack->operation.params[2].value.a = is_cache_mem; smc_cmd.operation_phys = mailbox_virt_to_phys((uintptr_t)&mb_pack->operation); smc_cmd.operation_h_phys = (uint64_t)mailbox_virt_to_phys((uintptr_t)&mb_pack->operation) >> ADDR_TRANS_NUM; if (is_tee_rebooting()) ret = send_smc_cmd_rebooting(TSP_REQUEST, 0, 0, &smc_cmd); else ret = tc_ns_smc(&smc_cmd); mailbox_free(mb_pack); if (ret != 0) tloge("Send log mem info failed\n"); return ret; } static int register_mem_cfg(uint64_t *addr, uint32_t *len) { int ret; ret = register_log_mem(addr, len); if (ret != 0) tloge("register log mem failed %x\n", ret); ret = register_log_exception(); if (ret != 0) tloge("teeos register exception to log module failed\n"); return ret; } static int check_log_mem(uint64_t mem_addr, uint32_t mem_len) { if (mem_len < TEMP_LOG_MEM_SIZE) { tloge("log mem init error, too small len:0x%x\n", mem_len); return -1; } if (!mem_addr) { tloge("mem init failed!!! addr is 0\n"); return -1; } return 0; } int register_tloger_mem(void) { int ret; uint64_t mem_addr = 0; ret = register_mem_cfg(&mem_addr, &g_log_mem_len); if (ret != 0) return ret; ret = check_log_mem(mem_addr, g_log_mem_len); if (ret != 0) return ret; g_log_buffer = (struct log_buffer *)map_log_mem(mem_addr, g_log_mem_len); if (!g_log_buffer) return -ENOMEM; g_log_buffer->flag.max_len = g_log_mem_len - sizeof(*g_log_buffer); return ret; } static int register_tloger_device(void) { int ret; tlogi("tlogcat version 1.0.0\n"); ret = register_device(LOGGER_LOG_TEEOS, (uintptr_t)g_log_buffer, sizeof(*g_log_buffer) + g_log_buffer->flag.max_len); if (ret != 0) { unmap_log_mem((int *)g_log_buffer); g_log_buffer = NULL; g_log_mem_len = 0; } return ret; } static int register_tloger(void) { int ret; ret = register_tloger_mem(); if (ret != 0) return ret; #ifdef CONFIG_LOG_POOL ret = init_log_pool(); if (ret != 0) tloge("init_log_pool init failed\n"); #endif ret = register_tloger_device(); return ret; } static void unregister_mem_cfg(void) { if (g_log_buffer) unmap_log_mem((int *)g_log_buffer); unregister_log_exception(); } static void unregister_tlogger(void) { struct tlogger_log *current_log = NULL; struct tlogger_log *next_log = NULL; list_for_each_entry_safe(current_log, next_log, &m_log_list, logs) { /* we have to delete all the entry inside m_log_list */ misc_deregister(¤t_log->misc_device); kfree(current_log->misc_device.name); list_del(¤t_log->logs); kfree(current_log); } #ifdef CONFIG_LOG_POOL free_log_pool(); #endif unregister_mem_cfg(); g_log_buffer = NULL; g_log_mem_len = 0; } #ifdef CONFIG_TZDRIVER_MODULE int init_tlogger_service(void) { return register_tloger(); } void free_tlogger_service(void) { unregister_tlogger(); } #else static int __init init_tlogger_service(void) { return register_tloger(); } static void __exit free_tlogger_service(void) { unregister_tlogger(); } #endif #ifdef CONFIG_TZDRIVER device_initcall(init_tlogger_service); module_exit(free_tlogger_service); MODULE_AUTHOR("iTrustee"); MODULE_DESCRIPTION("TrustCore Logger"); MODULE_VERSION("1.00"); #endif