From fe7b73c293f1a5b0b7d8a4748b4c076fa7db852c Mon Sep 17 00:00:00 2001 From: James Clark Date: Wed, 7 Oct 2026 11:52:13 +0100 Subject: [PATCH] perf tests mmap-basic: Fix user rdpmc detection logic RISC-V and Arm control userspace counter access through /proc/sys/kernel/perf_user_access. Add that as a fallback to set_user_read() so the test can exercise both enabled and disabled states on those platforms. Document disabled, enabled and legacy states in user_read_state and fail on values outside the known 0-2 range. Preserve and restore any value in this range, rather than just 0 or 1. Skip when the control files exist but can't be written to (USER_READ_ERR_PERM). We can't rely on always being able to write to them when an exclude_kernel=0 event can be opened because opening events might succeed for non-root users when perf_event_paranoid=-1. This test isn't run on unsupported platforms since the previous commit, so we can simplify the following things: * Test that cap_user_rdpmc is always equal to the requested state rather than checking it only when disabled. As long as we stop setting the cap in the attr unconditionally on Arm, this is ok. * Remove the USER_READ_UNKNOWN/rdpmc_supported fallback in the checks. Now the expected state is always the one requested. USER_READ_UNKNOWN only controls whether to skip restoration of the state on unsupported platforms, so call it USER_READ_ERR_NO_FILE. Signed-off-by: James Clark Cc: Adrian Hunter Cc: Albert Ou Cc: Alexander Shishkin Cc: Alexandre Ghiti Cc: Ian Rogers Cc: Ingo Molnar Cc: Jiri Olsa Cc: Leo Yan Cc: Mark Rutland Cc: Michael Petlan Cc: Namhyung Kim Cc: Palmer Dabbelt Cc: Paul Walmsley Cc: Peter Zijlstra Assisted-by: Codex:GPT-6.1-Sol [Re-write: [Re-write to fix bugs in set_user_read() and simplify tests] Signed-off-by: Qiao Zhao Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/tests/mmap-basic.c | 137 +++++++++++++++++++++++----------- 1 file changed, 94 insertions(+), 43 deletions(-) diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c index bd9b9cce6a64..602a78d48b10 100644 --- a/tools/perf/tests/mmap-basic.c +++ b/tools/perf/tests/mmap-basic.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,8 @@ #include #include +#include + #include "cpumap.h" #include "debug.h" #include "event.h" @@ -182,50 +185,83 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest } enum user_read_state { - USER_READ_ENABLED, - USER_READ_DISABLED, - USER_READ_UNKNOWN, + USER_READ_ERR_PERM = -2, + USER_READ_ERR_NO_FILE = -1, + + USER_READ_DISABLED = '0', + USER_READ_ENABLED = '1', +#if defined(__i386__) || defined(__x86_64__) || (defined(__riscv) && __riscv_xlen == 64) + /* Unrestricted access on x86, legacy access on RISC-V. */ + USER_READ_LEGACY = '2', +#endif + USER_READ_MAX }; -static enum user_read_state set_user_read(struct perf_pmu *pmu, enum user_read_state enabled) +static enum user_read_state user_read_file_op(int fd, enum user_read_state enabled) { char buf[2] = {0, '\n'}; - ssize_t len; - int events_fd, rdpmc_fd; - enum user_read_state old_user_read = USER_READ_UNKNOWN; + ssize_t len = read(fd, buf, 1); + enum user_read_state old_user_read = USER_READ_ERR_NO_FILE; - if (enabled == USER_READ_UNKNOWN) - return USER_READ_UNKNOWN; - - events_fd = perf_pmu__event_source_devices_fd(); - if (events_fd < 0) - return USER_READ_UNKNOWN; - - rdpmc_fd = perf_pmu__pathname_fd(events_fd, pmu->name, "rdpmc", O_RDWR); - if (rdpmc_fd < 0) { - close(events_fd); - return USER_READ_UNKNOWN; + if (len <= 0) { + pr_debug("%s read failed\n", __func__); + goto out; } - len = read(rdpmc_fd, buf, sizeof(buf)); - if (len != sizeof(buf)) - pr_debug("%s read failed\n", __func__); - - // Note, on Intel hybrid disabling on 1 PMU will implicitly disable on - // all the core PMUs. - old_user_read = (buf[0] == '1') ? USER_READ_ENABLED : USER_READ_DISABLED; + old_user_read = buf[0]; if (enabled != old_user_read) { - buf[0] = (enabled == USER_READ_ENABLED) ? '1' : '0'; - len = write(rdpmc_fd, buf, sizeof(buf)); + buf[0] = enabled; + lseek(fd, 0, SEEK_SET); + len = write(fd, buf, sizeof(buf)); if (len != sizeof(buf)) pr_debug("%s write failed\n", __func__); } - close(rdpmc_fd); - close(events_fd); + +out: + close(fd); return old_user_read; } +static enum user_read_state set_user_read(struct perf_pmu *pmu, + enum user_read_state enabled) +{ + int events_fd, fd; + enum user_read_state ret = USER_READ_ERR_NO_FILE; + + if (enabled == USER_READ_ERR_NO_FILE || enabled == USER_READ_ERR_PERM) + return enabled; + + events_fd = perf_pmu__event_source_devices_fd(); + if (events_fd >= 0) { + fd = perf_pmu__pathname_fd(events_fd, pmu->name, "rdpmc", O_RDWR); + if (fd >= 0) { + /* + * Note, on Intel hybrid disabling on 1 PMU will implicitly + * disable on all the core PMUs. + */ + ret = user_read_file_op(fd, enabled); + close(events_fd); + return ret; + } else if (errno == EACCES || errno == EROFS) { + /* Permissions failure, flag the failure for a skip. */ + close(events_fd); + return USER_READ_ERR_PERM; + } + close(events_fd); + } + + /* Fallback: perf_user_access interface (arm64, riscv, or similar) */ + fd = open("/proc/sys/kernel/perf_user_access", O_RDWR); + if (fd >= 0) + ret = user_read_file_op(fd, enabled); + else if (errno == EACCES || errno == EROFS) + ret = USER_READ_ERR_PERM; + + return ret; +} + + static int test_stat_user_read(u64 event, enum user_read_state enabled) { struct perf_pmu *pmu = NULL; @@ -246,9 +282,6 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) .config = perf_pmus__supports_extended_type() ? event | ((u64)pmu->type << PERF_PMU_TYPE_SHIFT) : event, -#ifdef __aarch64__ - .config1 = 0x2, /* Request user access */ -#endif }; struct perf_evsel *evsel = NULL; int err; @@ -256,8 +289,28 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) bool mapped = false, opened = false, rdpmc_supported; struct perf_counts_values counts = { .val = 0 }; + if (__is_defined(__aarch64__) && enabled == USER_READ_ENABLED) + attr.config1 = 0x2; /* Request user access */ pr_debug("User space counter reading for PMU %s\n", pmu->name); + + if (saved_user_read_state == USER_READ_ERR_PERM) { + pr_debug("User space counter reading for PMU %s [Failed to get user read state]\n", + pmu->name); + ret = TEST_SKIP; + goto cleanup; + } + + /* Check value is in the expected range */ + if (saved_user_read_state != USER_READ_ERR_NO_FILE && + (saved_user_read_state < USER_READ_DISABLED || + saved_user_read_state >= USER_READ_MAX)) { + pr_err("User space counter reading for PMU %s [Unexpected current state %d]\n", + pmu->name, saved_user_read_state); + ret = TEST_FAIL; + goto cleanup; + } + /* * Restrict scheduling to only use the rdpmc on the CPUs the * event can be on. If the test doesn't run on the CPU of the @@ -301,21 +354,18 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) goto cleanup; } - if (saved_user_read_state == USER_READ_UNKNOWN) - rdpmc_supported = pc->cap_user_rdpmc && pc->index; - else - rdpmc_supported = (enabled == USER_READ_ENABLED); + rdpmc_supported = (enabled == USER_READ_ENABLED); - if (rdpmc_supported && (!pc->cap_user_rdpmc || !pc->index)) { - pr_err("User space counter reading for PMU %s [Failed unexpected supported counter access %d %d]\n", - pmu->name, pc->cap_user_rdpmc, pc->index); + if (rdpmc_supported && !pc->index) { + pr_err("User space counter reading for PMU %s [index must be set when enabled %d]\n", + pmu->name, pc->index); ret = TEST_FAIL; goto cleanup; } - if (!rdpmc_supported && pc->cap_user_rdpmc) { - pr_err("User space counter reading for PMU %s [Failed unexpected unsupported counter access %d]\n", - pmu->name, pc->cap_user_rdpmc); + if (rdpmc_supported != pc->cap_user_rdpmc) { + pr_err("User space counter reading for PMU %s [cap should match requested state %d %d]\n", + pmu->name, pc->cap_user_rdpmc, rdpmc_supported); ret = TEST_FAIL; goto cleanup; } @@ -363,7 +413,8 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) perf_evsel__munmap(evsel); if (opened) perf_evsel__close(evsel); - perf_evsel__delete(evsel); + if (evsel) + perf_evsel__delete(evsel); /* If the affinity was changed, then put it back to all CPUs. */ if (pmu->cpus != NULL) {