| /* |
| * Samsung Exynos SoC series NPU driver |
| * |
| * Copyright (c) 2017 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com/ |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| */ |
| |
| #include <linux/atomic.h> |
| #include <linux/debugfs.h> |
| #include <linux/delay.h> |
| #include <linux/ctype.h> |
| #include <linux/uaccess.h> |
| #include <linux/slab.h> |
| #include <linux/sysfs.h> |
| #include <linux/sched/clock.h> |
| |
| #include "npu-device.h" |
| #include "npu-debug.h" |
| #include "npu-log.h" |
| #include "npu-interface.h" |
| |
| /* Non-printable character to mark the last dump postion is overwritten or not */ |
| const char NPU_LOG_DUMP_MARK = (char)0x01; |
| |
| /* Log level to filter message written to kernel log */ |
| struct npu_log npu_log = { |
| .pr_level = NPU_LOG_INFO, /* To kmsg */ |
| .st_level = NPU_LOG_DBG, /* To memory buffer */ |
| .st_buf = NULL, |
| .st_size = 0, |
| .wr_pos = 0, |
| .line_cnt = 0, |
| .last_dump_line_cnt = 0, |
| .last_dump_mark_pos = 0, |
| .fs_ref = ATOMIC_INIT(0), |
| }; |
| |
| /* |
| * In fw_report, some var(line_cnt) will be used with another purpose. |
| * this var will be used finish line when write pointer circled to start line. |
| * So, buffer in file object does not know about ... |
| */ |
| struct npu_log fw_report = { |
| .st_buf = NULL, |
| .st_size = 0, |
| .wr_pos = 0, |
| .line_cnt = 0, |
| .last_dump_line_cnt = 0, |
| }; |
| /* Spinlock for memory logger */ |
| static DEFINE_SPINLOCK(npu_log_lock); |
| static DEFINE_SPINLOCK(fw_report_lock); |
| |
| /* Temporal buffer */ |
| #define NPU_LOG_MSG_MAX 1024 |
| |
| const char *LOG_LEVEL_NAME[NPU_LOG_INVALID] = { |
| "Trace", "Debug", "Info", "Warning", "Error", "None" |
| }; |
| const char LOG_LEVEL_MARK[NPU_LOG_INVALID] = { |
| 'T', 'D', 'I', 'W', 'E', '_' |
| }; |
| |
| /* |
| * ISR-aware spin lock for log implementation |
| * |
| * returns 0 if locking is successful. |
| * - Case 1 : Current context is not interrupt. and spin_lock() was successful. |
| * - Case 2 : Current context is interrupt. and spin_trylock() was successful. |
| * returns -EINTR if |
| * - Current context is interrupt, and spin_trylock() returns zero |
| * (i.e. spin_lock is currently held) |
| * Caller should not access shared resource in this case |
| */ |
| static inline int spin_lock_safe_isr(spinlock_t *lock) |
| { |
| int ret; |
| |
| if (unlikely(in_interrupt())) { |
| ret = spin_trylock(lock); |
| if (ret == 0) |
| return -EINTR; |
| } else { |
| spin_lock(lock); |
| } |
| return 0; |
| } |
| |
| /* |
| * 0 : log success |
| * != 0 : Failure |
| */ |
| int npu_store_log(npu_log_level_e loglevel, const char *fmt, ...) |
| { |
| int ret; |
| size_t pr_size; |
| size_t wr_len = 0; |
| size_t remain; |
| va_list arg_ptr; |
| char *buf; |
| |
| /* |
| * Do not use store log on interrupt context, because it requires |
| * use of spin_lock_irqsave() which lowers system's responsiveness |
| * (The message will be forwarded to pr_XXXX macro instead) |
| */ |
| ret = spin_lock_safe_isr(&npu_log_lock); |
| if (ret) |
| goto imm_exit; |
| |
| remain = npu_log.st_size - npu_log.wr_pos; |
| buf = npu_log.st_buf + npu_log.wr_pos; |
| |
| if (unlikely(!npu_log.st_buf)) { |
| ret = -ENOENT; |
| goto unlock_exit; |
| } |
| |
| if ((npu_log.line_cnt & NPU_STORE_LOG_SYNC_MARK_INTERVAL_MASK) == 0) |
| pr_info(NPU_STORE_LOG_SYNC_MARK_MSG, npu_log.line_cnt); |
| |
| /* Execute from start */ |
| goto start; |
| |
| retry: |
| /* Executed when hit the buffer end during the writing messages. */ |
| if (unlikely(buf == npu_log.st_buf)) { |
| ret = -ENOMEM; |
| goto err_exit; /* Buffer is too short */ |
| } |
| |
| /* Fill the remain buffer as separator line and move to start pos to retry */ |
| memset(buf, '#', npu_log.st_size - npu_log.wr_pos); |
| npu_log.st_buf[npu_log.st_size - 1] = '\n'; |
| buf = npu_log.st_buf; |
| remain = npu_log.st_size; |
| npu_log.wr_pos = 0; |
| wr_len = 0; |
| |
| start: |
| if ((npu_log.line_cnt & NPU_STORE_LOG_SYNC_MARK_INTERVAL_MASK) == 0) { |
| pr_size = scnprintf(buf + wr_len, remain, NPU_STORE_LOG_SYNC_MARK_MSG, npu_log.line_cnt); |
| if (unlikely(pr_size < 0)) { |
| ret = -EFAULT; |
| goto err_exit; |
| } |
| remain -= pr_size; |
| wr_len += pr_size; |
| if ((remain <= 1) || (pr_size == 0)) { /* Underflow on 'remain -= pr_size' */ |
| goto retry; |
| } |
| } |
| |
| pr_size = scnprintf(buf + wr_len, remain, "%016llu;%c;" |
| , sched_clock(), LOG_LEVEL_MARK[loglevel]); |
| if (unlikely(pr_size < 0)) { |
| ret = -EFAULT; |
| goto err_exit; |
| } |
| remain -= pr_size; |
| wr_len += pr_size; |
| if ((remain <= 1) || (pr_size == 0)) { /* Underflow on 'remain -= pr_size' */ |
| goto retry; |
| } |
| |
| va_start(arg_ptr, fmt); |
| pr_size = vscnprintf(buf + wr_len, remain, fmt, arg_ptr); |
| va_end(arg_ptr); |
| if (unlikely(pr_size < 0)) { |
| ret = -EFAULT; |
| goto err_exit; |
| } |
| remain -= pr_size; |
| wr_len += pr_size; |
| if ((remain <= 1) || (pr_size == 0)) { /* Underflow on 'remain -= pr_size' */ |
| goto retry; |
| } |
| |
| /* Update write position */ |
| npu_log.wr_pos = (npu_log.wr_pos + wr_len) % npu_log.st_size; |
| npu_log.line_cnt++; |
| |
| ret = 0; |
| goto unlock_exit; |
| |
| err_exit: |
| pr_err("Log store error : remain: %zu wr_len: %zu pr_size : %zu ret :%d\n", |
| remain, wr_len, pr_size, ret); |
| |
| unlock_exit: |
| spin_unlock(&npu_log_lock); |
| |
| imm_exit: |
| return ret; |
| } |
| |
| void npu_store_log_init(char *buf_addr, const size_t size) |
| { |
| BUG_ON(!buf_addr); |
| BUG_ON(size < PAGE_SIZE); |
| |
| spin_lock(&npu_log_lock); |
| |
| npu_log.st_buf = buf_addr; |
| buf_addr[0] = '\0'; |
| buf_addr[size - 1] = '\0'; |
| npu_log.st_size = size; |
| npu_log.wr_pos = 0; |
| spin_unlock(&npu_log_lock); |
| |
| npu_info("Store log memory initialized : %pK[Len = %zu]\n", buf_addr, size); |
| } |
| |
| void npu_store_log_deinit(void) |
| { |
| /* Wake-up readers and preserve some time to flush */ |
| wake_up_all(&npu_log.wq); |
| |
| /* Wait a few ms until all the readers dump their log */ |
| if (atomic_read(&npu_log.fs_ref) > 0) { |
| npu_info("Waiting for all the logs are dumped via debufgs.\n"); |
| msleep(NPU_STORE_LOG_FLUSH_INTERVAL_MS); |
| } |
| |
| npu_info("Store log memory deinitializing : %pK -> NULL\n", npu_log.st_buf); |
| spin_lock(&npu_log_lock); |
| npu_log.st_buf = NULL; |
| npu_log.st_size = 0; |
| npu_log.wr_pos = 0; |
| /* |
| * Reset ref count as Zero. There may be other client left, |
| * but they will not reduce counter if current value is zero. |
| */ |
| atomic_set(&npu_log.fs_ref, 0); |
| spin_unlock(&npu_log_lock); |
| } |
| |
| void npu_fw_report_init(char *buf_addr, const size_t size) |
| { |
| unsigned long intr_flags; |
| |
| BUG_ON(!buf_addr); |
| BUG_ON(size < PAGE_SIZE); |
| |
| spin_lock_irqsave(&fw_report_lock, intr_flags); |
| fw_report.st_buf = buf_addr; |
| buf_addr[0] = '\0'; |
| buf_addr[size - 1] = '\0'; |
| fw_report.st_size = size; |
| fw_report.wr_pos = 0; |
| fw_report.line_cnt = 0; |
| |
| spin_unlock_irqrestore(&fw_report_lock, intr_flags); |
| |
| } |
| |
| void npu_fw_report_deinit(void) |
| { |
| unsigned long intr_flags; |
| |
| /* Wake-up readers and preserve some time to flush */ |
| wake_up_all(&fw_report.wq); |
| msleep(NPU_STORE_LOG_FLUSH_INTERVAL_MS); |
| |
| npu_info("fw_report memory deinitializing : %pK -> NULL\n", fw_report.st_buf); |
| spin_lock_irqsave(&fw_report_lock, intr_flags); |
| fw_report.st_buf = NULL; |
| fw_report.st_size = 0; |
| fw_report.wr_pos = 0; |
| fw_report.line_cnt = 0; |
| |
| spin_unlock_irqrestore(&fw_report_lock, intr_flags); |
| |
| } |
| |
| const u32 READ_OBJ_MAGIC = 0x1090D358; |
| struct npu_store_log_read_obj { |
| u32 magic; |
| size_t read_pos; |
| }; |
| |
| /* |
| * Set readpos to specified offset from current wr_pos. |
| * 0 means set offset as big as possible. |
| */ |
| static void npu_store_log_set_offset(struct npu_store_log_read_obj *robj, ssize_t offset) |
| { |
| int ret; |
| ssize_t new_rpos; |
| ssize_t wr_pos, st_size, last_dump_mark_pos; |
| |
| ret = spin_lock_safe_isr(&npu_log_lock); |
| if (ret) |
| goto imm_exit; |
| |
| st_size = npu_log.st_size; |
| wr_pos = npu_log.wr_pos; |
| last_dump_mark_pos = npu_log.last_dump_mark_pos; |
| |
| if (st_size < offset + PAGE_SIZE) { |
| new_rpos = 0; |
| goto unlock_exit; |
| } |
| |
| /* Biggest offset -> buffer size - PAGE_SIZE */ |
| if (offset == 0) |
| offset = st_size - PAGE_SIZE; |
| |
| new_rpos = 0; |
| if (npu_log.st_buf) { |
| /* Limit offset if last dump postion is available */ |
| if (npu_log.st_buf[last_dump_mark_pos] == NPU_LOG_DUMP_MARK) { |
| /* offset = min(offset, distance between wr_pos and last_dump_mark_pos) */ |
| offset = min(offset, |
| (wr_pos + st_size - (last_dump_mark_pos + 1)) % st_size); |
| } |
| |
| if (npu_log.st_buf[st_size - 1] != '\0') { |
| /* Buffer is rolled */ |
| new_rpos = (wr_pos + (st_size - offset)) % st_size; |
| } else { |
| /* Buffer is not rolled */ |
| if (offset > wr_pos) |
| new_rpos = 0; |
| else |
| new_rpos = wr_pos - offset; |
| } |
| } |
| |
| unlock_exit: |
| BUG_ON(new_rpos < 0); |
| robj->read_pos = (size_t)new_rpos; |
| spin_unlock(&npu_log_lock); |
| |
| imm_exit: |
| return; |
| } |
| |
| static int npu_store_log_fops_open(struct inode *inode, struct file *file) |
| { |
| int ret; |
| struct npu_store_log_read_obj *robj; |
| |
| robj = kzalloc(sizeof(*robj), GFP_ATOMIC); |
| if (!robj) { |
| ret = -ENOMEM; |
| goto err_exit; |
| } |
| |
| robj->magic = READ_OBJ_MAGIC; |
| /* TODO: It would be more useful if read_pos can start from circular queue tail */ |
| |
| file->private_data = robj; |
| |
| /* Increase reference counter */ |
| atomic_inc(&npu_log.fs_ref); |
| |
| npu_store_log_set_offset(robj, 0); |
| |
| npu_info("fd open robj @ %pK\n", robj); |
| return 0; |
| |
| err_exit: |
| return ret; |
| } |
| |
| static int npu_fw_report_fops_open(struct inode *inode, struct file *file) |
| { |
| int ret = 0; |
| struct npu_store_log_read_obj *robj; |
| |
| robj = kzalloc(sizeof(*robj), GFP_ATOMIC); |
| if (!robj) { |
| ret = -ENOMEM; |
| goto err_exit; |
| } |
| robj->read_pos = 0; |
| robj->magic = READ_OBJ_MAGIC; |
| /* TODO: It would be more useful if read_pos can start from circular queue tail */ |
| file->private_data = robj; |
| |
| npu_info("fd open robj @ %pK\n", robj); |
| return 0; |
| |
| err_exit: |
| return ret; |
| } |
| |
| static int npu_fw_report_fops_close(struct inode *inode, struct file *file) |
| { |
| struct npu_store_log_read_obj *robj; |
| |
| BUG_ON(!file); |
| robj = file->private_data; |
| BUG_ON(!robj); |
| BUG_ON(robj->magic != READ_OBJ_MAGIC); |
| |
| npu_info("fd close robj @ %pK\n", robj); |
| kfree(robj); |
| |
| return 0; |
| |
| } |
| |
| static int npu_store_log_fops_close(struct inode *inode, struct file *file) |
| { |
| struct npu_store_log_read_obj *robj; |
| |
| BUG_ON(!file); |
| robj = file->private_data; |
| BUG_ON(!robj); |
| BUG_ON(robj->magic != READ_OBJ_MAGIC); |
| |
| /* Decrease reference counter */ |
| atomic_dec_if_positive(&npu_log.fs_ref); |
| |
| npu_info("fd close robj @ %pK\n", robj); |
| kfree(robj); |
| |
| return 0; |
| } |
| |
| /* Caller should acquire npu_log_lock spinlock before call this function */ |
| static ssize_t __npu_store_log_fops_read(struct npu_store_log_read_obj *robj, char *outbuf, const size_t outlen) |
| { |
| size_t copy_len, buf_end; |
| |
| /* Copy data to temporary buffer*/ |
| if (npu_log.st_buf) { |
| buf_end = (robj->read_pos > npu_log.wr_pos) ? npu_log.st_size : npu_log.wr_pos; |
| copy_len = min(outlen, buf_end - robj->read_pos); |
| memcpy(outbuf, npu_log.st_buf + robj->read_pos, copy_len); |
| } else { |
| buf_end = 0; |
| copy_len = 0; |
| } |
| |
| pr_debug("end: %zu copy_len: %zu wr_pos: %zu read_pos: %zu\n", buf_end, copy_len, npu_log.wr_pos, robj->read_pos); |
| |
| if (copy_len > 0) { |
| robj->read_pos = (robj->read_pos + copy_len) % npu_log.st_size; |
| /* Set dump mark */ |
| npu_log.last_dump_mark_pos = (robj->read_pos - 1) % npu_log.st_size; |
| npu_log.st_buf[npu_log.last_dump_mark_pos] = NPU_LOG_DUMP_MARK; |
| } |
| |
| pr_debug("Buf = %pK, Read pos = %zu, Read len = %zu\n", npu_log.st_buf, robj->read_pos, copy_len); |
| |
| return copy_len; |
| } |
| |
| static ssize_t npu_store_log_fops_read(struct file *file, char __user *outbuf, size_t outlen, loff_t *loff) |
| { |
| struct npu_store_log_read_obj *robj; |
| ssize_t ret, copy_len; |
| size_t tmp_buf_len; |
| char *tmp_buf = NULL; |
| |
| BUG_ON(!file); |
| robj = file->private_data; |
| BUG_ON(!robj); |
| BUG_ON(robj->magic != READ_OBJ_MAGIC); |
| |
| /* Temporal kernel buffer, to read inside of spinlock */ |
| tmp_buf_len = min(outlen, PAGE_SIZE); |
| tmp_buf = kzalloc(tmp_buf_len, GFP_KERNEL); |
| if (!tmp_buf) { |
| ret = -ENOMEM; |
| goto err_exit; |
| } |
| |
| /* Check data available */ |
| ret = 0; |
| while (ret == 0) { /* ret = 0 on timeout */ |
| /* TODO: Accessing npu_log.wr_pos outside of spinlock is potentially dangerous */ |
| ret = wait_event_interruptible_timeout(npu_log.wq, robj->read_pos != npu_log.wr_pos, 1 * HZ); |
| if (ret == -ERESTARTSYS) { |
| ret = 0; |
| goto err_exit; |
| } |
| } |
| |
| ret = spin_lock_safe_isr(&npu_log_lock); |
| if (ret) |
| goto err_exit; |
| |
| copy_len = __npu_store_log_fops_read(robj, tmp_buf, tmp_buf_len); |
| spin_unlock(&npu_log_lock); |
| |
| if (copy_len > 0) { |
| ret = copy_to_user(outbuf, tmp_buf, copy_len); |
| if (ret) { |
| pr_err("copy_to_user failed : %zd\n", ret); |
| ret = -EFAULT; |
| goto err_exit; |
| } |
| } |
| |
| ret = copy_len; |
| err_exit: |
| |
| if (tmp_buf) |
| kfree(tmp_buf); |
| |
| /* schedule() is insearted to prevent busy-waiting loop around npu_log_lock */ |
| schedule(); |
| return ret; |
| } |
| |
| void npu_fw_report_store(char *strRep, int nSize) |
| { |
| size_t wr_len = 0; |
| size_t remain = fw_report.st_size - fw_report.wr_pos; |
| char *buf = fw_report.st_buf + fw_report.wr_pos; |
| unsigned long intr_flags; |
| |
| spin_lock_irqsave(&fw_report_lock, intr_flags); |
| |
| //check overflow |
| if (nSize >= (int)remain) { |
| fw_report.st_buf[fw_report.wr_pos] = '\0'; |
| fw_report.line_cnt = fw_report.wr_pos; |
| fw_report.wr_pos = 0; |
| remain = fw_report.st_size; |
| buf = fw_report.st_buf; |
| fw_report.last_dump_line_cnt++; |
| } |
| |
| memcpy(buf, strRep, nSize); |
| remain -= nSize; |
| wr_len += nSize; |
| npu_dbg("fw_report nSize : %d,\t remain = %zu\n", nSize, remain); |
| |
| /* Update write position */ |
| fw_report.wr_pos = (fw_report.wr_pos + wr_len) % fw_report.st_size; |
| fw_report.st_buf[fw_report.wr_pos] = '\0'; |
| |
| spin_unlock_irqrestore(&fw_report_lock, intr_flags); |
| } |
| |
| static ssize_t __npu_fw_report_fops_read(struct npu_store_log_read_obj *robj, char *outbuf, const size_t outlen) |
| { |
| size_t copy_len, buf_end; |
| |
| /* Copy data to temporary buffer*/ |
| if (fw_report.st_buf) { |
| buf_end = (robj->read_pos > fw_report.wr_pos) ? fw_report.line_cnt : fw_report.wr_pos; |
| copy_len = min(outlen, buf_end - robj->read_pos); |
| memcpy(outbuf, fw_report.st_buf + robj->read_pos, copy_len); |
| } else |
| copy_len = 0; |
| |
| if (copy_len > 0) { |
| robj->read_pos = (robj->read_pos + copy_len) % fw_report.st_size; |
| if (fw_report.line_cnt == robj->read_pos) { |
| fw_report.line_cnt = 0; |
| robj->read_pos = 0; |
| memset(&fw_report.st_buf[fw_report.wr_pos], '\0', (fw_report.st_size - fw_report.wr_pos)); |
| } |
| } |
| |
| return copy_len; |
| } |
| |
| static ssize_t npu_fw_report_fops_read(struct file *file, char __user *outbuf, size_t outlen, loff_t *loff) |
| { |
| struct npu_store_log_read_obj *robj; |
| ssize_t ret, copy_len; |
| size_t tmp_buf_len; |
| char *tmp_buf = NULL; |
| unsigned long intr_flags; |
| |
| BUG_ON(!file); |
| robj = file->private_data; |
| BUG_ON(!robj); |
| BUG_ON(robj->magic != READ_OBJ_MAGIC); |
| |
| /* Temporal kernel buffer, to read inside of spinlock */ |
| tmp_buf_len = min(outlen, PAGE_SIZE); |
| tmp_buf = kzalloc(tmp_buf_len, GFP_KERNEL); |
| if (!tmp_buf) { |
| ret = -ENOMEM; |
| goto err_exit; |
| } |
| |
| /* Check data available */ |
| ret = 0; |
| while (ret == 0) { /* ret = 0 on timeout */ |
| /* TODO: Accessing npu_log.wr_pos outside of spinlock is potentially dangerous */ |
| ret = wait_event_interruptible_timeout(fw_report.wq, robj->read_pos != fw_report.wr_pos, 1 * HZ); |
| if (ret == -ERESTARTSYS) { |
| ret = 0; |
| goto err_exit; |
| } |
| } |
| |
| spin_lock_irqsave(&fw_report_lock, intr_flags); |
| copy_len = __npu_fw_report_fops_read(robj, tmp_buf, tmp_buf_len); |
| spin_unlock_irqrestore(&fw_report_lock, intr_flags); |
| |
| if (copy_len > 0) { |
| ret = copy_to_user(outbuf, tmp_buf, copy_len); |
| if (ret) { |
| pr_err("copy_to_user failed : %zd\n", ret); |
| ret = -EFAULT; |
| goto err_exit; |
| } |
| } |
| |
| ret = copy_len; |
| err_exit: |
| if (tmp_buf) |
| kfree(tmp_buf); |
| return ret; |
| } |
| |
| const struct file_operations npu_store_log_fops = { |
| .owner = THIS_MODULE, |
| .open = npu_store_log_fops_open, |
| .release = npu_store_log_fops_close, |
| .read = npu_store_log_fops_read, |
| }; |
| |
| const struct file_operations npu_fw_report_fops = { |
| .owner = THIS_MODULE, |
| .open = npu_fw_report_fops_open, |
| .release = npu_fw_report_fops_close, |
| .read = npu_fw_report_fops_read, |
| }; |
| |
| static int npu_store_log_dump(const size_t dump_size) |
| { |
| char *dump_buf = NULL; |
| struct npu_store_log_read_obj dump_robj; |
| size_t rd, total, pos; |
| char *st; |
| int ret; |
| |
| /* waitq initialization is not necessary */ |
| memset(&dump_robj, 0, sizeof(dump_robj)); |
| dump_robj.magic = READ_OBJ_MAGIC; |
| |
| npu_store_log_set_offset(&dump_robj, dump_size); |
| |
| dump_buf = kzalloc(dump_size, GFP_ATOMIC); |
| if (!dump_buf) |
| return -ENOMEM; |
| |
| /* Read log */ |
| total = 0; |
| ret = spin_lock_safe_isr(&npu_log_lock); |
| if (ret) { |
| pr_err("NPU log dump is not available - in interrupt context\n", total); |
| goto err_exit; |
| } |
| |
| if (npu_log.line_cnt == npu_log.last_dump_line_cnt + 1) { |
| /* No need to print out because already dumped */ |
| total = 0; |
| } else { |
| while (total < dump_size) { |
| rd = __npu_store_log_fops_read(&dump_robj, (dump_buf + total), (dump_size - total)); |
| if (rd == 0) /* No data anymore */ |
| break; |
| total += rd; |
| } |
| } |
| npu_log.last_dump_line_cnt = npu_log.line_cnt; |
| spin_unlock(&npu_log_lock); |
| |
| if (total > 0) { |
| /* Print stack back trace - Printed if there is a log to print to avoid duplication */ |
| //dump_stack();// removed for kernel pointer leakage issue |
| |
| pos = 0; |
| st = dump_buf; |
| pr_err("---------- Start NPU log dump (len = %zu) ---------------\n", total); |
| while (pos < total) { |
| if (dump_buf[pos] == '\n') { |
| /* Print on newline character */ |
| dump_buf[pos] = '\0'; |
| pr_cont("%s\n", st); |
| st = dump_buf + pos + 1; |
| } |
| pos++; |
| } |
| if (st != dump_buf + pos) { |
| /* Printout remaining - Not common case but for fail safe */ |
| dump_buf[pos - 1] = '\0'; |
| pr_cont("%s <<No Line Ending>>\n", st); |
| } |
| pr_cont("---------- End of NPU log dump ---------------\n"); |
| } |
| |
| err_exit: |
| |
| kfree(dump_buf); |
| return ret; |
| } |
| |
| /* |
| * Called when an error message is printed |
| */ |
| void npu_log_on_error(void) |
| { |
| int ret; |
| |
| /* Print last log */ |
| ret = npu_store_log_dump(NPU_STORE_LOG_DUMP_SIZE_ON_ERROR); |
| if (ret) |
| pr_err("%s(): npu_store_log_dump() failed: ret = %d\n", __func__, ret); |
| } |
| |
| s32 atoi(const char *psz_buf) |
| { |
| const char *pch = psz_buf; |
| s32 base = 0; |
| |
| while (isspace(*pch)) |
| pch++; |
| |
| if (*pch == '-' || *pch == '+') { |
| base = 10; |
| pch++; |
| } else if (*pch && tolower(pch[strlen(pch) - 1]) == 'h') { |
| base = 16; |
| } |
| |
| return simple_strtoul(pch, NULL, base); |
| } |
| |
| int bitmap_scnprintf(char *buf, unsigned int buflen, |
| const unsigned long *maskp, int nmaskbits) |
| { |
| int i, word, bit, len = 0; |
| unsigned long val; |
| const char *sep = ""; |
| int chunksz; |
| u32 chunkmask; |
| |
| chunksz = nmaskbits & (CHUNKSZ - 1); |
| if (chunksz == 0) |
| chunksz = CHUNKSZ; |
| |
| i = ALIGN(nmaskbits, CHUNKSZ) - CHUNKSZ; |
| for (; i >= 0; i -= CHUNKSZ) { |
| chunkmask = ((1ULL << chunksz) - 1); |
| word = i / BITS_PER_LONG; |
| bit = i % BITS_PER_LONG; |
| val = (maskp[word] >> bit) & chunkmask; |
| len += scnprintf(buf+len, buflen-len, "%s%0*lx", sep, |
| (chunksz+3)/4, val); |
| chunksz = CHUNKSZ; |
| sep = ","; |
| } |
| return len; |
| } |
| |
| int npu_debug_memdump8(u8 *start, u8 *end) |
| { |
| int ret = 0; |
| u8 *cur; |
| u32 items; |
| size_t offset; |
| char term[50], sentence[250]; |
| |
| cur = start; |
| items = 0; |
| offset = 0; |
| |
| memset(sentence, 0, sizeof(sentence)); |
| snprintf(sentence, sizeof(sentence), "[V] Memory Dump8(%pK ~ %pK)", start, end); |
| |
| while (cur < end) { |
| if ((items % 16) == 0) { |
| #ifdef DEBUG_LOG_MEMORY |
| printk(KERN_DEBUG "%s\n", sentence); |
| #else |
| printk(KERN_INFO "%s\n", sentence); |
| #endif |
| offset = 0; |
| snprintf(term, sizeof(term), "[V] %pK: ", cur); |
| snprintf(&sentence[offset], sizeof(sentence) - offset, "%s", term); |
| offset += strlen(term); |
| items = 0; |
| } |
| |
| snprintf(term, sizeof(term), "%02X ", *cur); |
| snprintf(&sentence[offset], sizeof(sentence) - offset, "%s", term); |
| offset += strlen(term); |
| cur++; |
| items++; |
| } |
| |
| if (items) { |
| #ifdef DEBUG_LOG_MEMORY |
| printk(KERN_DEBUG "%s\n", sentence); |
| #else |
| printk(KERN_INFO "%s\n", sentence); |
| #endif |
| } |
| |
| ret = cur - end; |
| |
| return ret; |
| } |
| |
| |
| int npu_debug_memdump16(u16 *start, u16 *end) |
| { |
| int ret = 0; |
| u16 *cur; |
| u32 items; |
| size_t offset; |
| char term[50], sentence[250]; |
| |
| cur = start; |
| items = 0; |
| offset = 0; |
| |
| memset(sentence, 0, sizeof(sentence)); |
| snprintf(sentence, sizeof(sentence), "[V] Memory Dump16(%pK ~ %pK)", start, end); |
| |
| while (cur < end) { |
| if ((items % 16) == 0) { |
| #ifdef DEBUG_LOG_MEMORY |
| printk(KERN_DEBUG "%s\n", sentence); |
| #else |
| printk(KERN_INFO "%s\n", sentence); |
| #endif |
| offset = 0; |
| snprintf(term, sizeof(term), "[V] %pK: ", cur); |
| snprintf(&sentence[offset], sizeof(sentence) - offset, "%s", term); |
| offset += strlen(term); |
| items = 0; |
| } |
| |
| snprintf(term, sizeof(term), "0x%04X ", *cur); |
| snprintf(&sentence[offset], sizeof(sentence) - offset, "%s", term); |
| offset += strlen(term); |
| cur++; |
| items++; |
| } |
| |
| if (items) { |
| #ifdef DEBUG_LOG_MEMORY |
| printk(KERN_DEBUG "%s\n", sentence); |
| #else |
| printk(KERN_INFO "%s\n", sentence); |
| #endif |
| } |
| |
| ret = cur - end; |
| |
| return ret; |
| } |
| |
| int npu_debug_memdump32(u32 *start, u32 *end) |
| { |
| int ret = 0; |
| u32 *cur; |
| u32 items; |
| size_t offset; |
| char term[50], sentence[250]; |
| |
| cur = start; |
| items = 0; |
| offset = 0; |
| |
| memset(sentence, 0, sizeof(sentence)); |
| snprintf(sentence, sizeof(sentence), "[V] Memory Dump32(%pK ~ %pK)", start, end); |
| |
| while (cur < end) { |
| if ((items % 8) == 0) { |
| #ifdef DEBUG_LOG_MEMORY |
| printk(KERN_DEBUG "%s\n", sentence); |
| #else |
| printk(KERN_INFO "%s\n", sentence); |
| #endif |
| offset = 0; |
| snprintf(term, sizeof(term), "[V] %pK: ", cur); |
| snprintf(&sentence[offset], sizeof(sentence) - offset, "%s", term); |
| offset += strlen(term); |
| items = 0; |
| } |
| |
| snprintf(term, sizeof(term), "0x%08X ", *cur); |
| snprintf(&sentence[offset], sizeof(sentence) - offset, "%s", term); |
| offset += strlen(term); |
| cur++; |
| items++; |
| } |
| |
| if (items) { |
| #ifdef DEBUG_LOG_MEMORY |
| printk(KERN_DEBUG "%s\n", sentence); |
| #else |
| printk(KERN_INFO "%s\n", sentence); |
| #endif |
| } |
| |
| ret = cur - end; |
| |
| return ret; |
| } |
| |
| /* |
| This function could be used when direct access to SRAM is not approved. |
| */ |
| int npu_debug_memdump32_by_memcpy(u32 *start, u32 *end) |
| { |
| int j, k, l; |
| int ret = 0; |
| u32 items; |
| u32 *cur; |
| char term[4], strHexa[128], strString[128], sentence[256]; |
| |
| cur = start; |
| items = 0; |
| j = k = l = 0; |
| |
| memset(sentence, 0, sizeof(sentence)); |
| memset(strString, 0, sizeof(strString)); |
| memset(strHexa, 0, sizeof(strHexa)); |
| j = sprintf(sentence, "[V] Memory Dump32(%pK ~ %pK)", start, end); |
| while (cur < end) { |
| if ((items % 4) == 0) { |
| j += sprintf(sentence + j, "%s %s", strHexa, strString); |
| #ifdef DEBUG_LOG_MEMORY |
| pr_debug("%s\n", sentence); |
| #else |
| pr_info("%s\n", sentence); |
| #endif |
| j = 0; items = 0; k = 0; l = 0; |
| j = sprintf(sentence, "[V] %pK: ", cur); |
| items = 0; |
| } |
| memcpy_fromio(term, cur, sizeof(term)); |
| k += sprintf(strHexa + k, "%02X%02X%02X%02X ", |
| term[0], term[1], term[2], term[3]); |
| l += sprintf(strString + l, "%c%c%c%c", ISPRINTABLE(term[0]), |
| ISPRINTABLE(term[1]), ISPRINTABLE(term[2]), ISPRINTABLE(term[3])); |
| cur++; |
| items++; |
| } |
| if (items) { |
| j += sprintf(sentence + j, "%s %s", strHexa, strString); |
| #ifdef DEBUG_LOG_MEMORY |
| pr_debug("%s\n", sentence); |
| #else |
| pr_info("%s\n", sentence); |
| #endif |
| } |
| ret = cur - end; |
| return ret; |
| } |
| |
| |
| static ssize_t npu_chg_log_level_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| /* |
| * sysfs read buffer size is PAGE_SIZE |
| * Ref: http://www.kernel.org/pub/linux/kernel/people/mochel/doc/papers/ols-2005/mochel.pdf |
| */ |
| ssize_t remain = PAGE_SIZE - 1; |
| ssize_t len = 0; |
| ssize_t ret = 0; |
| int lv; |
| |
| npu_info("start."); |
| |
| npu_dbg("current log level : pr = %d, st = %d", npu_log.pr_level, npu_log.st_level); |
| ret = scnprintf(buf, remain, |
| " Usage : echo <printk>.<Memory> > log_level\n" |
| " Example: # echo 3.4 > log_level\n\n" |
| " <printk> <Memory>\n"); |
| if (ret > 0) |
| len += ret, remain -= ret, buf += ret; |
| |
| for (lv = 0; lv < NPU_LOG_INVALID; ++lv) { |
| if (npu_log.pr_level == lv) |
| ret = scnprintf(buf, remain, |
| "[%d] %-10s", lv, LOG_LEVEL_NAME[lv]); |
| else |
| ret = scnprintf(buf, remain, |
| " %d %-10s", lv, LOG_LEVEL_NAME[lv]); |
| if (ret > 0) |
| len += ret, remain -= ret, buf += ret; |
| |
| if (npu_log.st_level == lv) |
| ret = scnprintf(buf, remain, |
| "[%d] %-10s\n", lv, LOG_LEVEL_NAME[lv]); |
| else |
| ret = scnprintf(buf, remain, |
| " %d %-10s\n", lv, LOG_LEVEL_NAME[lv]); |
| if (ret > 0) |
| len += ret, remain -= ret, buf += ret; |
| } |
| |
| return len; |
| } |
| |
| static ssize_t npu_chg_log_level_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) |
| { |
| int pr, st; |
| int ret; |
| |
| /* Parsing */ |
| pr = st = -1; |
| ret = sscanf(buf, "%1d.%1d", &pr, &st); |
| if (ret != 2) { |
| npu_err("Command line parsing error.\n"); |
| return -EINVAL; |
| } |
| if (pr < NPU_LOG_TRACE || NPU_LOG_INVALID < pr) { |
| npu_err("Invalid pr_level [%d] specified.\n", pr); |
| return -EINVAL; |
| } |
| if (st < NPU_LOG_TRACE || NPU_LOG_INVALID < st) { |
| npu_err("Invalid st_level [%d] specified.\n", st); |
| return -EINVAL; |
| } |
| |
| /* Change log level */ |
| npu_info("log level for pr_level [%d -> %d] / st_level [%d -> %d]\n", |
| npu_log.pr_level, pr, npu_log.st_level, st); |
| npu_log.pr_level = pr; |
| npu_log.st_level = st; |
| barrier(); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(log_level, 0644, npu_chg_log_level_show, npu_chg_log_level_store); |
| |
| int fw_will_note(size_t len) |
| { |
| unsigned long intr_flags; |
| char *buf; |
| size_t i, pos; |
| bool bReqLegacy = FALSE; |
| |
| //Gather one more time before make will note. |
| fw_rprt_manager(); |
| spin_lock_irqsave(&fw_report_lock, intr_flags); |
| |
| //Consideration for early phase |
| if (fw_report.wr_pos < len) { |
| len = fw_report.wr_pos; |
| bReqLegacy = TRUE; |
| } |
| buf = kzalloc((len+1), GFP_ATOMIC); |
| if (!buf) { |
| npu_err("%s() fail to alloc mem for fw_report\n", __func__); |
| spin_unlock_irqrestore(&fw_report_lock, intr_flags); |
| return -ENOMEM; |
| } |
| pos = 0; |
| pr_err("----------- Start will_note for npu_fw (/sys/kernel/debug/npu/fw-report )-------------\n"); |
| if ((fw_report.last_dump_line_cnt != 0) && (bReqLegacy == TRUE)) { |
| for (i = fw_report.st_size - (len - fw_report.wr_pos); i < fw_report.st_size; i++) { |
| buf[pos] = fw_report.st_buf[i]; |
| pos++; |
| if (fw_report.st_buf[i] == '\n') { |
| buf[pos] = '\0'; |
| pr_cont("%s", buf); |
| pos = 0; |
| buf[0] = '\0'; |
| } |
| } |
| //Change length to current position. |
| len = fw_report.wr_pos; |
| } |
| for (i = fw_report.wr_pos - len ; i < fw_report.wr_pos; i++) { |
| buf[pos] = fw_report.st_buf[i]; |
| pos++; |
| if (fw_report.st_buf[i] == '\n') { |
| buf[pos] = '\0'; |
| pr_cont("%s", buf); |
| pos = 0; |
| buf[0] = '\0'; |
| } |
| } |
| |
| pr_cont("----------- End of will_note for npu_fw -------------\n"); |
| spin_unlock_irqrestore(&fw_report_lock, intr_flags); |
| kfree(buf); |
| pr_err("----------- Check unposted_mbox ---------------------\n"); |
| npu_check_unposted_mbox(ECTRL_LOW); |
| npu_check_unposted_mbox(ECTRL_HIGH); |
| npu_check_unposted_mbox(ECTRL_ACK); |
| npu_check_unposted_mbox(ECTRL_REPORT); |
| pr_err("----------- Done unposted_mbox ----------------------\n"); |
| return 0; |
| |
| } |
| |
| /* Exported functions */ |
| int npu_log_probe(struct npu_device *npu_device) |
| { |
| int ret; |
| |
| probe_info("start in npu_log_probe\n"); |
| |
| /* Basic initialization of log store */ |
| npu_log.st_buf = NULL; |
| npu_log.st_size = 0; |
| npu_log.wr_pos = 0; |
| init_waitqueue_head(&npu_log.wq); |
| init_waitqueue_head(&fw_report.wq); |
| |
| /* Log level change function on sysfs */ |
| ret = device_create_file(npu_device->dev, &dev_attr_log_level); |
| if (ret) { |
| probe_err("device_create_file() failed: ret = %d\n", ret); |
| return ret; |
| } |
| |
| /* Register memory store logger */ |
| ret = npu_debug_register("dev-log", &npu_store_log_fops); |
| if (ret) { |
| npu_err("npu_debug_register error : ret = %d\n", ret); |
| return ret; |
| } |
| |
| /* Register FW log keeper */ |
| ret = npu_debug_register("fw-report", &npu_fw_report_fops); |
| if (ret) { |
| npu_err("npu_debug_register error : ret = %d\n", ret); |
| return ret; |
| } |
| |
| probe_info("complete in npu_log_probe\n"); |
| return 0; |
| } |
| |
| int npu_log_release(struct npu_device *npu_device) |
| { |
| probe_info("start in npu_log_release\n"); |
| device_remove_file(npu_device->dev, &dev_attr_log_level); |
| probe_info("complete in npu_log_release\n"); |
| return 0; |
| } |
| |
| int npu_log_open(struct npu_device *npu_device) |
| { |
| npu_info("start in npu_log_open\n"); |
| npu_info("complete in npu_log_open\n"); |
| return 0; |
| } |
| |
| int npu_log_close(struct npu_device *npu_device) |
| { |
| npu_info("start in npu_log_close\n"); |
| npu_info("complete in npu_log_close\n"); |
| return 0; |
| } |
| |
| int npu_log_start(struct npu_device *npu_device) |
| { |
| npu_info("start in npu_log_start\n"); |
| npu_info("complete in npu_log_start\n"); |
| return 0; |
| } |
| |
| int npu_log_stop(struct npu_device *npu_device) |
| { |
| npu_info("start in npu_log_stop\n"); |
| npu_info("complete in npu_log_stop\n"); |
| return 0; |
| } |
| |
| /* Unit test */ |
| #ifdef CONFIG_VISION_UNITTEST |
| #define IDIOT_TESTCASE_IMPL "npu-log.idiot" |
| #include "idiot-def.h" |
| #endif |