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)