| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2019 MediaTek Inc. | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/proc_fs.h> | |
| #include <linux/sched.h> | |
| #include <linux/seq_file.h> | |
| #include <linux/slab.h> | |
| #include <linux/tracepoint.h> | |
| #include <linux/uaccess.h> | |
| #include <mt-plat/mtk_pidmap.h> | |
| /* | |
| * The best way favor performance to know task name is | |
| * keeping pid and task name mapping in a one-way table. | |
| * | |
| * Other options will suffer performance, for example, | |
| * using find_task_by_vpid() w/ rcu read lock, or | |
| * maintaining mapping in an rb-tree. | |
| * | |
| * Besides, static memory is chosen for pid map because | |
| * static memory layout is required for some exception | |
| * handling flow, e.g., hwt or hw reboot. | |
| */ | |
| static char *mtk_pidmap; | |
| static int mtk_pidmap_proc_dump_mode; | |
| static int mtk_pidmap_max_pid; | |
| static char mtk_pidmap_proc_cmd_buf[PIDMAP_PROC_CMD_BUF_SIZE]; | |
| static struct proc_dir_entry *mtk_pidmap_proc_entry; | |
| /** | |
| * Data structures to store tracepoints information | |
| */ | |
| struct tracepoints_table { | |
| const char *name; | |
| void *func; | |
| struct tracepoint *tp; | |
| bool init; | |
| }; | |
| /* | |
| * mtk_pidmap_update | |
| * insert or update new mapping between pid and task name. | |
| * | |
| * task: current task | |
| */ | |
| static void mtk_pidmap_update(struct task_struct *task) | |
| { | |
| char *name; | |
| int len; | |
| pid_t pid; | |
| pid = task->pid; | |
| if (unlikely((pid < 1) || (pid > mtk_pidmap_max_pid))) | |
| return; | |
| /* | |
| * part 1, get current task's name. | |
| * | |
| * this could be lockless because the specific offset | |
| * will be updated by its task only. | |
| */ | |
| name = mtk_pidmap + ((pid - 1) * PIDMAP_ENTRY_SIZE); | |
| /* copy task name */ | |
| memcpy(name, task->comm, PIDMAP_TASKNAME_SIZE); | |
| *(name + PIDMAP_TASKNAME_SIZE) = '\0'; | |
| /* clear garbage tail chars to help parsers */ | |
| len = strlen(name); | |
| if (len > PIDMAP_TASKNAME_SIZE) | |
| len = PIDMAP_TASKNAME_SIZE; | |
| memset(name + len, 0, PIDMAP_TASKNAME_SIZE - len); | |
| /* part 2, copy thread group ID as hex format */ | |
| name += PIDMAP_TASKNAME_SIZE; | |
| *(name + 0) = (char)(task->tgid & 0xFF); | |
| *(name + 1) = (char)(task->tgid >> 8); | |
| } | |
| static void probe_task_rename(void *data, struct task_struct *task, | |
| const char *comm) | |
| { | |
| mtk_pidmap_update(task); | |
| } | |
| static void probe_task_newtask(void *data, struct task_struct *task, | |
| unsigned long clone_flags) | |
| { | |
| mtk_pidmap_update(task); | |
| } | |
| static struct tracepoints_table interests[] = { | |
| {.name = "task_rename", .func = probe_task_rename}, | |
| {.name = "task_newtask", .func = probe_task_newtask} | |
| }; | |
| #define FOR_EACH_INTEREST(i) \ | |
| for (i = 0; i < sizeof(interests) / \ | |
| sizeof(struct tracepoints_table); i++) | |
| /** | |
| * Find the struct tracepoint* associated with a given tracepoint | |
| * name. | |
| */ | |
| static void lookup_tracepoints(struct tracepoint *tp, void *ignore) | |
| { | |
| int i; | |
| FOR_EACH_INTEREST(i) { | |
| if (strcmp(interests[i].name, tp->name) == 0) | |
| interests[i].tp = tp; | |
| } | |
| } | |
| static void mtk_pidmap_deinit(void) | |
| { | |
| int i; | |
| FOR_EACH_INTEREST(i) { | |
| if (interests[i].init) { | |
| tracepoint_probe_unregister(interests[i].tp, | |
| interests[i].func, NULL); | |
| } | |
| } | |
| } | |
| static int mtk_pidmap_init_map(void) | |
| { | |
| int i; | |
| mtk_pidmap = kzalloc(PIDMAP_AEE_BUF_SIZE, GFP_KERNEL); | |
| if (!mtk_pidmap) | |
| return -ENOMEM; | |
| /* Install the tracepoints */ | |
| for_each_kernel_tracepoint(lookup_tracepoints, NULL); | |
| FOR_EACH_INTEREST(i) { | |
| if (interests[i].tp == NULL) { | |
| pr_info("Error: %s not found\n", interests[i].name); | |
| /* Unload previously loaded */ | |
| mtk_pidmap_deinit(); | |
| return -EINVAL; | |
| } | |
| tracepoint_probe_register(interests[i].tp, interests[i].func, | |
| NULL); | |
| interests[i].init = true; | |
| } | |
| /* | |
| * now pidmap is designed for keeping | |
| * maximum 32768 pids in 512 KB buffer. | |
| */ | |
| mtk_pidmap_max_pid = | |
| PIDMAP_AEE_BUF_SIZE / PIDMAP_ENTRY_SIZE; | |
| return 0; | |
| } | |
| static void mtk_pidmap_seq_dump_readable(char **buff, unsigned long *size, | |
| struct seq_file *seq) | |
| { | |
| int i, active_pid; | |
| pid_t tgid; | |
| char *name; | |
| char name_tmp[TASK_COMM_LEN + 1] = {0}; | |
| seq_puts(seq, "<PID Map>\n"); | |
| seq_puts(seq, "PID\tTGID\tTask Name\n"); | |
| /* | |
| * pid map shall be protected for dump, however | |
| * we ignore locking here to favor performance. | |
| */ | |
| active_pid = 0; | |
| for (i = 0; i < PIDMAP_ENTRY_CNT; i++) { | |
| name = &mtk_pidmap[i * PIDMAP_ENTRY_SIZE]; | |
| if (name[0]) { | |
| /* get task name */ | |
| memset(name_tmp, 0, PIDMAP_TASKNAME_SIZE); | |
| memcpy(name_tmp, name, PIDMAP_TASKNAME_SIZE); | |
| /* get tgid */ | |
| name += PIDMAP_TASKNAME_SIZE; | |
| tgid = (pid_t)name[0] + | |
| ((pid_t)name[1] << 8); | |
| seq_printf(seq, "%d\t%d\t%s\n", | |
| i + 1, tgid, name_tmp); | |
| active_pid++; | |
| } | |
| } | |
| seq_puts(seq, "\n<Information>\n"); | |
| seq_printf(seq, "Total PIDs: %d\n", active_pid); | |
| seq_printf(seq, "Entry size: %d bytes\n", | |
| PIDMAP_ENTRY_SIZE); | |
| seq_printf(seq, " - Task name size: %d bytes\n", | |
| (int)PIDMAP_TASKNAME_SIZE); | |
| seq_printf(seq, " - TGID size: %d bytes\n", | |
| (int)PIDMAP_TGID_SIZE); | |
| seq_printf(seq, "Total Buffer Size: %d bytes\n", | |
| PIDMAP_AEE_BUF_SIZE + PIDMAP_PROC_CMD_BUF_SIZE); | |
| seq_printf(seq, "mtk_pidmap address: 0x%p\n", | |
| &mtk_pidmap[0]); | |
| seq_puts(seq, "\n<Configuration>\n"); | |
| seq_puts(seq, "echo 0 > pidmap: Dump raw pidmap (default, for AEE DB)\n"); | |
| seq_puts(seq, "echo 1 > pidmap: Dump readable pidmap\n"); | |
| seq_puts(seq, "echo 2 > pidmap: Reset pidmap\n"); | |
| } | |
| static void mtk_pidmap_seq_dump_raw(struct seq_file *seq) | |
| { | |
| int i; | |
| /* | |
| * pid map shall be protected for dump, however | |
| * we ignore locking here to favor performance. | |
| * | |
| * Notice: be aware that you shall NOT read this seq_file | |
| * in Windows environment because Windows will automatically | |
| * add Carriage Return 0Dh ('\r') while you output 0Ah ('\n') | |
| * unexpectedly. | |
| * | |
| * 0Ah may be existed for TGID, and you'll get an incorrect | |
| * PID map outputs. | |
| */ | |
| for (i = 0; i < PIDMAP_AEE_BUF_SIZE; i++) | |
| seq_putc(seq, mtk_pidmap[i]); | |
| } | |
| static int mtk_pidmap_seq_show(struct seq_file *seq, void *v) | |
| { | |
| if (mtk_pidmap_proc_dump_mode == PIDMAP_PROC_DUMP_READABLE) | |
| mtk_pidmap_seq_dump_readable(NULL, NULL, seq); | |
| else if (mtk_pidmap_proc_dump_mode == PIDMAP_PROC_DUMP_RAW) | |
| mtk_pidmap_seq_dump_raw(seq); | |
| return 0; | |
| } | |
| void get_pidmap_aee_buffer(unsigned long *vaddr, unsigned long *size) | |
| { | |
| /* retrun start location */ | |
| if (vaddr) | |
| *vaddr = (unsigned long)mtk_pidmap; | |
| /* return valid buffer size */ | |
| if (size) | |
| *size = PIDMAP_AEE_BUF_SIZE; | |
| } | |
| EXPORT_SYMBOL(get_pidmap_aee_buffer); | |
| static ssize_t mtk_pidmap_proc_write(struct file *file, const char *buf, | |
| size_t count, loff_t *data) | |
| { | |
| int ret; | |
| if (count == 0) | |
| goto err; | |
| else if (count > PIDMAP_PROC_CMD_BUF_SIZE) | |
| count = PIDMAP_PROC_CMD_BUF_SIZE; | |
| ret = copy_from_user(mtk_pidmap_proc_cmd_buf, buf, count); | |
| if (ret < 0) | |
| goto err; | |
| if (mtk_pidmap_proc_cmd_buf[0] == '0') { | |
| mtk_pidmap_proc_dump_mode = PIDMAP_PROC_DUMP_RAW; | |
| pr_info("[pidmap] dump mode: raw\n"); | |
| } else if (mtk_pidmap_proc_cmd_buf[0] == '1') { | |
| mtk_pidmap_proc_dump_mode = PIDMAP_PROC_DUMP_READABLE; | |
| pr_info("[pidmap] dump mode: readable\n"); | |
| } else if (mtk_pidmap_proc_cmd_buf[0] == '2') { | |
| memset(mtk_pidmap, 0, PIDMAP_AEE_BUF_SIZE); | |
| pr_info("[pidmap] reset pidmap\n"); | |
| } else | |
| goto err; | |
| goto out; | |
| err: | |
| pr_info("[pidmap] invalid arg: 0x%x\n", mtk_pidmap_proc_cmd_buf[0]); | |
| return -1; | |
| out: | |
| return count; | |
| } | |
| static int mtk_pidmap_proc_open(struct inode *inode, struct file *file) | |
| { | |
| return single_open(file, mtk_pidmap_seq_show, inode->i_private); | |
| } | |
| static const struct proc_ops mtk_pidmap_proc_fops = { | |
| .proc_open = mtk_pidmap_proc_open, | |
| .proc_write = mtk_pidmap_proc_write, | |
| .proc_read = seq_read, | |
| .proc_lseek = seq_lseek, | |
| .proc_release = single_release, | |
| }; | |
| static int mtk_pidmap_proc_init(void) | |
| { | |
| kuid_t uid; | |
| kgid_t gid; | |
| uid = make_kuid(&init_user_ns, 0); | |
| gid = make_kgid(&init_user_ns, 1001); | |
| mtk_pidmap_proc_entry = proc_create("pidmap", | |
| PIDMAP_PROC_PERM, NULL, | |
| &mtk_pidmap_proc_fops); | |
| if (mtk_pidmap_proc_entry) | |
| proc_set_user(mtk_pidmap_proc_entry, uid, gid); | |
| else | |
| pr_info("[pidmap] failed to create /proc/pidmap\n"); | |
| return 0; | |
| } | |
| static int __init mtk_pidmap_init(void) | |
| { | |
| int ret; | |
| ret = mtk_pidmap_init_map(); | |
| if (ret) | |
| return ret; | |
| ret = mtk_pidmap_proc_init(); | |
| return ret; | |
| } | |
| static void __exit mtk_pidmap_exit(void) | |
| { | |
| proc_remove(mtk_pidmap_proc_entry); | |
| } | |
| /* | |
| * TODO: The timing of loadable module is too late to have full | |
| * list of kernel threads. Need to find out solution. | |
| */ | |
| early_initcall(mtk_pidmap_init); | |
| module_exit(mtk_pidmap_exit); | |
| MODULE_AUTHOR("Stanley Chu <stanley.chu@mediatek.com>"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_DESCRIPTION("PID Map"); | |