// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2015 MediaTek Inc. */ #include #include #include #include #define CREATE_TRACE_POINTS #include /** * Data structures to store tracepoints information */ struct tracepoints_table { const char *name; const char *mod_name; bool module; void *func; struct tracepoint *tp; bool init; }; static unsigned int ufs_count; static unsigned int ccci_count; static unsigned int gpu_count; static unsigned int threshold; static struct proc_dir_entry *dir; void slog(const char *fmt, ...) { struct va_format vaf; va_list args; va_start(args, fmt); vaf.fmt = fmt; vaf.va = &args; trace_slog(&vaf); va_end(args); } static void probe_ufs_mtk_event(void *data, unsigned int type, unsigned int val) { ufs_count++; slog("#$#ufs#@#event#%lu:%lu(0x%x)#%d", type, val, val, ufs_count); if ((threshold > 0) && ((ufs_count % threshold) == 0)) aee_kernel_exception("ufs", "occur:%d, threshold:%d\n", ufs_count, threshold); } static void probe_ccci_event(void *data, char *string, char *sub_string, unsigned int sub_type, unsigned int resv) { ccci_count++; slog("#$#%s#@#%s#%d:%d#%d", string, sub_string, sub_type, resv, ccci_count); } static void probe_gpu_hardstop_event(void *data, char *string, char *sub_string, unsigned int gpu_freq, unsigned int gpu_volt, unsigned int gpu_vsram, unsigned int stack_freq, unsigned int stack_volt, unsigned int stack_vsram) { gpu_count++; slog("#$#%s#@#%s#%d:%d:%d:%d:%d:%d#%d", string, sub_string, gpu_freq, gpu_volt, gpu_vsram, stack_freq, stack_volt, stack_vsram, gpu_count); } static struct tracepoints_table interests[] = { {.name = "ufs_mtk_event", .mod_name = NULL, .module = false, .func = probe_ufs_mtk_event}, {.name = "ccci_event", .mod_name = NULL, .module = false, .func = probe_ccci_event}, {.name = "gpu_hardstop", .mod_name = "mtk_gpufreq_wrapper", .module = true, .func = probe_gpu_hardstop_event}, }; /** * Find the struct tracepoint* associated with a given tracepoint * name. */ static void lookup_tracepoints(struct tracepoint *tp, void *ignore) { int i; for (i = 0; i < ARRAY_SIZE(interests); i++) { if ((interests[i].module == false) && (strcmp(interests[i].name, tp->name) == 0)) { interests[i].tp = tp; tracepoint_probe_register(interests[i].tp, interests[i].func, NULL); interests[i].init = true; } } } static void cleanup(void) { int i; for (i = 0; i < ARRAY_SIZE(interests); i++) { if (interests[i].init) { tracepoint_probe_unregister(interests[i].tp, interests[i].func, NULL); } } } #ifdef MODULE static int slog_module_callback(struct notifier_block *nb, unsigned long val, void *data) { struct module *mod = data; tracepoint_ptr_t *iter, *begin, *end; struct tracepoint *tp; int i; if (val != MODULE_STATE_LIVE) return NOTIFY_DONE; for (i = 0; i < ARRAY_SIZE(interests); i++) { if ((interests[i].module == true) && (interests[i].init == false)) { begin = mod->tracepoints_ptrs; end = mod->tracepoints_ptrs + mod->num_tracepoints; for (iter = begin; iter < end; iter++) { tp = tracepoint_ptr_deref(iter); if (strcmp(interests[i].name, tp->name) == 0) { interests[i].tp = tp; tracepoint_probe_register(interests[i].tp, interests[i].func, NULL); interests[i].init = true; } } } } return NOTIFY_OK; } static struct notifier_block slog_module_nb = { .notifier_call = slog_module_callback, }; #endif static int slog_threshold_proc_show(struct seq_file *m, void *v) { seq_printf(m, "slog threshold: %d\n", threshold); return 0; } static char *_copy_from_user_for_proc(const char __user *buffer, size_t count) { char *buf = (char *)__get_free_page(GFP_USER); if (!buf) return NULL; if (count >= PAGE_SIZE) goto out; if (copy_from_user(buf, buffer, count)) goto out; buf[count] = '\0'; return buf; out: free_page((unsigned long)buf); return NULL; } static ssize_t slog_threshold_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *pos) { unsigned int num; int rc; char *buf = _copy_from_user_for_proc(buffer, count); if (!buf) return -EINVAL; rc = kstrtouint(buf, 10, &num); if (rc < 0) pr_info("transfer string to int error\n"); else threshold = num; free_page((unsigned long)buf); pr_info("set threshold to %d\n", threshold); return count; } #define PROC_FOPS_RW(name) \ static int name ## _proc_open(struct inode *inode, struct file *file) \ { \ return single_open(file, name ## _proc_show, PDE_DATA(inode)); \ } \ static const struct proc_ops name ## _proc_fops = { \ .proc_open = name ## _proc_open, \ .proc_read = seq_read, \ .proc_lseek = seq_lseek, \ .proc_release = single_release, \ .proc_write = name ## _proc_write, \ } #define PROC_ENTRY(name) {__stringify(name), &name ## _proc_fops} PROC_FOPS_RW(slog_threshold); int slog_procfs_init(void) { int i = 0; struct pentry { const char *name; const struct proc_ops *fops; }; const struct pentry entries[] = { PROC_ENTRY(slog_threshold), }; dir = proc_mkdir("slog", NULL); if (!dir) { pr_info("mkdir /proc/slog failed\n"); return -ENOMEM; } for (i = 0; i < ARRAY_SIZE(entries); i++) { if (!proc_create(entries[i].name, 0640, dir, entries[i].fops)) pr_info("create /proc/slog/%s failed\n", entries[i].name); } return 0; } int mtk_slog_init(void) { #ifdef MODULE register_module_notifier(&slog_module_nb); #endif for_each_kernel_tracepoint(lookup_tracepoints, NULL); slog_procfs_init(); return 0; } void mtk_slog_exit(void) { #ifdef MODULE unregister_module_notifier(&slog_module_nb); #endif remove_proc_entry("slog_threshold", dir); cleanup(); }