Re: [PATCH v4] perf tests: mmap-basic: fix user rdpmc detection logic

Ian Rogers <[email protected]>
Newsgroups org.kernel.vger.linux-perf-users,org.infradead.lists.linux-riscv,org.kernel.vger.linux-kernel
Message-ID <CAP-5=fWw6-NsAOjSUSNFgnA0vNg1pbE=Yf=QwF54y97WTdjXyQ@mail.gmail.com>
On Mon, Aug 17, 2026 at 8:54 AM James Clark <[email protected]> wrote:
>
> From: Qiao Zhao <[email protected]>
>
> 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 test both the enabled and disabled
> states on those platforms. RISC-V also uses a '2' value rather than just
> 0 or 1 so add support for restoring arbitrary values.
>
> On Arm, cap_user_rdpmc will always be set when requested, even if the
> global setting is disabled. This is so that the feature can be enabled
> or revoked while events are live. Skip checking it on Arm for the
> "expected disabled" case, otherwise the test will fail.
>
> Add comments, more meaningful variable names and improve the error
> messages so that it's clearer what this part of the test is doing.
>
> Signed-off-by: Qiao Zhao <[email protected]>
> [Test pc->index, fix bugs in set_user_read(), and simplify commit msg]
> Assisted-by: Codex:GPT-5.6
> Signed-off-by: James Clark <[email protected]>
> ---
> I'm sending this to fix the comments that I left on the "V3 resend"
> because I don't think Qiao sent a V4 and it's been quite a while.
>
> There were also some unreported bugs that I found during testing.
>
> Changes in V4:
>  - Don't remove pc->index check. Without it Perf can silently fall back
>    to the read() syscall and the test is useless.
>  - Test the 'expected disabled' case for Arm in an ifdef to workaround
>    platform differences.
>  - lseek() before writing to perf_user_access otherwise it's ignored.
>  - Support restoring arbitrary values to perf_user_access because RISC-V
>    uses '2' for legacy mode.

What does that mean? Should there be corresponding "legacy" support in libperf?

>  - Rename rdpmc_supported to rdpmc_expected as this is what the test
>    expects, not what the system does.

Can you explain the distinction here? The test expects that if
userspace reading is enabled, it should be supported. Imo this makes a
line like:
```
if (rdpmc_supported && counts.val == 0) {
```
easy to read. The same line with rdpmc_expected, well I need to then
go and figure out what expected should mean and it seems to just mean
supported, so the code was more readable before.

>  - Label pc->index as rdpmc_event_active for clarity.
>  - Add comments and simplify the commit message.
> ---
>  tools/perf/tests/mmap-basic.c | 137 ++++++++++++++++++++++++++++++------------
>  1 file changed, 98 insertions(+), 39 deletions(-)
>
> diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c
> index 5cec7644952c..4433a5df3d77 100644
> --- a/tools/perf/tests/mmap-basic.c
> +++ b/tools/perf/tests/mmap-basic.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0
>  #include <errno.h>
>  #include <inttypes.h>
> +#include <limits.h>
>  #include <stdlib.h>
>
>  #include <fcntl.h>
> @@ -182,47 +183,77 @@ 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_UNKNOWN = -1,
> +       USER_READ_DISABLED = 0,
> +       USER_READ_ENABLED = 1,
>  };
>
> -static enum user_read_state set_user_read(struct perf_pmu *pmu, enum user_read_state enabled)
> +static int set_user_read_fd(int fd, int enabled)

Why change this to an int rather than adding "legacy" to the
user_read_state enum? An int gives far more potential values than the
enum and so appears inherently less intention-revealing.

>  {
> -       char buf[2] = {0, '\n'};
> +       char buf[32], *endptr;
> +       long value;
>         ssize_t len;
> -       int events_fd, rdpmc_fd;
> -       enum user_read_state old_user_read = USER_READ_UNKNOWN;
> +       int old_user_read;
>
> -       if (enabled == USER_READ_UNKNOWN)
> +       len = read(fd, buf, sizeof(buf) - 1);
> +       if (len <= 0) {
> +               pr_debug("%s read failed\n", __func__);
>                 return USER_READ_UNKNOWN;
> +       }
> +       buf[len] = '\0';
>
> -       events_fd = perf_pmu__event_source_devices_fd();
> -       if (events_fd < 0)
> +       errno = 0;
> +       value = strtol(buf, &endptr, 10);
> +       if (errno || endptr == buf || value < 0 || value > INT_MAX) {

Given we're range checking the read value, can the upper bound be "> 2" ?

> +               pr_debug("%s invalid value: %s\n", __func__, buf);
>                 return USER_READ_UNKNOWN;
> +       }
> +       old_user_read = value;
>
> -       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 (enabled == old_user_read)
> +               return old_user_read;
> +
> +       len = scnprintf(buf, sizeof(buf), "%d\n", enabled);
> +       if (lseek(fd, 0, SEEK_SET) < 0) {
> +               pr_debug("%s seek failed\n", __func__);
> +               return old_user_read;
>         }
> +       if (write(fd, buf, len) != len)
> +               pr_debug("%s write failed\n", __func__);
>
> -       len = read(rdpmc_fd, buf, sizeof(buf));
> -       if (len != sizeof(buf))
> -               pr_debug("%s read failed\n", __func__);
> +       return old_user_read;
> +}
> +
> +static int set_user_read(struct perf_pmu *pmu, int enabled)
> +{
> +       int events_fd, fd, old_user_read;
>
> -       // 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;
> +       if (enabled == USER_READ_UNKNOWN)
> +               return USER_READ_UNKNOWN;
>
> -       if (enabled != old_user_read) {
> -               buf[0] = (enabled == USER_READ_ENABLED) ? '1' : '0';
> -               len = write(rdpmc_fd, buf, sizeof(buf));
> -               if (len != sizeof(buf))
> -                       pr_debug("%s write failed\n", __func__);
> +       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.
> +                        */
> +                       old_user_read = set_user_read_fd(fd, enabled);
> +                       close(fd);
> +                       close(events_fd);
> +                       return old_user_read;
> +               }
> +               close(events_fd);
>         }
> -       close(rdpmc_fd);
> -       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)
> +               return USER_READ_UNKNOWN;
> +
> +       old_user_read = set_user_read_fd(fd, enabled);
> +       close(fd);
>         return old_user_read;
>  }
>
> @@ -240,7 +271,7 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled)
>         perf_thread_map__set_pid(threads, 0, 0);
>
>         while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
> -               enum user_read_state saved_user_read_state = set_user_read(pmu, enabled);
> +               int saved_user_read_state = set_user_read(pmu, enabled);
>                 struct perf_event_attr attr = {
>                         .type   = PERF_TYPE_HARDWARE,
>                         .config = perf_pmus__supports_extended_type()
> @@ -253,7 +284,8 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled)
>                 struct perf_evsel *evsel = NULL;
>                 int err;
>                 struct perf_event_mmap_page *pc;
> -               bool mapped = false, opened = false, rdpmc_supported;
> +               bool mapped = false, opened = false, rdpmc_expected;
> +               bool rdpmc_event_active;
>                 struct perf_counts_values counts = { .val = 0 };
>
>
> @@ -301,26 +333,53 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled)
>                         goto cleanup;
>                 }
>
> +               /*
> +                * When pc->index == 0, userspace access is disabled and Perf
> +                * will silently use the read() syscall instead. Test this to
> +                * make sure we're not doing that.
> +                */
> +               rdpmc_event_active = pc->index;
> +
> +               /*
> +                * If we couldn't set the state, test that whatever state we're
> +                * already in is the expected one.
> +                */
>                 if (saved_user_read_state == USER_READ_UNKNOWN)
> -                       rdpmc_supported = pc->cap_user_rdpmc && pc->index;
> +                       rdpmc_expected = pc->cap_user_rdpmc && rdpmc_event_active;
>                 else
> -                       rdpmc_supported = (enabled == USER_READ_ENABLED);
> +                       rdpmc_expected = (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_expected && (!pc->cap_user_rdpmc || !rdpmc_event_active)) {
> +                       pr_err("User space counter reading for PMU %s [Failed. rdpmc event should be both enabled and active %d %d]\n",
> +                               pmu->name, pc->cap_user_rdpmc, rdpmc_event_active);
>                         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);
> +#ifdef __aarch64__
> +               /*
> +                * On Arm, pc->cap_user_rdpmc is set when the event is opened
> +                * with userspace counter access, regardless of whether rdpmc is
> +                * enabled or not via sysfs. The event is always opened with it
> +                * in this test, so don't check it in the expected disabled
> +                * case.
> +                */

It seems uapi/linux/perf_event.h should be amended with this meaning.
Currently it says:
```
cap_user_rdpmc          : 1, /* The RDPMC instruction can be used to
read counts */
```
and that lacks the sysfs nuance particular to ARM.

> +               if (!rdpmc_expected && rdpmc_event_active) {
> +                       pr_err("User space counter reading for PMU %s [Failed. rdpmc event should be inactive %d]\n",
> +                               pmu->name, rdpmc_event_active);
> +                       ret = TEST_FAIL;
> +                       goto cleanup;
> +               }
> +#else
> +               if (!rdpmc_expected && pc->cap_user_rdpmc) {
> +                       pr_err("User space counter reading for PMU %s [Failed. rdpmc event should be disabled and inactive %d %d]\n",
> +                               pmu->name, pc->cap_user_rdpmc, rdpmc_event_active);
>                         ret = TEST_FAIL;
>                         goto cleanup;
>                 }
> +#endif

So in the general (non-ARM) case should there be two prints? One for
"disabled" from pc->cap_user_rdpmc and one for "inactive" from
rdpmc_event_active? In that case the cap_user_rdpmc can be skipped on
ARM due to it not adhering to the common behavior.

Thanks,
Ian

>
> -               if (rdpmc_supported && pc->pmc_width < 32) {
> +               if (rdpmc_expected && pc->pmc_width < 32) {
>                         pr_err("User space counter reading for PMU %s [Failed width not set %d]\n",
>                                 pmu->name, pc->pmc_width);
>                         ret = TEST_FAIL;
> @@ -328,7 +387,7 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled)
>                 }
>
>                 perf_evsel__read(evsel, 0, 0, &counts);
> -               if (rdpmc_supported && counts.val == 0) {
> +               if (rdpmc_expected && counts.val == 0) {
>                         pr_err("User space counter reading for PMU %s [Failed read]\n", pmu->name);
>                         ret = TEST_FAIL;
>                         goto cleanup;
>
> ---
> base-commit: 6ae6fb96ccd48032b00a38d5f8e0e0a2cce4972b
> change-id: 20260817-rdpmc-detection-logic-d3f7a49cfb46
>
> Best regards,
> --
> James Clark <[email protected]>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.