/* SPDX-License-Identifier: GPL-2.0 */1/*2* This program is free software; you can redistribute it and/or modify3* it under the terms of the GNU General Public License as published by4* the Free Software Foundation; either version 2 of the License, or5* (at your option) any later version.6*7* This program is distributed in the hope that it will be useful,8* but WITHOUT ANY WARRANTY; without even the implied warranty of9* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10* GNU General Public License for more details.11*12* Authors: Waiman Long <[email protected]>13*/1415/*16* Collect locking event counts17*/18#include <linux/debugfs.h>19#include <linux/sched.h>20#include <linux/sched/clock.h>21#include <linux/fs.h>2223#include "lock_events.h"2425#undef LOCK_EVENT26#define LOCK_EVENT(name) [LOCKEVENT_ ## name] = #name,2728#define LOCK_EVENTS_DIR "lock_event_counts"2930/*31* When CONFIG_LOCK_EVENT_COUNTS is enabled, event counts of different32* types of locks will be reported under the <debugfs>/lock_event_counts/33* directory. See lock_events_list.h for the list of available locking34* events.35*36* Writing to the special ".reset_counts" file will reset all the above37* locking event counts. This is a very slow operation and so should not38* be done frequently.39*40* These event counts are implemented as per-cpu variables which are41* summed and computed whenever the corresponding debugfs files are read. This42* minimizes added overhead making the counts usable even in a production43* environment.44*/45static const char * const lockevent_names[lockevent_num + 1] = {4647#include "lock_events_list.h"4849[LOCKEVENT_reset_cnts] = ".reset_counts",50};5152/*53* Per-cpu counts54*/55DEFINE_PER_CPU(unsigned long, lockevents[lockevent_num]);5657/*58* The lockevent_read() function can be overridden.59*/60ssize_t __weak lockevent_read(struct file *file, char __user *user_buf,61size_t count, loff_t *ppos)62{63char buf[64];64int cpu, id, len;65u64 sum = 0;6667/*68* Get the counter ID stored in file->f_inode->i_private69*/70id = (long)file_inode(file)->i_private;7172if (id >= lockevent_num)73return -EBADF;7475for_each_possible_cpu(cpu)76sum += per_cpu(lockevents[id], cpu);77len = snprintf(buf, sizeof(buf) - 1, "%llu\n", sum);7879return simple_read_from_buffer(user_buf, count, ppos, buf, len);80}8182/*83* Function to handle write request84*85* When idx = reset_cnts, reset all the counts.86*/87static ssize_t lockevent_write(struct file *file, const char __user *user_buf,88size_t count, loff_t *ppos)89{90int cpu;9192/*93* Get the counter ID stored in file->f_inode->i_private94*/95if ((long)file_inode(file)->i_private != LOCKEVENT_reset_cnts)96return count;9798for_each_possible_cpu(cpu) {99int i;100unsigned long *ptr = per_cpu_ptr(lockevents, cpu);101102for (i = 0 ; i < lockevent_num; i++)103WRITE_ONCE(ptr[i], 0);104}105return count;106}107108/*109* Debugfs data structures110*/111static const struct file_operations fops_lockevent = {112.read = lockevent_read,113.write = lockevent_write,114.llseek = default_llseek,115};116117#ifdef CONFIG_PARAVIRT_SPINLOCKS118#include <asm/paravirt.h>119120static bool __init skip_lockevent(const char *name)121{122static int pv_on __initdata = -1;123124if (pv_on < 0)125pv_on = !pv_is_native_spin_unlock();126/*127* Skip PV qspinlock events on bare metal.128*/129if (!pv_on && !memcmp(name, "pv_", 3))130return true;131return false;132}133#else134static inline bool skip_lockevent(const char *name)135{136return false;137}138#endif139140/*141* Initialize debugfs for the locking event counts.142*/143static int __init init_lockevent_counts(void)144{145struct dentry *d_counts = debugfs_create_dir(LOCK_EVENTS_DIR, NULL);146int i;147148if (IS_ERR(d_counts))149goto out;150151/*152* Create the debugfs files153*154* As reading from and writing to the stat files can be slow, only155* root is allowed to do the read/write to limit impact to system156* performance.157*/158for (i = 0; i < lockevent_num; i++) {159if (skip_lockevent(lockevent_names[i]))160continue;161if (IS_ERR(debugfs_create_file(lockevent_names[i], 0400, d_counts,162(void *)(long)i, &fops_lockevent)))163goto fail_undo;164}165166if (IS_ERR(debugfs_create_file(lockevent_names[LOCKEVENT_reset_cnts], 0200,167d_counts, (void *)(long)LOCKEVENT_reset_cnts,168&fops_lockevent)))169goto fail_undo;170171return 0;172fail_undo:173debugfs_remove_recursive(d_counts);174out:175pr_warn("Could not create '%s' debugfs entries\n", LOCK_EVENTS_DIR);176return -ENOMEM;177}178fs_initcall(init_lockevent_counts);179180181