[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251016144656.74928-7-yonch@yonch.com>
Date: Thu, 16 Oct 2025 09:46:54 -0500
From: Jonathan Perry <yonch@...ch.com>
To: Tony Luck <tony.luck@...el.com>,
Reinette Chatre <reinette.chatre@...el.com>,
linux-kernel@...r.kernel.org
Cc: linux-kselftest@...r.kernel.org,
linux-doc@...r.kernel.org,
Jonathan Corbet <corbet@....net>,
James Morse <james.morse@....com>,
Roman Storozhenko <romeusmeister@...il.com>,
Jonathan Perry <yonch@...ch.com>
Subject: [PATCH 6/8] resctrl/pmu: Introduce skeleton PMU and selftests
Register a read-only "resctrl" PMU and implement minimal perf hooks
(event_init, add, del, start, stop, read, destroy). The PMU accepts a
resctrl monitoring file descriptor via attr.config, resolves the
rdtgroup, and pins it for the event's lifetime.
Call PMU init/exit in resctrl_init()/resctrl_exit().
Add a selftest to exercise PMU registration and verify that only
allowed monitoring files can be opened via perf.
Signed-off-by: Jonathan Perry <yonch@...ch.com>
---
fs/resctrl/Makefile | 2 +-
fs/resctrl/internal.h | 12 ++
fs/resctrl/pmu.c | 139 ++++++++++++
fs/resctrl/rdtgroup.c | 53 +++++
tools/testing/selftests/resctrl/pmu_test.c | 202 ++++++++++++++++++
tools/testing/selftests/resctrl/resctrl.h | 1 +
.../testing/selftests/resctrl/resctrl_tests.c | 1 +
7 files changed, 409 insertions(+), 1 deletion(-)
create mode 100644 fs/resctrl/pmu.c
create mode 100644 tools/testing/selftests/resctrl/pmu_test.c
diff --git a/fs/resctrl/Makefile b/fs/resctrl/Makefile
index e67f34d2236a..f738b0165ccc 100644
--- a/fs/resctrl/Makefile
+++ b/fs/resctrl/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_RESCTRL_FS) += rdtgroup.o ctrlmondata.o monitor.o
+obj-$(CONFIG_RESCTRL_FS) += rdtgroup.o ctrlmondata.o monitor.o pmu.o
obj-$(CONFIG_RESCTRL_FS_PSEUDO_LOCK) += pseudo_lock.o
# To allow define_trace.h's recursive include:
diff --git a/fs/resctrl/internal.h b/fs/resctrl/internal.h
index 486cbca8d0ec..b42c625569a8 100644
--- a/fs/resctrl/internal.h
+++ b/fs/resctrl/internal.h
@@ -4,6 +4,7 @@
#include <linux/resctrl.h>
#include <linux/kernfs.h>
+#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/tick.h>
@@ -362,6 +363,17 @@ void mon_event_count(void *info);
int rdtgroup_mondata_show(struct seq_file *m, void *arg);
int rdtgroup_mondata_open(struct kernfs_open_file *of);
void rdtgroup_mondata_release(struct kernfs_open_file *of);
+void rdtgroup_get(struct rdtgroup *rdtgrp);
+void rdtgroup_put(struct rdtgroup *rdtgrp);
+
+/* PMU support */
+/*
+ * Get rdtgroup from a resctrl monitoring file and take a reference.
+ * Returns a valid pointer with an extra reference on success, or ERR_PTR on failure.
+ */
+struct rdtgroup *rdtgroup_get_from_file(struct file *file);
+int resctrl_pmu_init(void);
+void resctrl_pmu_exit(void);
void rmid_read_init(struct rmid_read *rr, struct rdt_resource *r,
struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
diff --git a/fs/resctrl/pmu.c b/fs/resctrl/pmu.c
new file mode 100644
index 000000000000..e7915a0a3520
--- /dev/null
+++ b/fs/resctrl/pmu.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Perf event access to resctrl monitoring (cache occupancy, memory bandwidth)
+ */
+
+#define pr_fmt(fmt) "resctrl_pmu: " fmt
+
+#include <linux/kernel.h>
+#include <linux/perf_event.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/seq_file.h>
+#include "internal.h"
+
+static struct pmu resctrl_pmu;
+
+/*
+ * Event private data - stores information about the monitored resctrl group
+ */
+struct resctrl_pmu_event {
+ struct rdtgroup *rdtgrp; /* Reference to rdtgroup being monitored */
+};
+
+static void resctrl_event_destroy(struct perf_event *event);
+
+/*
+ * Initialize a new resctrl perf event
+ * The config field contains the file descriptor of the monitoring file
+ */
+static int resctrl_event_init(struct perf_event *event)
+{
+ struct resctrl_pmu_event *resctrl_event;
+ struct file *file;
+ struct rdtgroup *rdtgrp;
+ int fd;
+ int ret;
+
+ fd = (int)event->attr.config;
+ if (fd < 0)
+ return -EINVAL;
+
+ file = fget(fd);
+ if (!file)
+ return -EBADF;
+
+ /* Resolve rdtgroup from the monitoring file and take a reference */
+ rdtgrp = rdtgroup_get_from_file(file);
+ fput(file);
+ if (IS_ERR(rdtgrp))
+ return PTR_ERR(rdtgrp);
+
+ resctrl_event = kzalloc(sizeof(*resctrl_event), GFP_KERNEL);
+ if (!resctrl_event) {
+ rdtgroup_put(rdtgrp);
+ return -ENOMEM;
+ }
+
+ resctrl_event->rdtgrp = rdtgrp;
+ event->pmu_private = resctrl_event;
+ event->destroy = resctrl_event_destroy;
+
+ return 0;
+}
+
+static void resctrl_event_destroy(struct perf_event *event)
+{
+ struct resctrl_pmu_event *resctrl_event = event->pmu_private;
+
+ if (resctrl_event) {
+ struct rdtgroup *rdtgrp = resctrl_event->rdtgrp;
+
+ if (rdtgrp)
+ rdtgroup_put(rdtgrp);
+
+ kfree(resctrl_event);
+ event->pmu_private = NULL;
+ }
+}
+
+static void resctrl_event_update(struct perf_event *event)
+{
+ /* Currently just a stub - would read actual cache occupancy here */
+ local64_set(&event->hw.prev_count, 0);
+}
+
+static void resctrl_event_start(struct perf_event *event, int flags)
+{
+ resctrl_event_update(event);
+}
+
+static void resctrl_event_stop(struct perf_event *event, int flags)
+{
+ if (flags & PERF_EF_UPDATE)
+ resctrl_event_update(event);
+}
+
+static int resctrl_event_add(struct perf_event *event, int flags)
+{
+ if (flags & PERF_EF_START)
+ resctrl_event_start(event, flags);
+
+ return 0;
+}
+
+static void resctrl_event_del(struct perf_event *event, int flags)
+{
+ resctrl_event_stop(event, PERF_EF_UPDATE);
+}
+
+static struct pmu resctrl_pmu = {
+ .task_ctx_nr = perf_invalid_context,
+ .event_init = resctrl_event_init,
+ .add = resctrl_event_add,
+ .del = resctrl_event_del,
+ .start = resctrl_event_start,
+ .stop = resctrl_event_stop,
+ .read = resctrl_event_update,
+ .capabilities = PERF_PMU_CAP_NO_INTERRUPT | PERF_PMU_CAP_NO_EXCLUDE,
+};
+
+int resctrl_pmu_init(void)
+{
+ int ret;
+
+ ret = perf_pmu_register(&resctrl_pmu, "resctrl", -1);
+ if (ret) {
+ pr_err("Failed to register resctrl PMU: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+void resctrl_pmu_exit(void)
+{
+ perf_pmu_unregister(&resctrl_pmu);
+}
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 34337abe5345..4f4139edafbf 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -3428,6 +3428,53 @@ void rdtgroup_mondata_release(struct kernfs_open_file *of)
}
}
+/*
+ * rdtgroup_get_from_file - Resolve rdtgroup from a resctrl mon data file
+ * @file: struct file opened on a resctrl monitoring data file
+ *
+ * Validate that @file belongs to resctrl and refers to a monitoring data
+ * file (kf_mondata_ops). Then, using the kernfs_open_file stored in the
+ * seq_file, safely fetch the rdtgroup that was pinned at open time and take
+ * an additional rdtgroup reference for the caller under rdtgroup_mutex.
+ *
+ * Returns: rdtgroup* with an extra reference on success; ERR_PTR on failure.
+ */
+struct rdtgroup *rdtgroup_get_from_file(struct file *file)
+{
+ struct rdtgroup *rdtgrp = NULL;
+ struct kernfs_open_file *of;
+ struct seq_file *seq;
+ struct inode *inode;
+
+ if (!file)
+ return ERR_PTR(-EBADF);
+
+ inode = file_inode(file);
+ /* Check the file is part of the resctrl filesystem */
+ if (!inode || !inode->i_sb || inode->i_sb->s_type != &rdt_fs_type)
+ return ERR_PTR(-EINVAL);
+
+ /* kernfs monitoring files use seq_file; seq_file->private is kernfs_open_file */
+ seq = (struct seq_file *)file->private_data;
+ if (!seq)
+ return ERR_PTR(-EINVAL);
+
+ of = (struct kernfs_open_file *)seq->private;
+ /* Check this is a monitoring file */
+ if (!of || !of->kn || of->kn->attr.ops != &kf_mondata_ops)
+ return ERR_PTR(-EINVAL);
+
+ /* Hold rdtgroup_mutex to prevent race with release callback */
+ guard(mutex)(&rdtgroup_mutex);
+
+ rdtgrp = of->priv;
+ if (!rdtgrp || (rdtgrp->flags & RDT_DELETED))
+ return ERR_PTR(-ENOENT);
+
+ rdtgroup_get(rdtgrp);
+ return rdtgrp;
+}
+
/**
* cbm_ensure_valid - Enforce validity on provided CBM
* @_val: Candidate CBM
@@ -4509,6 +4556,10 @@ int resctrl_init(void)
*/
debugfs_resctrl = debugfs_create_dir("resctrl", NULL);
+ ret = resctrl_pmu_init();
+ if (ret)
+ pr_warn("Failed to initialize resctrl PMU: %d\n", ret);
+
return 0;
cleanup_mountpoint:
@@ -4558,6 +4609,8 @@ static bool resctrl_online_domains_exist(void)
*/
void resctrl_exit(void)
{
+ resctrl_pmu_exit();
+
cpus_read_lock();
WARN_ON_ONCE(resctrl_online_domains_exist());
diff --git a/tools/testing/selftests/resctrl/pmu_test.c b/tools/testing/selftests/resctrl/pmu_test.c
new file mode 100644
index 000000000000..29a0ac329619
--- /dev/null
+++ b/tools/testing/selftests/resctrl/pmu_test.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Resctrl PMU test
+ *
+ * Test program to verify the resctrl PMU functionality.
+ * Walks resctrl filesystem and verifies only allowed files can be
+ * used with the resctrl PMU via perf_event_open.
+ */
+
+#include "resctrl.h"
+#include <fcntl.h>
+#include <dirent.h>
+
+#define RESCTRL_PMU_NAME "resctrl"
+
+static int find_pmu_type(const char *pmu_name)
+{
+ char path[256];
+ FILE *file;
+ int type;
+
+ snprintf(path, sizeof(path), "/sys/bus/event_source/devices/%s/type",
+ pmu_name);
+
+ file = fopen(path, "r");
+ if (!file) {
+ ksft_print_msg("Failed to open %s: %s\n", path,
+ strerror(errno));
+ return -1;
+ }
+
+ if (fscanf(file, "%d", &type) != 1) {
+ ksft_print_msg("Failed to read PMU type from %s\n", path);
+ fclose(file);
+ return -1;
+ }
+
+ fclose(file);
+ return type;
+}
+
+static bool is_allowed_file(const char *filename)
+{
+ const char *base;
+
+ /* Only exact llc_occupancy and mbm files (no *_config) are allowed */
+ base = strrchr(filename, '/');
+ base = base ? base + 1 : filename;
+
+ return (!strcmp(base, "llc_occupancy") ||
+ !strcmp(base, "mbm_total_bytes") ||
+ !strcmp(base, "mbm_local_bytes"));
+}
+
+static int test_file_safety(int pmu_type, const char *filepath)
+{
+ struct perf_event_attr pe = { 0 };
+ int fd, perf_fd;
+ bool should_succeed;
+
+ /* Try to open the file */
+ fd = open(filepath, O_RDONLY);
+ if (fd < 0) {
+ /* File couldn't be opened, skip it */
+ return 0;
+ }
+
+ should_succeed = is_allowed_file(filepath);
+
+ /* Setup perf event attributes */
+ pe.type = pmu_type;
+ pe.config = fd;
+ pe.size = sizeof(pe);
+ pe.disabled = 1;
+ pe.exclude_kernel = 0;
+ pe.exclude_hv = 0;
+
+ /* Try to open the perf event */
+ perf_fd = perf_event_open(&pe, -1, 0, -1, 0);
+
+ if (should_succeed) {
+ if (perf_fd < 0) {
+ ksft_print_msg("FAIL: unexpected - perf_event_open failed for %s: %s\n",
+ filepath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+ ksft_print_msg("PASS: Allowed file %s successfully opened perf event\n",
+ filepath);
+ close(perf_fd);
+ } else {
+ if (perf_fd >= 0) {
+ ksft_print_msg("FAIL: unexpected - perf_event_open succeeded for %s\n",
+ filepath);
+ close(perf_fd);
+ close(fd);
+ return -1;
+ }
+ ksft_print_msg("PASS: Blocked file %s correctly failed perf_event_open: %s\n",
+ filepath, strerror(errno));
+ }
+
+out:
+ close(fd);
+ return 0;
+}
+
+static int walk_directory_recursive(int pmu_type, const char *dir_path)
+{
+ DIR *dir;
+ struct dirent *entry;
+ char full_path[1024];
+ struct stat statbuf;
+ int ret = 0;
+
+ dir = opendir(dir_path);
+ if (!dir) {
+ ksft_print_msg("Failed to open directory %s: %s\n", dir_path,
+ strerror(errno));
+ return -1;
+ }
+
+ while ((entry = readdir(dir)) != NULL) {
+ /* Skip . and .. */
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ snprintf(full_path, sizeof(full_path), "%s/%s", dir_path,
+ entry->d_name);
+
+ if (stat(full_path, &statbuf) != 0) {
+ ksft_print_msg("Failed to stat %s: %s\n", full_path,
+ strerror(errno));
+ continue;
+ }
+
+ if (S_ISDIR(statbuf.st_mode)) {
+ /* Recursively walk subdirectories */
+ if (walk_directory_recursive(pmu_type, full_path) != 0)
+ ret = -1;
+ } else if (S_ISREG(statbuf.st_mode)) {
+ /* Test regular files */
+ if (test_file_safety(pmu_type, full_path) != 0)
+ ret = -1;
+ }
+ }
+
+ closedir(dir);
+ return ret;
+}
+
+static int test_resctrl_pmu_safety(int pmu_type)
+{
+ ksft_print_msg("Testing resctrl PMU safety - walking all files in %s\n",
+ RESCTRL_PATH);
+
+ /* Walk through all files and directories in /sys/fs/resctrl */
+ return walk_directory_recursive(pmu_type, RESCTRL_PATH);
+}
+
+static bool pmu_feature_check(const struct resctrl_test *test)
+{
+ return resctrl_mon_feature_exists("L3_MON", "llc_occupancy");
+}
+
+static int pmu_run_test(const struct resctrl_test *test,
+ const struct user_params *uparams)
+{
+ int pmu_type, ret;
+
+ ksft_print_msg("Testing resctrl PMU file access safety\n");
+
+ /* Find the resctrl PMU type */
+ pmu_type = find_pmu_type(RESCTRL_PMU_NAME);
+ if (pmu_type < 0) {
+ ksft_print_msg("Resctrl PMU not found - PMU is not registered?\n");
+ return -1;
+ }
+
+ ksft_print_msg("Found resctrl PMU with type: %d\n", pmu_type);
+
+ /* Run the safety test to ensure only appropriate files work */
+ ret = test_resctrl_pmu_safety(pmu_type);
+
+ if (ret == 0)
+ ksft_print_msg("Resctrl PMU safety test completed successfully\n");
+ else
+ ksft_print_msg("Resctrl PMU safety test failed\n");
+
+ return ret;
+}
+
+struct resctrl_test pmu_test = {
+ .name = "PMU",
+ .group = "pmu",
+ .resource = "L3",
+ .vendor_specific = 0,
+ .feature_check = pmu_feature_check,
+ .run_test = pmu_run_test,
+ .cleanup = NULL,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index cd3adfc14969..5b0e6074eaba 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -244,5 +244,6 @@ extern struct resctrl_test cmt_test;
extern struct resctrl_test l3_cat_test;
extern struct resctrl_test l3_noncont_cat_test;
extern struct resctrl_test l2_noncont_cat_test;
+extern struct resctrl_test pmu_test;
#endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index 5154ffd821c4..11ba9000e015 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -21,6 +21,7 @@ static struct resctrl_test *resctrl_tests[] = {
&l3_cat_test,
&l3_noncont_cat_test,
&l2_noncont_cat_test,
+ &pmu_test,
};
static int detect_vendor(void)
Powered by blists - more mailing lists