|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2019 MediaTek Inc. | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/list.h> | 
|  | #include <linux/debugfs.h> | 
|  | #include <linux/proc_fs.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/semaphore.h> | 
|  | #include <linux/freezer.h> | 
|  | #include <linux/uaccess.h> | 
|  | #include <linux/completion.h> | 
|  |  | 
|  | #include "ondiemet_log.h" | 
|  | #include "interface.h" | 
|  |  | 
|  | #define ONDIEMET_LOG_REQ 1 | 
|  | /* TODO: abandon this constatnt */ | 
|  | #define ONDIEMET_LOG_STOP 2 | 
|  |  | 
|  | #define PID_NONE (-1) | 
|  |  | 
|  | #define ONDIEMET_LOG_STOP_MODE 0 | 
|  | #define ONDIEMET_LOG_RUN_MODE 1 | 
|  | #define ONDIEMET_LOG_DEBUG_MODE 2 | 
|  |  | 
|  | static int ondiemet_trace_run; | 
|  | #ifdef ONDIEMET_MOUNT_DEBUGFS | 
|  | static struct dentry *dbgfs_met_dir; | 
|  | #else | 
|  | static struct proc_dir_entry *procfs_met_dir; | 
|  | #endif | 
|  |  | 
|  | struct mutex lock_tracef; | 
|  | struct ondiemet_log_req_q_t { | 
|  | struct list_head listq; | 
|  | struct mutex lockq; | 
|  | /* struct semaphore new_evt_sema; */ | 
|  | struct completion new_evt_comp; | 
|  | int closeq_flag; | 
|  | } ondiemet_log_req_q; | 
|  |  | 
|  | struct ondiemet_log_req { | 
|  | struct list_head list; | 
|  | int cmd_type; | 
|  | const char *src; | 
|  | size_t num; | 
|  |  | 
|  | void (*on_fini_cb)(const void *p); | 
|  | const void *param; | 
|  | }; | 
|  |  | 
|  | #define __ondiemet_log_req_init(req, cmd, s, n, pf, p)	\ | 
|  | do {						\ | 
|  | INIT_LIST_HEAD(&req->list);		\ | 
|  | req->cmd_type = cmd;			\ | 
|  | req->src = s;				\ | 
|  | req->num = n;				\ | 
|  | req->on_fini_cb = pf;			\ | 
|  | req->param = p;				\ | 
|  | } while (0) | 
|  |  | 
|  | #define __ondiemet_log_req_fini(req)		        \ | 
|  | do {					        \ | 
|  | if (req->on_fini_cb)			\ | 
|  | req->on_fini_cb(req->param);	\ | 
|  | kfree(req);				\ | 
|  | } while (0) | 
|  |  | 
|  | static void __ondiemet_log_req_q_init(struct ondiemet_log_req_q_t *q) | 
|  | { | 
|  | INIT_LIST_HEAD(&q->listq); | 
|  | mutex_init(&q->lockq); | 
|  | /* sema_init(&q->new_evt_sema, 0); */ | 
|  | init_completion(&q->new_evt_comp); | 
|  | q->closeq_flag = 1; | 
|  | } | 
|  |  | 
|  | /* undequeue is seen as a roll-back operation, so it can be done even when the queue is closed */ | 
|  | static void __ondiemet_log_req_undeq(struct ondiemet_log_req *req) | 
|  | { | 
|  | mutex_lock(&ondiemet_log_req_q.lockq); | 
|  | list_add(&req->list, &ondiemet_log_req_q.listq); | 
|  | mutex_unlock(&ondiemet_log_req_q.lockq); | 
|  |  | 
|  | /* up(&ondiemet_log_req_q.new_evt_sema); */ | 
|  | complete(&ondiemet_log_req_q.new_evt_comp); | 
|  | } | 
|  |  | 
|  | static int __ondiemet_log_req_enq(struct ondiemet_log_req *req) | 
|  | { | 
|  | mutex_lock(&ondiemet_log_req_q.lockq); | 
|  | if (ondiemet_log_req_q.closeq_flag) { | 
|  | mutex_unlock(&ondiemet_log_req_q.lockq); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | list_add_tail(&req->list, &ondiemet_log_req_q.listq); | 
|  | if (req->cmd_type == ONDIEMET_LOG_STOP) | 
|  | ondiemet_log_req_q.closeq_flag = 1; | 
|  | mutex_unlock(&ondiemet_log_req_q.lockq); | 
|  |  | 
|  | /* up(&ondiemet_log_req_q.new_evt_sema); */ | 
|  | complete(&ondiemet_log_req_q.new_evt_comp); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ondiemet_log_req_enq(const char *src, size_t num, void (*on_fini_cb)(const void *p), const void *param) | 
|  | { | 
|  | struct ondiemet_log_req *req = kmalloc(sizeof(*req), GFP_KERNEL); | 
|  |  | 
|  | __ondiemet_log_req_init(req, ONDIEMET_LOG_REQ, src, num, on_fini_cb, param); | 
|  | return __ondiemet_log_req_enq(req); | 
|  | } | 
|  |  | 
|  | /*int down_freezable_interruptible(struct semaphore *sem) */ | 
|  | int down_freezable_interruptible(struct completion *comp) | 
|  | { | 
|  |  | 
|  | int ret; | 
|  |  | 
|  | freezer_do_not_count(); | 
|  | /* ret = down_interruptible(sem); */ | 
|  | ret = wait_for_completion_interruptible(comp); | 
|  | freezer_count(); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | struct ondiemet_log_req *__ondiemet_log_req_deq(void) | 
|  | { | 
|  | struct ondiemet_log_req *ret_req; | 
|  |  | 
|  | /*if (down_freezable_interruptible(&ondiemet_log_req_q.new_evt_sema))*/ | 
|  | if (down_freezable_interruptible(&ondiemet_log_req_q.new_evt_comp)) | 
|  | return NULL; | 
|  |  | 
|  | mutex_lock(&ondiemet_log_req_q.lockq); | 
|  | ret_req = list_entry(ondiemet_log_req_q.listq.next, struct ondiemet_log_req, list); | 
|  | list_del_init(&ret_req->list); | 
|  | mutex_unlock(&ondiemet_log_req_q.lockq); | 
|  |  | 
|  | return ret_req; | 
|  | } | 
|  |  | 
|  | void __ondiemet_log_req_open(void) | 
|  | { | 
|  | mutex_lock(&ondiemet_log_req_q.lockq); | 
|  | ondiemet_log_req_q.closeq_flag = 0; | 
|  | mutex_unlock(&ondiemet_log_req_q.lockq); | 
|  | } | 
|  |  | 
|  | int __ondiemet_log_req_closed(void) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&ondiemet_log_req_q.lockq); | 
|  | ret = ondiemet_log_req_q.closeq_flag && list_empty(&ondiemet_log_req_q.listq); | 
|  | mutex_unlock(&ondiemet_log_req_q.lockq); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int __ondiemet_log_req_working(void) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&ondiemet_log_req_q.lockq); | 
|  | ret = !ondiemet_log_req_q.closeq_flag; | 
|  | mutex_unlock(&ondiemet_log_req_q.lockq); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void *__ondiemet_trace_seq_next(struct seq_file *seqf, loff_t *offset) | 
|  | { | 
|  | struct ondiemet_log_req *next_req; | 
|  |  | 
|  | if (ondiemet_trace_run == ONDIEMET_LOG_DEBUG_MODE) | 
|  | pr_debug("[met] __ondiemet_trace_seq_next: pid: %d\n", current->pid); | 
|  |  | 
|  | if (__ondiemet_log_req_closed()) | 
|  | return NULL; | 
|  |  | 
|  | next_req = __ondiemet_log_req_deq(); | 
|  |  | 
|  | if (next_req == NULL) | 
|  | return NULL; | 
|  |  | 
|  | if (next_req->cmd_type == ONDIEMET_LOG_STOP) { | 
|  | __ondiemet_log_req_fini(next_req); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return (void *) next_req; | 
|  | } | 
|  |  | 
|  | struct mutex lock_trace_owner_pid; | 
|  | pid_t trace_owner_pid = PID_NONE; | 
|  | static void *ondiemet_trace_seq_start(struct seq_file *seqf, loff_t *offset) | 
|  | { | 
|  | void *ret; | 
|  |  | 
|  | if (ondiemet_trace_run == ONDIEMET_LOG_DEBUG_MODE) { | 
|  | pr_debug("[met] ondiemet_trace_seq_start: locked_pid: %d, pid: %d, offset: %llu\n", | 
|  | trace_owner_pid, current->pid, *offset); | 
|  | } | 
|  |  | 
|  | if (!mutex_trylock(&lock_tracef)) | 
|  | return NULL; | 
|  |  | 
|  | mutex_lock(&lock_trace_owner_pid); | 
|  | trace_owner_pid = current->pid; | 
|  | current->flags |= PF_NOFREEZE; | 
|  | mutex_unlock(&lock_trace_owner_pid); | 
|  |  | 
|  | ret = __ondiemet_trace_seq_next(seqf, offset); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void *ondiemet_trace_seq_next(struct seq_file *seqf, void *p, loff_t *offset) | 
|  | { | 
|  | if (ondiemet_trace_run == ONDIEMET_LOG_DEBUG_MODE) | 
|  | pr_debug("[met] ondiemet_trace_seq_next: pid: %d\n", current->pid); | 
|  |  | 
|  | (*offset)++; | 
|  | return __ondiemet_trace_seq_next(seqf, offset); | 
|  | } | 
|  |  | 
|  | static int ondiemet_trace_seq_show(struct seq_file *seqf, void *p) | 
|  | { | 
|  | struct ondiemet_log_req *req = (struct ondiemet_log_req *) p; | 
|  | size_t l_sz; | 
|  | size_t r_sz; | 
|  | struct ondiemet_log_req *l_req; | 
|  | struct ondiemet_log_req *r_req; | 
|  | int ret; | 
|  |  | 
|  | if (ondiemet_trace_run == ONDIEMET_LOG_DEBUG_MODE) | 
|  | pr_debug("[met] ondiemet_trace_seq_show: pid: %d\n", current->pid); | 
|  |  | 
|  | if (req->num >= seqf->size) { | 
|  | l_req = kmalloc(sizeof(*req), GFP_KERNEL); | 
|  | r_req = req; | 
|  |  | 
|  | l_sz = seqf->size >> 1; | 
|  | r_sz = req->num - l_sz; | 
|  | __ondiemet_log_req_init(l_req, ONDIEMET_LOG_REQ, req->src, l_sz, NULL, NULL); | 
|  | __ondiemet_log_req_init(r_req, ONDIEMET_LOG_REQ, req->src + l_sz, | 
|  | r_sz, req->on_fini_cb, req->param); | 
|  |  | 
|  | __ondiemet_log_req_undeq(r_req); | 
|  | req = l_req; | 
|  |  | 
|  | if (ondiemet_trace_run == ONDIEMET_LOG_DEBUG_MODE) | 
|  | pr_debug("[met] ondiemet_trace_seq_show: split request\n"); | 
|  | } | 
|  |  | 
|  | ret = seq_write(seqf, req->src, req->num); | 
|  |  | 
|  | if (ret) { | 
|  | /* check if seq_file buffer overflows */ | 
|  | if (seqf->count == seqf->size) { | 
|  | __ondiemet_log_req_undeq(req); | 
|  | } else { | 
|  | if (ondiemet_trace_run == ONDIEMET_LOG_DEBUG_MODE) | 
|  | pr_debug("[met] ondiemet_trace_seq_show: reading trace record failed, some data may be lost or corrupted\n"); | 
|  | __ondiemet_log_req_fini(req); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | __ondiemet_log_req_fini(req); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void ondiemet_trace_seq_stop(struct seq_file *seqf, void *p) | 
|  | { | 
|  | if (ondiemet_trace_run == ONDIEMET_LOG_DEBUG_MODE) | 
|  | pr_debug("[met] ondiemet_trace_seq_stop: pid: %d\n", current->pid); | 
|  |  | 
|  | mutex_lock(&lock_trace_owner_pid); | 
|  | if (current->pid == trace_owner_pid) { | 
|  | trace_owner_pid = PID_NONE; | 
|  | mutex_unlock(&lock_tracef); | 
|  | } | 
|  | mutex_unlock(&lock_trace_owner_pid); | 
|  | } | 
|  |  | 
|  | static const struct seq_operations ondiemet_trace_seq_ops = { | 
|  | .start = ondiemet_trace_seq_start, | 
|  | .next = ondiemet_trace_seq_next, | 
|  | .stop = ondiemet_trace_seq_stop, | 
|  | .show = ondiemet_trace_seq_show | 
|  | }; | 
|  |  | 
|  | static int ondiemet_trace_open(struct inode *inode, struct file *fp) | 
|  | { | 
|  | return seq_open(fp, &ondiemet_trace_seq_ops); | 
|  | } | 
|  |  | 
|  | static const struct file_operations ondiemet_trace_fops = { | 
|  | .owner = THIS_MODULE, | 
|  | .open = ondiemet_trace_open, | 
|  | .read = seq_read, | 
|  | .llseek = seq_lseek, | 
|  | .release = seq_release | 
|  | }; | 
|  |  | 
|  | /*struct semaphore log_start_sema;*/ | 
|  | struct completion log_start_comp; | 
|  | int ondiemet_log_manager_start(void) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | /* TODO: choose a better return value */ | 
|  | if (__ondiemet_log_req_working()) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (!__ondiemet_log_req_closed()) { | 
|  | /*ret = down_killable(&log_start_sema);*/ | 
|  | ret = wait_for_completion_killable(&log_start_comp); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | __ondiemet_log_req_open(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /*struct semaphore log_stop_sema;*/ | 
|  | struct completion log_stop_comp; | 
|  | static void __log_stop_cb(const void *p) | 
|  | { | 
|  | /* up(&log_start_sema); */ | 
|  | /* up(&log_stop_sema); */ | 
|  | complete(&log_start_comp); | 
|  | complete(&log_stop_comp); | 
|  | } | 
|  |  | 
|  | int ondiemet_log_manager_stop(void) | 
|  | { | 
|  | int ret; | 
|  | struct ondiemet_log_req *req; | 
|  |  | 
|  | /* TODO: choose a better return value */ | 
|  | if (__ondiemet_log_req_closed()) | 
|  | return -EINVAL; | 
|  |  | 
|  | req = kmalloc(sizeof(*req), GFP_KERNEL); | 
|  |  | 
|  | __ondiemet_log_req_init(req, ONDIEMET_LOG_STOP, NULL, 0, __log_stop_cb, NULL); | 
|  | /*sema_init(&log_start_sema, 0); */ | 
|  | /*sema_init(&log_stop_sema, 0); */ | 
|  | init_completion(&log_start_comp); | 
|  | init_completion(&log_stop_comp); | 
|  |  | 
|  | ret = __ondiemet_log_req_enq(req); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* XXX: blocking may be break by SIGKILL */ | 
|  | /*return down_killable(&log_stop_sema);*/ | 
|  | return wait_for_completion_killable(&log_stop_comp); | 
|  | } | 
|  |  | 
|  | int ondiemet_parse_num(const char *str, unsigned int *value, int len) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | if (len <= 0) | 
|  | return -1; | 
|  |  | 
|  | if ((len > 2) && | 
|  | ((str[0] == '0') && | 
|  | ((str[1] == 'x') || (str[1] == 'X')))) { | 
|  | ret = kstrtouint(str, 16, value); | 
|  | } else { | 
|  | ret = kstrtouint(str, 10, value); | 
|  | } | 
|  |  | 
|  | if (ret != 0) | 
|  | return -1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* XXX: seq_file will output only when a page is filled */ | 
|  | static ssize_t ondiemet_log_write_store(struct device *dev, | 
|  | struct device_attribute *attr, | 
|  | const char *buf, | 
|  | size_t count) | 
|  | { | 
|  | char *plog = NULL; | 
|  |  | 
|  | plog = kmalloc_array(count, sizeof(*plog), GFP_KERNEL); | 
|  | if (!plog) { | 
|  | /* TODO: use a better error code */ | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | memcpy(plog, buf, count); | 
|  |  | 
|  | mutex_lock(&dev->mutex); | 
|  | ondiemet_log_req_enq(plog, strnlen(plog, count), kfree, plog); | 
|  | mutex_unlock(&dev->mutex); | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static DEVICE_ATTR(ondiemet_log_write, 0664, NULL, ondiemet_log_write_store); | 
|  |  | 
|  | static ssize_t ondiemet_log_run_show(struct device *dev, struct device_attribute *attr, char *buf) | 
|  | { | 
|  | int sz; | 
|  |  | 
|  | mutex_lock(&dev->mutex); | 
|  | sz = snprintf(buf, PAGE_SIZE, "%d\n", ondiemet_trace_run); | 
|  | mutex_unlock(&dev->mutex); | 
|  | return sz; | 
|  | } | 
|  |  | 
|  | static ssize_t ondiemet_log_run_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) | 
|  | { | 
|  | int ret; | 
|  | int prev_run_state; | 
|  |  | 
|  | mutex_lock(&dev->mutex); | 
|  |  | 
|  | prev_run_state = ondiemet_trace_run; | 
|  |  | 
|  | if (kstrtoint(buf, 10, &ondiemet_trace_run) != 0) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (ondiemet_trace_run <= ONDIEMET_LOG_STOP_MODE) { | 
|  | ondiemet_trace_run = ONDIEMET_LOG_STOP_MODE; | 
|  | ondiemet_log_manager_stop(); | 
|  |  | 
|  | if (prev_run_state == ONDIEMET_LOG_DEBUG_MODE) | 
|  | device_remove_file(dev, &dev_attr_ondiemet_log_write); | 
|  | } else if (ondiemet_trace_run == ONDIEMET_LOG_RUN_MODE) { | 
|  | ondiemet_trace_run = ONDIEMET_LOG_RUN_MODE; | 
|  | ondiemet_log_manager_start(); | 
|  |  | 
|  | if (prev_run_state == ONDIEMET_LOG_DEBUG_MODE) | 
|  | device_remove_file(dev, &dev_attr_ondiemet_log_write); | 
|  | } else { | 
|  | ondiemet_trace_run = ONDIEMET_LOG_DEBUG_MODE; | 
|  | ondiemet_log_manager_start(); | 
|  |  | 
|  | if (prev_run_state != ONDIEMET_LOG_DEBUG_MODE) { | 
|  | ret = device_create_file(dev, &dev_attr_ondiemet_log_write); | 
|  | if (ret != 0) | 
|  | pr_debug("[met] can not create device node: ondiemet_log_write\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | mutex_unlock(&dev->mutex); | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static DEVICE_ATTR(ondiemet_log_run, 0660, ondiemet_log_run_show, ondiemet_log_run_store); | 
|  |  | 
|  | int ondiemet_log_manager_init(struct device *dev) | 
|  | { | 
|  | int ret; | 
|  | #ifdef ONDIEMET_MOUNT_DEBUGFS | 
|  | struct dentry *d; | 
|  | #else | 
|  | struct proc_dir_entry *d; | 
|  | #endif | 
|  |  | 
|  | mutex_init(&lock_tracef); | 
|  |  | 
|  | __ondiemet_log_req_q_init(&ondiemet_log_req_q); | 
|  |  | 
|  | /*sema_init(&log_start_sema, 0);*/ | 
|  | /*sema_init(&log_stop_sema, 0);*/ | 
|  | init_completion(&log_start_comp); | 
|  | init_completion(&log_stop_comp); | 
|  |  | 
|  | #ifdef ONDIEMET_MOUNT_DEBUGFS | 
|  | dbgfs_met_dir = debugfs_create_dir("ondiemet", NULL); | 
|  | if (!dbgfs_met_dir) { | 
|  | PR_BOOTMSG("[met] can not create debugfs directory: ondiemet\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  | #else | 
|  | procfs_met_dir = proc_mkdir("ondiemet", NULL); | 
|  | if (!procfs_met_dir) { | 
|  | PR_BOOTMSG("[met] can not create proc directory: ondiemet\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | mutex_init(&lock_trace_owner_pid); | 
|  |  | 
|  | #ifdef ONDIEMET_MOUNT_DEBUGFS | 
|  | d = debugfs_create_file("trace", 0600, dbgfs_met_dir, NULL, &ondiemet_trace_fops); | 
|  | if (!d) { | 
|  | PR_BOOTMSG("[met] can not create devide node in debugfs: ondiemet_trace\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  | #else | 
|  | d = proc_create("trace", 0600, procfs_met_dir, &ondiemet_trace_fops); | 
|  | if (!d) { | 
|  | PR_BOOTMSG("[met] can not create devide node in procfs: ondiemet_trace\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | ondiemet_trace_run = __ondiemet_log_req_working(); | 
|  | ret = device_create_file(dev, &dev_attr_ondiemet_log_run); | 
|  | if (ret != 0) { | 
|  | pr_debug("[met] can not create device node: ondiemet_log_run\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ondiemet_log_manager_uninit(struct device *dev) | 
|  | { | 
|  | device_remove_file(dev, &dev_attr_ondiemet_log_run); | 
|  | #ifdef ONDIEMET_MOUNT_DEBUGFS | 
|  | debugfs_remove_recursive(dbgfs_met_dir); | 
|  | #else | 
|  | proc_remove(procfs_met_dir); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } |