| /* |
| * Copyright (c) 2017, Amlogic, Inc. |
| * |
| * 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 <linux/errno.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/types.h> |
| #include <linux/uaccess.h> |
| #include <linux/sysfs.h> |
| #include <linux/kthread.h> |
| #include <generated/uapi/linux/version.h> |
| #include <linux/workqueue.h> |
| |
| #include "optee_smc.h" |
| #include "optee_private.h" |
| #include "../tee_private.h" |
| |
| //#define OPTEE_LOG_BUFFER_DEBUG 1 |
| |
| #define OPTEE_LOG_BUFFER_MAGIC 0xAA00AA00 |
| #define OPTEE_LOG_BUFFER_OFFSET 0x00000080 |
| #define OPTEE_LOG_READ_MAX PAGE_SIZE |
| #define OPTEE_LOG_LINE_MAX 1024 |
| #define OPTEE_LOG_TIMER_INTERVAL 1 |
| |
| #undef pr_fmt |
| #define pr_fmt(fmt) "[TEE] " fmt |
| |
| struct optee_log_ctl_s { |
| unsigned int magic; |
| unsigned int inited; |
| unsigned int total_size; |
| unsigned int fill_size; |
| unsigned int mode; |
| unsigned int reader; |
| unsigned int writer; |
| |
| unsigned char *buffer; |
| }; |
| |
| static struct optee_log_ctl_s *optee_log_ctl; |
| static unsigned char *optee_log_buff; |
| static uint32_t optee_log_mode = 1; |
| static uint8_t line_buff[OPTEE_LOG_LINE_MAX]; |
| static uint32_t looped = 0; |
| static void *g_shm_va; |
| |
| struct delayed_work log_work; |
| static struct workqueue_struct *log_workqueue = NULL; |
| |
| static bool init_shm(phys_addr_t shm_pa, uint32_t shm_size) |
| { |
| struct arm_smccc_res smccc; |
| uint32_t start = 1; |
| |
| if (pfn_valid(__phys_to_pfn(shm_pa))) |
| g_shm_va = (void __iomem *)__phys_to_virt(shm_pa); |
| else |
| g_shm_va = ioremap_cache(shm_pa, shm_size); |
| |
| if (!g_shm_va) { |
| pr_err("map logger share-mem failed\n"); |
| return false; |
| } |
| |
| arm_smccc_smc(OPTEE_SMC_SET_LOGGER, start, shm_pa, shm_size, 0, 0, 0, 0, |
| &smccc); |
| |
| return (0 == smccc.a0); |
| } |
| |
| static void uninit_shm(void) |
| { |
| struct arm_smccc_res smccc; |
| uint32_t start = 0; |
| |
| if (g_shm_va) |
| iounmap(g_shm_va); |
| |
| arm_smccc_smc(OPTEE_SMC_SET_LOGGER, start, 0, 0, 0, 0, 0, 0, &smccc); |
| } |
| |
| static ssize_t log_mode_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%d\n", optee_log_mode); |
| } |
| |
| static ssize_t log_mode_store(struct class *cla, |
| struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int res = 0; |
| int rc = 0; |
| struct optee_log_ctl_s *ctl = optee_log_ctl; |
| |
| rc = kstrtoint(buf, 0, &res); |
| pr_notice("log_mode: %d->%d\n", optee_log_mode, res); |
| optee_log_mode = res; |
| if (ctl && ctl->mode != optee_log_mode) |
| ctl->mode = optee_log_mode; |
| |
| return count; |
| } |
| |
| static ssize_t log_buff_get_read_buff(char **buf, int len) |
| { |
| int writer; |
| int reader; |
| int read_size = 0; |
| struct optee_log_ctl_s *ctl = optee_log_ctl; |
| |
| if ((!ctl) || (len <= 0)) |
| return 0; |
| |
| writer = ctl->writer; |
| reader = ctl->reader; |
| |
| if (reader == writer) |
| read_size = 0; |
| else if (reader < writer) |
| read_size = writer - reader; |
| else { |
| looped = 1; |
| read_size = ctl->total_size - reader; |
| } |
| |
| if (read_size > len) |
| read_size = len; |
| |
| *buf = optee_log_buff + reader; |
| ctl->reader += read_size; |
| if (ctl->reader == ctl->total_size) |
| ctl->reader = 0; |
| |
| return read_size; |
| } |
| |
| static size_t log_print_text(char *buf, size_t size) |
| { |
| const char *text = buf; |
| size_t text_size = size; |
| size_t len = 0; |
| char *line = line_buff; |
| |
| if (size == 0) |
| return 0; |
| |
| /* Line_size Out-of-bounds check */ |
| text_size = text_size < (OPTEE_LOG_LINE_MAX - 16) ? text_size : OPTEE_LOG_LINE_MAX - 16; |
| |
| do { |
| const char *next = memchr(text, '\n', text_size); |
| size_t line_size; |
| |
| if (next) { |
| line_size = next - text; |
| next++; |
| text_size -= next - text; |
| } else |
| line_size = text_size; |
| |
| memcpy(line, text, line_size); |
| len += line_size; |
| if (next) |
| len++; |
| else if (line[line_size] == '\0') |
| len++; |
| line[line_size] = '\n'; |
| line[line_size + 1] = '\0'; |
| pr_notice("%s", line); |
| text = next; |
| } while (text && (len < size)); |
| |
| return len; |
| } |
| |
| static ssize_t log_buff_dump(void) |
| { |
| ssize_t len; |
| char *ptr = NULL; |
| unsigned int writer = 0; |
| struct optee_log_ctl_s *ctl = optee_log_ctl; |
| |
| if (!ctl) |
| return 0; |
| |
| writer = ctl->writer; |
| |
| if (looped) { |
| ptr = optee_log_buff + writer; |
| len = ctl->total_size - writer; |
| |
| log_print_text(ptr, len); |
| } |
| |
| ptr = optee_log_buff; |
| len = writer; |
| |
| log_print_text(ptr, len); |
| |
| return 0; |
| } |
| |
| static void log_buff_reset(void) |
| { |
| struct optee_log_ctl_s *ctl = optee_log_ctl; |
| |
| if (!ctl) |
| return; |
| |
| ctl->writer = 0; |
| ctl->reader = 0; |
| |
| return; |
| } |
| |
| static void log_buff_info(void) |
| { |
| #ifdef OPTEE_LOG_BUFFER_DEBUG |
| struct optee_log_ctl_s *ctl = optee_log_ctl; |
| |
| if (!ctl) |
| return; |
| |
| pr_notice(" total_size: %d\n", ctl->total_size); |
| pr_notice(" fill_size: %d\n", ctl->fill_size); |
| pr_notice(" mode: %d\n", ctl->mode); |
| pr_notice(" reader: %d\n", ctl->reader); |
| pr_notice(" writer: %d\n", ctl->writer); |
| #endif |
| } |
| |
| static ssize_t log_buff_store(struct class *cla, struct class_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int res = 0; |
| int rc = 0; |
| |
| rc = kstrtoint(buf, 0, &res); |
| if (res == 0) |
| /* reset log buffer */ |
| log_buff_reset(); |
| else if (res == 1) |
| /* show log buffer info */ |
| log_buff_info(); |
| else |
| pr_notice("set 0 to reset tee log buffer\n"); |
| |
| return count; |
| } |
| |
| static ssize_t log_buff_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| log_buff_dump(); |
| |
| return 0; |
| } |
| |
| static struct class_attribute log_class_attrs[] = { |
| __ATTR(log_mode, S_IRUGO | S_IWUSR, log_mode_show, log_mode_store), |
| __ATTR(log_buff, S_IRUGO | S_IWUSR, log_buff_show, log_buff_store), |
| }; |
| |
| static void log_buff_output(void) |
| { |
| size_t len; |
| char *read_buff = NULL; |
| |
| if (optee_log_mode == 0) |
| return; |
| |
| len = log_buff_get_read_buff(&read_buff, OPTEE_LOG_READ_MAX); |
| if (len > 0) |
| log_print_text(read_buff, len); |
| } |
| |
| static void do_log_timer(struct work_struct *work) |
| { |
| log_buff_output(); |
| if (queue_delayed_work(log_workqueue, &log_work, OPTEE_LOG_TIMER_INTERVAL * HZ) == 0) { |
| pr_err("%s:%d Failed to join the workqueue\n", __func__, __LINE__); |
| } |
| } |
| |
| int optee_log_init(struct tee_device *tee_dev, phys_addr_t shm_pa, |
| uint32_t shm_size) |
| { |
| int rc = 0; |
| int i = 0; |
| int n = 0; |
| |
| if (!init_shm(shm_pa, shm_size)) |
| return -1; |
| |
| optee_log_buff = (unsigned char *)(g_shm_va + OPTEE_LOG_BUFFER_OFFSET); |
| optee_log_ctl = (struct optee_log_ctl_s *)g_shm_va; |
| if ((optee_log_ctl->magic != OPTEE_LOG_BUFFER_MAGIC) |
| || (optee_log_ctl->inited != 1)) { |
| uninit_shm(); |
| optee_log_ctl = NULL; |
| rc = -1; |
| pr_err("tee log buffer init failed\n"); |
| goto err; |
| } |
| optee_log_ctl->mode = optee_log_mode; |
| |
| /* create attr files */ |
| n = sizeof(log_class_attrs) / sizeof(struct class_attribute); |
| for (i = 0; i < n; i++) { |
| rc = class_create_file(tee_dev->dev.class, &log_class_attrs[i]); |
| if (rc) |
| goto err; |
| } |
| |
| /* init workqueue */ |
| log_workqueue = create_singlethread_workqueue("tee-log-wq"); |
| INIT_DELAYED_WORK(&log_work,do_log_timer); |
| if (queue_delayed_work(log_workqueue, &log_work, OPTEE_LOG_TIMER_INTERVAL * HZ) == 0) { |
| pr_err("%s:%d Failed to join the workqueue.\n", __func__, __LINE__); |
| } |
| |
| err: |
| return rc; |
| } |
| |
| void optee_log_exit(struct tee_device *tee_dev) |
| { |
| int i = 0; |
| int n = 0; |
| |
| if (log_workqueue) { |
| cancel_delayed_work_sync(&log_work); |
| destroy_workqueue(log_workqueue); |
| } |
| |
| n = sizeof(log_class_attrs) / sizeof(struct class_attribute); |
| for (i = 0; i < n; i++) |
| class_remove_file(tee_dev->dev.class, &log_class_attrs[i]); |
| |
| uninit_shm(); |
| } |