// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include /* for Kernel Native SMC API */ #include #include #include #include #include #include #include /* min() */ #include /* copy_to_user() */ #include /* local_clock() */ #include /* TASK_INTERRUPTIBLE/signal_pending/schedule */ #include #include /* ioremap() */ #include #include #include #include #include #include #include #include #include #include #include #include /* for SMC ID table */ #ifdef CONFIG_OF_RESERVED_MEM #include #define ATF_LOG_RESERVED_MEMORY_KEY "mediatek,atf-log-reserved" #endif /* #define ATF_LOGGER_DEBUG */ #define ATF_LOG_CTRL_BUF_SIZE 512 #define ATF_CRASH_MAGIC_NO 0xdead1abf #define ATF_LAST_MAGIC_NO 0x41544641 static struct mutex atf_mutex; /* shared between the threads */ static struct mutex atf_raw_buf_mutex; static u32 atf_raw_read_offset; static wait_queue_head_t atf_log_wq; #ifdef CONFIG_OF static const struct of_device_id atf_logger_of_ids[] = { { .compatible = "mediatek,atf_logger", }, {}, }; #endif union atf_log_ctrl_t { struct { u64 atf_log_addr; /* 0x00 */ u64 atf_log_size; u32 atf_write_offset; u32 atf_read_offset; /* must keep value after reboot */ u64 atf_crash_log_addr; u64 atf_crash_log_size; u32 atf_total_write_count; u32 atf_crash_flag; /* Remove arrays used CONFIG_NR_CPUS */ /* Please add new field before these arrays */ } info; unsigned char data[ATF_LOG_CTRL_BUF_SIZE]; }; static union atf_log_ctrl_t *atf_buf_vir_ctrl; static phys_addr_t atf_buf_phy_ctrl; static phys_addr_t atf_buf_len; static unsigned char *atf_buf_vir_addr; static unsigned char *atf_log_vir_addr; static unsigned int atf_log_len; #if defined(CONFIG_OF) static int dt_parse_atf_logger_buf(void) { struct device_node *atf_logger_rmem_np; struct reserved_mem *rmem; atf_logger_rmem_np = of_find_compatible_node(NULL, NULL, ATF_LOG_RESERVED_MEMORY_KEY); if (!atf_logger_rmem_np) { pr_info("No atf_logger reserved memory\n"); return -EINVAL; } rmem = of_reserved_mem_lookup(atf_logger_rmem_np); if (!rmem) { pr_info("No rmem\n"); return -EINVAL; } atf_buf_phy_ctrl = (phys_addr_t) rmem->base; atf_buf_len = (phys_addr_t) rmem->size; #ifdef ATF_LOGGER_DEBUG pr_notice("%s, start: %pa, size: %pa\n", ATF_LOG_RESERVED_MEMORY_KEY, &atf_buf_phy_ctrl, &atf_buf_len); #endif return 0; } #endif static ssize_t atf_log_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { unsigned long ret = -1; unsigned long param = -1; struct arm_smccc_res res; if (count < 12) { /* for coverity check */ /* use kstrtoul_from_user() instead of */ /* copy_from_user() and kstrtoul() */ ret = kstrtoul_from_user(buf, count, 16, ¶m); } #ifdef ATF_LOGGER_DEBUG pr_notice("[%s]param:0x%lx, count:%zu, ret:%ld\n", __func__, param, count, ret); #endif if (!ret) { arm_smccc_smc(MTK_SIP_KERNEL_ATF_DEBUG, param, 0, 0, 0, 0, 0, 0, &res); } else { wake_up_interruptible(&atf_log_wq); } *pos += count; return count; } static ssize_t do_read_log_to_usr(char __user *buf, size_t count) { size_t copy_len; size_t right; unsigned int local_write_index = atf_buf_vir_ctrl->info.atf_write_offset; unsigned int local_read_index = atf_buf_vir_ctrl->info.atf_read_offset; /* check copy length */ copy_len = (local_write_index + atf_log_len - local_read_index) % atf_log_len; /* if copy length < count, just copy the "copy length" */ if (count > copy_len) count = copy_len; if (local_write_index > local_read_index) { /* write (right) - read (left) */ /* --------R-------W-----------*/ if (copy_to_user(buf, atf_log_vir_addr + local_read_index, count)) return -EFAULT; } else { /* turn around to the head */ /* --------W-------R-----------*/ right = atf_log_len - local_read_index; /* check buf space is enough to copy */ if (count > right) { /* if space is enough to copy */ if (copy_to_user(buf, atf_log_vir_addr + local_read_index, right)) return -EFAULT; if (copy_to_user((buf + right), atf_log_vir_addr, count - right)) return -EFAULT; } else { /* if count is only enough to copy right or count, * just copy right or count. */ if (copy_to_user(buf, atf_log_vir_addr + local_read_index, count)) return -EFAULT; } } /* update the read pos */ local_read_index = (local_read_index + count) % atf_log_len; atf_buf_vir_ctrl->info.atf_read_offset = local_read_index; return count; } static int atf_log_open(struct inode *inode, struct file *file) { int ret; ret = nonseekable_open(inode, file); if (unlikely(ret)) return ret; file->private_data = NULL; return 0; } static int atf_log_release(struct inode *ignored, struct file *file) { return 0; } static ssize_t atf_log_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) { ssize_t readback_bytes = 0; int rc = 0; unsigned int write_pos; unsigned int read_pos; /* inside a thread */ mutex_lock(&atf_mutex); start: while (1) { /* pr_notice("atf_log_read: wait in wq\n"); */ wait_event_timeout(atf_log_wq, (atf_buf_vir_ctrl->info.atf_write_offset != atf_buf_vir_ctrl->info.atf_read_offset), HZ); write_pos = atf_buf_vir_ctrl->info.atf_write_offset; read_pos = atf_buf_vir_ctrl->info.atf_read_offset; if (write_pos != read_pos) { break; } else if (file->f_flags & O_NONBLOCK) { rc = -EAGAIN; break; } } if (rc) { pr_notice("%s: rc=%d\n", __func__, rc); /* do the work with the data you're protecting */ mutex_unlock(&atf_mutex); return rc; } if (unlikely(write_pos == read_pos)) goto start; readback_bytes = do_read_log_to_usr(buf, count); /* update the file pos */ *f_pos += readback_bytes; /* do the work with the data you're protecting */ mutex_unlock(&atf_mutex); return readback_bytes; } static unsigned int atf_log_poll(struct file *file, poll_table *wait) { unsigned int ret = 0; if (!(file->f_mode & FMODE_READ)) return ret; poll_wait(file, &atf_log_wq, wait); if (atf_buf_vir_ctrl->info.atf_write_offset != atf_buf_vir_ctrl->info.atf_read_offset) ret = POLLIN | POLLRDNORM; return ret; } static long atf_log_ioctl(struct file *flip, unsigned int cmd, unsigned long arg) { return 0; } #ifdef ATF_LOGGER_DEBUG void show_atf_log_ctl(void) { pr_notice("ATF reserved memory, start: %pa, size: %pa\n", &atf_buf_phy_ctrl, &atf_buf_len); pr_notice("atf_buf_phy_ctrl: %pa\n", &atf_buf_phy_ctrl); pr_notice("atf_buf_len: %pa\n", &atf_buf_len); pr_notice("atf_buf_vir_ctrl: %pa\n", &atf_buf_vir_ctrl); pr_notice("atf_log_vir_addr: %pa\n", &atf_log_vir_addr); pr_notice("atf_log_len: %u\n", atf_log_len); pr_notice("atf_log_addr(%pa) = 0x%x\n", &(atf_buf_vir_ctrl->info.atf_log_addr), atf_buf_vir_ctrl->info.atf_log_addr); pr_notice("atf_log_size(%pa) = 0x%x\n", &(atf_buf_vir_ctrl->info.atf_log_size), atf_buf_vir_ctrl->info.atf_log_size); pr_notice("atf_write_offset(%pa) = %u\n", &(atf_buf_vir_ctrl->info.atf_write_offset), atf_buf_vir_ctrl->info.atf_write_offset); pr_notice("atf_read_offset(%pa) = %u\n", &(atf_buf_vir_ctrl->info.atf_read_offset), atf_buf_vir_ctrl->info.atf_read_offset); } static void show_data(unsigned long addr, uint64_t nbytes, const char *name) { uint64_t i, j; uint64_t nlines; uint32_t *p = 0; pr_notice("\n%s: %#lx:\n", name, addr); /* * round address down to a 32 bit boundary * and always dump a multiple of 32 bytes */ p = (uint32_t *)(addr & ~(sizeof(uint32_t) - 1)); nbytes += (uint64_t) (addr & (sizeof(uint32_t) - 1)); nlines = (nbytes + 31) / 32; for (i = 0; i < nlines; i++) { /* * just display low 16 bits of address to keep * each line of the dump < 80 characters */ pr_notice("%#lx ", ((uintptr_t)p) & 0xffff); for (j = 0; j < 8; j++) { u32 data; if (probe_kernel_address((void *)p, data)) pr_notice(" ********"); else pr_notice(" %08x", data); ++p; } pr_debug("\n"); } } #endif static void atf_time_sync_resume(void) { /* Get local_clock and sync to ATF */ u64 time_to_sync = local_clock(); struct arm_smccc_res res; /* always separate time_to_sync into two 32 bits args */ arm_smccc_smc(MTK_SIP_KERNEL_TIME_SYNC, (u32)time_to_sync, (u32)(time_to_sync >> 32), 0, 0, 0, 0, 0, &res); } static struct syscore_ops atf_time_sync_syscore_ops = { .resume = atf_time_sync_resume, }; static const struct file_operations atf_log_fops = { .owner = THIS_MODULE, .unlocked_ioctl = atf_log_ioctl, .compat_ioctl = atf_log_ioctl, .poll = atf_log_poll, .open = atf_log_open, .release = atf_log_release, .read = atf_log_read, .write = atf_log_write, }; static struct miscdevice atf_log_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "atf_log", .fops = &atf_log_fops, .mode = 0640, }; static ssize_t do_read_raw_buf_to_usr(char __user *buf, size_t count) { size_t copy_len; /* check copy length */ copy_len = atf_buf_len - atf_raw_read_offset; /* if copy length < count, just copy the "copy length" */ if (count > copy_len) count = copy_len; if (copy_to_user(buf, (uint8_t *)(atf_buf_vir_addr + atf_raw_read_offset), count)) return -EFAULT; /* update the read pos */ atf_raw_read_offset += count; return count; } static ssize_t atf_raw_buf_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) { ssize_t readback_bytes = 0; /* inside a thread */ mutex_lock(&atf_raw_buf_mutex); readback_bytes = do_read_raw_buf_to_usr(buf, count); /* update the file pos */ *f_pos += readback_bytes; /* do the work with the data you're protecting */ mutex_unlock(&atf_raw_buf_mutex); return readback_bytes; } static int atf_raw_buf_open(struct inode *inode, struct file *file) { int ret; ret = nonseekable_open(inode, file); if (unlikely(ret)) return ret; file->private_data = NULL; /* clear raw buffer read offset */ mutex_lock(&atf_raw_buf_mutex); atf_raw_read_offset = 0; mutex_unlock(&atf_raw_buf_mutex); return 0; } static unsigned int atf_raw_buf_poll(struct file *file, poll_table *wait) { unsigned int ret = 0; if (!(file->f_mode & FMODE_READ)) return ret; poll_wait(file, &atf_log_wq, wait); /* always reply readable */ if (atf_raw_read_offset < atf_buf_len) ret = POLLIN | POLLRDNORM; return ret; } static const struct file_operations atf_raw_buf_fops = { .owner = THIS_MODULE, .poll = atf_raw_buf_poll, .open = atf_raw_buf_open, .release = atf_log_release, .read = atf_raw_buf_read, }; static struct miscdevice atf_raw_buf_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "atf_raw_buf", .fops = &atf_raw_buf_fops, .mode = 0440, }; static int __init atf_logger_probe(struct platform_device *pdev) { /* register module driver */ int err; struct proc_dir_entry *atf_log_proc_dir = NULL; struct proc_dir_entry *atf_log_proc_file = NULL; struct proc_dir_entry *atf_raw_buf_proc_file = NULL; /* register module driver */ u64 time_to_sync; struct arm_smccc_res res; pr_notice("atf_log: inited"); err = dt_parse_atf_logger_buf(); if (unlikely(err)) { pr_info("No atf_log_buffer\n"); return -1; } err = misc_register(&atf_log_dev); if (unlikely(err)) { pr_info("atf_log: failed to register device"); return -1; } err = misc_register(&atf_raw_buf_dev); if (unlikely(err)) { pr_info("atf_raw_buf: failed to register device"); return -1; } /* map whole buffer for raw dump */ atf_buf_vir_addr = ioremap_wc(atf_buf_phy_ctrl, atf_buf_len); atf_buf_vir_ctrl = (union atf_log_ctrl_t *) atf_buf_vir_addr; atf_log_len = atf_buf_vir_ctrl->info.atf_log_size; atf_log_vir_addr = (unsigned char *)atf_buf_vir_ctrl + ATF_LOG_CTRL_BUF_SIZE; #ifdef ATF_LOGGER_DEBUG show_atf_log_ctl(); show_data((unsigned long)atf_buf_vir_ctrl, 512, "atf_buf"); #endif /* Synchronize timestamp in Kernel and ATF */ register_syscore_ops(&atf_time_sync_syscore_ops); /* Get local_clock and sync to ATF */ time_to_sync = local_clock(); /* always separate time_to_sync into two 32 bits args */ arm_smccc_smc(MTK_SIP_KERNEL_TIME_SYNC, (u32)time_to_sync, (u32)(time_to_sync >> 32), 0, 0, 0, 0, 0, &res); /* initial wait queue */ init_waitqueue_head(&atf_log_wq); /* create /proc/atf_log */ atf_log_proc_dir = proc_mkdir("atf_log", NULL); if (atf_log_proc_dir == NULL) { pr_info("atf_log proc_mkdir failed\n"); return -ENOMEM; } /* create /proc/atf_log/atf_log */ atf_log_proc_file = proc_create("atf_log", 0440, atf_log_proc_dir, &atf_log_fops); if (atf_log_proc_file == NULL) { pr_info("atf_log proc_create failed at atf_log\n"); return -ENOMEM; } /* create /proc/atf_log/atf_raw_buf */ atf_raw_buf_proc_file = proc_create("atf_raw_buf", 0440, atf_log_proc_dir, &atf_raw_buf_fops); if (atf_raw_buf_proc_file == NULL) { pr_info("atf_raw_buf proc_create failed at atf_log\n"); return -ENOMEM; } return 0; } static void __exit atf_log_exit(void) { misc_deregister(&atf_log_dev); pr_notice("atf_log: exited"); } static int atf_logger_remove(struct platform_device *dev) { return 0; } static struct platform_driver atf_logger_driver_probe = { .probe = atf_logger_probe, .remove = atf_logger_remove, .driver = { .name = "atf_logger", .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = atf_logger_of_ids, #endif }, }; static int __init atf_log_init(void) { int ret = 0; mutex_init(&atf_mutex); /* called only ONCE */ mutex_init(&atf_raw_buf_mutex); ret = platform_driver_register(&atf_logger_driver_probe); if (ret) pr_info("atf logger init FAIL, ret 0x%x\n", ret); return ret; } module_init(atf_log_init); module_exit(atf_log_exit); MODULE_DESCRIPTION("MEDIATEK Module ATF Logging Driver"); MODULE_AUTHOR("Chun Fan"); MODULE_LICENSE("GPL");