|  | /* SPDX-License-Identifier: GPL-2.0 */ | 
|  | /* | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License as published by | 
|  | * the Free Software Foundation; either version 2 of the License, or | 
|  | * (at your option) any later version. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | * | 
|  | * Authors: Waiman Long <waiman.long@hpe.com> | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Collect locking event counts | 
|  | */ | 
|  | #include <linux/debugfs.h> | 
|  | #include <linux/sched.h> | 
|  | #include <linux/sched/clock.h> | 
|  | #include <linux/fs.h> | 
|  |  | 
|  | #include "lock_events.h" | 
|  |  | 
|  | #undef  LOCK_EVENT | 
|  | #define LOCK_EVENT(name)	[LOCKEVENT_ ## name] = #name, | 
|  |  | 
|  | #define LOCK_EVENTS_DIR		"lock_event_counts" | 
|  |  | 
|  | /* | 
|  | * When CONFIG_LOCK_EVENT_COUNTS is enabled, event counts of different | 
|  | * types of locks will be reported under the <debugfs>/lock_event_counts/ | 
|  | * directory. See lock_events_list.h for the list of available locking | 
|  | * events. | 
|  | * | 
|  | * Writing to the special ".reset_counts" file will reset all the above | 
|  | * locking event counts. This is a very slow operation and so should not | 
|  | * be done frequently. | 
|  | * | 
|  | * These event counts are implemented as per-cpu variables which are | 
|  | * summed and computed whenever the corresponding debugfs files are read. This | 
|  | * minimizes added overhead making the counts usable even in a production | 
|  | * environment. | 
|  | */ | 
|  | static const char * const lockevent_names[lockevent_num + 1] = { | 
|  |  | 
|  | #include "lock_events_list.h" | 
|  |  | 
|  | [LOCKEVENT_reset_cnts] = ".reset_counts", | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Per-cpu counts | 
|  | */ | 
|  | DEFINE_PER_CPU(unsigned long, lockevents[lockevent_num]); | 
|  |  | 
|  | /* | 
|  | * The lockevent_read() function can be overridden. | 
|  | */ | 
|  | ssize_t __weak lockevent_read(struct file *file, char __user *user_buf, | 
|  | size_t count, loff_t *ppos) | 
|  | { | 
|  | char buf[64]; | 
|  | int cpu, id, len; | 
|  | u64 sum = 0; | 
|  |  | 
|  | /* | 
|  | * Get the counter ID stored in file->f_inode->i_private | 
|  | */ | 
|  | id = (long)file_inode(file)->i_private; | 
|  |  | 
|  | if (id >= lockevent_num) | 
|  | return -EBADF; | 
|  |  | 
|  | for_each_possible_cpu(cpu) | 
|  | sum += per_cpu(lockevents[id], cpu); | 
|  | len = snprintf(buf, sizeof(buf) - 1, "%llu\n", sum); | 
|  |  | 
|  | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Function to handle write request | 
|  | * | 
|  | * When idx = reset_cnts, reset all the counts. | 
|  | */ | 
|  | static ssize_t lockevent_write(struct file *file, const char __user *user_buf, | 
|  | size_t count, loff_t *ppos) | 
|  | { | 
|  | int cpu; | 
|  |  | 
|  | /* | 
|  | * Get the counter ID stored in file->f_inode->i_private | 
|  | */ | 
|  | if ((long)file_inode(file)->i_private != LOCKEVENT_reset_cnts) | 
|  | return count; | 
|  |  | 
|  | for_each_possible_cpu(cpu) { | 
|  | int i; | 
|  | unsigned long *ptr = per_cpu_ptr(lockevents, cpu); | 
|  |  | 
|  | for (i = 0 ; i < lockevent_num; i++) | 
|  | WRITE_ONCE(ptr[i], 0); | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Debugfs data structures | 
|  | */ | 
|  | static const struct file_operations fops_lockevent = { | 
|  | .read = lockevent_read, | 
|  | .write = lockevent_write, | 
|  | .llseek = default_llseek, | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_PARAVIRT_SPINLOCKS | 
|  | #include <asm/paravirt.h> | 
|  |  | 
|  | static bool __init skip_lockevent(const char *name) | 
|  | { | 
|  | static int pv_on __initdata = -1; | 
|  |  | 
|  | if (pv_on < 0) | 
|  | pv_on = !pv_is_native_spin_unlock(); | 
|  | /* | 
|  | * Skip PV qspinlock events on bare metal. | 
|  | */ | 
|  | if (!pv_on && !memcmp(name, "pv_", 3)) | 
|  | return true; | 
|  | return false; | 
|  | } | 
|  | #else | 
|  | static inline bool skip_lockevent(const char *name) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * Initialize debugfs for the locking event counts. | 
|  | */ | 
|  | static int __init init_lockevent_counts(void) | 
|  | { | 
|  | struct dentry *d_counts = debugfs_create_dir(LOCK_EVENTS_DIR, NULL); | 
|  | int i; | 
|  |  | 
|  | if (IS_ERR(d_counts)) | 
|  | goto out; | 
|  |  | 
|  | /* | 
|  | * Create the debugfs files | 
|  | * | 
|  | * As reading from and writing to the stat files can be slow, only | 
|  | * root is allowed to do the read/write to limit impact to system | 
|  | * performance. | 
|  | */ | 
|  | for (i = 0; i < lockevent_num; i++) { | 
|  | if (skip_lockevent(lockevent_names[i])) | 
|  | continue; | 
|  | if (IS_ERR(debugfs_create_file(lockevent_names[i], 0400, d_counts, | 
|  | (void *)(long)i, &fops_lockevent))) | 
|  | goto fail_undo; | 
|  | } | 
|  |  | 
|  | if (IS_ERR(debugfs_create_file(lockevent_names[LOCKEVENT_reset_cnts], 0200, | 
|  | d_counts, (void *)(long)LOCKEVENT_reset_cnts, | 
|  | &fops_lockevent))) | 
|  | goto fail_undo; | 
|  |  | 
|  | return 0; | 
|  | fail_undo: | 
|  | debugfs_remove_recursive(d_counts); | 
|  | out: | 
|  | pr_warn("Could not create '%s' debugfs entries\n", LOCK_EVENTS_DIR); | 
|  | return -ENOMEM; | 
|  | } | 
|  | fs_initcall(init_lockevent_counts); |