// SPDX-License-Identifier: GPL-2.0 /* * Provide an interface /dev/tinypmu_register and /dev/tinypmu_unregister to * stress test perf_pmu_register() and perf_pmu_unregister() functions. * * Author: Ravi Bangoria */ #define pr_fmt(fmt) "tinypmu: " fmt #include #include #include #include #define NR_TINYPMUS 1 static int tinypmu_event_init(struct perf_event *event) { return 0; } static void tinypmu_del(struct perf_event *event, int flags) { } static int tinypmu_add(struct perf_event *event, int flags) { return 0; } static void tinypmu_start(struct perf_event *event, int flags) { } static void tinypmu_stop(struct perf_event *event, int flags) { } static void tinypmu_read(struct perf_event *event) { } PMU_FORMAT_ATTR(event, "config:0-20"); static struct attribute *tinypmu_events_attr[] = { &format_attr_event.attr, NULL, }; static struct attribute_group tinypmu_events_group = { .name = "format", .attrs = tinypmu_events_attr, }; static const struct attribute_group *tinypmu_attr_groups[] = { &tinypmu_events_group, NULL, }; static struct pmu *alloc_tinypmu(void) { struct pmu *tinypmu = kzalloc(sizeof(struct pmu), GFP_KERNEL); if (!tinypmu) return NULL; tinypmu->task_ctx_nr = perf_invalid_context; tinypmu->event_init = tinypmu_event_init; tinypmu->add = tinypmu_add; tinypmu->del = tinypmu_del; tinypmu->start = tinypmu_start; tinypmu->stop = tinypmu_stop; tinypmu->read = tinypmu_read; tinypmu->attr_groups = tinypmu_attr_groups; return tinypmu; } static DEFINE_MUTEX(lock); static struct pmu *tinypmus[NR_TINYPMUS]; static char pmu_name[NR_TINYPMUS][11]; static void register_pmu(unsigned int idx) { struct pmu *temp; int ret; if (idx >= NR_TINYPMUS) return; temp = alloc_tinypmu(); if (!temp) { mutex_unlock(&lock); return; } mutex_lock(&lock); if (tinypmus[idx]) { mutex_unlock(&lock); kfree(temp); return; } ret = perf_pmu_register(temp, pmu_name[idx], -1); if (!ret) { tinypmus[idx] = temp; mutex_unlock(&lock); return; } mutex_unlock(&lock); kfree(temp); return; } static void unregister_pmu(unsigned int idx) { struct pmu *temp; if (idx >= NR_TINYPMUS) return; mutex_lock(&lock); if (!tinypmus[idx]) { mutex_unlock(&lock); return; } /* * Must call perf_pmu_unregister() inside atomic section. If I try * to reduce atomic section by using temp to cache tinypmus[idx] * plus clear tinypmus[idx] and do unregister after mutex_unlock(), * register_pmu() will assume no pmu exists with "tinypmu" name * since tinypmus[idx] is NULL and try to register new pmu although * this function is yet to unregistered pmu with the same name. */ perf_pmu_unregister(tinypmus[idx]); temp = tinypmus[idx]; tinypmus[idx] = NULL; mutex_unlock(&lock); kfree(temp); } static ssize_t register_write(struct file *f, const char *data, size_t size, loff_t *ppos) { unsigned int idx; get_random_bytes(&idx, sizeof(idx)); idx %= NR_TINYPMUS; register_pmu(idx); return 1; } static ssize_t unregister_write(struct file *f, const char *data, size_t size, loff_t *ppos) { unsigned int idx; get_random_bytes(&idx, sizeof(idx)); idx %= NR_TINYPMUS; unregister_pmu(idx); return 1; } static const struct file_operations register_fops = { .owner = THIS_MODULE, .write = register_write, }; static const struct file_operations unregister_fops = { .owner = THIS_MODULE, .write = unregister_write, }; static struct miscdevice register_dev = { MISC_DYNAMIC_MINOR, "tinypmu_register", ®ister_fops, }; static struct miscdevice unregister_dev = { MISC_DYNAMIC_MINOR, "tinypmu_unregister", &unregister_fops, }; static int __init hello_init(void) { int ret; int i; ret = misc_register(®ister_dev); if (ret) { pr_err("Failed to register register_dev\n"); return ret; } ret = misc_register(&unregister_dev); if (ret) { pr_err("Failed to register unregister_dev\n"); misc_deregister(®ister_dev); return ret; } for (i = 0; i < NR_TINYPMUS; i++) sprintf(pmu_name[i], "tinypmu%d", i); for (i = 0; i < NR_TINYPMUS; i++) register_pmu(i); return 0; } module_init(hello_init); static void __exit hello_exit(void) { int i; for (i = 0; i < NR_TINYPMUS; i++) unregister_pmu(i); misc_deregister(®ister_dev); misc_deregister(&unregister_dev); } module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ravi Bangoria"); MODULE_DESCRIPTION("PMU register/unregister stress test"); MODULE_VERSION("dev");