All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jonathan Perry <yonch@yonch.com>
To: Tony Luck <tony.luck@intel.com>,
	Reinette Chatre <reinette.chatre@intel.com>,
	linux-kernel@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org, linux-doc@vger.kernel.org,
	Jonathan Corbet <corbet@lwn.net>,
	James Morse <james.morse@arm.com>,
	Roman Storozhenko <romeusmeister@gmail.com>,
	Jonathan Perry <yonch@yonch.com>
Subject: [PATCH 6/8] resctrl/pmu: Introduce skeleton PMU and selftests
Date: Thu, 16 Oct 2025 09:46:54 -0500	[thread overview]
Message-ID: <20251016144656.74928-7-yonch@yonch.com> (raw)
In-Reply-To: <20251016144656.74928-1-yonch@yonch.com>

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@yonch.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)

  parent reply	other threads:[~2025-10-16 14:47 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-16 14:46 [PATCH 0/8] resctrl: Add perf PMU for resctrl monitoring Jonathan Perry
2025-10-16 14:46 ` [PATCH 1/8] resctrl: Pin rdtgroup for mon_data file lifetime Jonathan Perry
2025-10-16 14:46 ` [PATCH 2/8] resctrl/mon: Split RMID read init from execution Jonathan Perry
2025-10-16 14:46 ` [PATCH 3/8] resctrl/mon: Select cpumask before invoking mon_event_read() Jonathan Perry
2025-10-16 14:46 ` [PATCH 4/8] resctrl/mon: Create mon_event_setup_read() helper Jonathan Perry
2025-10-16 14:46 ` [PATCH 5/8] resctrl: Propagate CPU mask validation error via rr->err Jonathan Perry
2025-10-16 14:46 ` Jonathan Perry [this message]
2025-10-16 14:46 ` [PATCH 7/8] resctrl/pmu: Use mon_event_setup_read() and validate CPU Jonathan Perry
2025-10-16 14:46 ` [PATCH 8/8] resctrl/pmu: Implement .read via direct RMID read; add LLC selftest Jonathan Perry
2025-10-16 21:25 ` [PATCH 0/8] resctrl: Add perf PMU for resctrl monitoring Luck, Tony
2025-10-16 21:51   ` Luck, Tony
2025-10-17 18:17     ` Jonathan Perry

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251016144656.74928-7-yonch@yonch.com \
    --to=yonch@yonch.com \
    --cc=corbet@lwn.net \
    --cc=james.morse@arm.com \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=reinette.chatre@intel.com \
    --cc=romeusmeister@gmail.com \
    --cc=tony.luck@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.