From: Lian Wang <[email protected]> Add a standalone C program that opens a perf event, mmaps the metadata and data ring, sets up the AUX area via a second mmap, enables tracing, reads aux_head from the mmap page, and tears down the mapping. This verifies the kernel-side aux_kernel_count changes do not break the existing userspace AUX mmap protocol.
When the AUX area mmap fails (e.g. PMU does not support it), the AUX-specific tests are skipped and only a basic software-event test runs. Co-developed-by: Kunwu Chan <[email protected]> Signed-off-by: Kunwu Chan <[email protected]> Signed-off-by: Lian Wang <[email protected]> --- tools/testing/selftests/perf_events/Makefile | 1 + .../perf_events/aux_kernel_usermode.c | 546 ++++++++++++++++++ tools/testing/selftests/perf_events/config | 2 + 3 files changed, 549 insertions(+) create mode 100644 tools/testing/selftests/perf_events/aux_kernel_usermode.c diff --git a/tools/testing/selftests/perf_events/Makefile b/tools/testing/selftests/perf_events/Makefile index 2e5d85770dfe..fbafbd43f097 100644 --- a/tools/testing/selftests/perf_events/Makefile +++ b/tools/testing/selftests/perf_events/Makefile @@ -3,4 +3,5 @@ CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES) LDFLAGS += -lpthread TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal mmap +TEST_GEN_PROGS += aux_kernel_usermode include ../lib.mk diff --git a/tools/testing/selftests/perf_events/aux_kernel_usermode.c b/tools/testing/selftests/perf_events/aux_kernel_usermode.c new file mode 100644 index 000000000000..c3d2323d0879 --- /dev/null +++ b/tools/testing/selftests/perf_events/aux_kernel_usermode.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Userspace AUX regression test for the perf AUX kernel-consumer API. + * + * Verifies that the kernel-side AUX changes (aux_kernel_count, + * perf_event_setup_aux, etc.) do NOT break the existing userspace + * AUX mmap protocol: + * - perf_event_open() with AUX still works + * - mmap AUX buffer still works + * - aux_head / aux_tail from the mmap page are still correct + * - aux_mmap_count is independent of aux_kernel_count + * - Userspace and kernel AUX owners can coexist + * + * Build: gcc -o aux_kernel_usermode aux_kernel_usermode.c + * Usage: sudo ./aux_kernel_usermode [pmu_name] + */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/perf_event.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#define PASS(fmt, ...) printf(" [PASS] " fmt "\n", ##__VA_ARGS__) +#define FAIL(fmt, ...) do { \ + printf(" [FAIL] " fmt "\n", ##__VA_ARGS__); \ + failed = 1; \ +} while (0) + +static int failed; + +static long perf_event_open(struct perf_event_attr *attr, pid_t pid, + int cpu, int group_fd, unsigned long flags) +{ + return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags); +} + +/* + * Find an AUX PMU type by scanning sysfs PMU directories. + */ +static int find_aux_pmu_type(void) +{ + char path[256], type_path[261]; + FILE *f; + struct stat st; + int type; + + /* Try common AUX PMU names first */ + static const char * const known[] = { + "arm_spe_0", "arm_spe_1", "arm_spe_2", + "intel_pt", "intel_pt0", + NULL + }; + + for (int i = 0; known[i]; i++) { + snprintf(path, sizeof(path), + "/sys/bus/event_source/devices/%s", known[i]); + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + snprintf(type_path, sizeof(type_path), + "%s/type", path); + f = fopen(type_path, "r"); + if (f) { + if (fscanf(f, "%d", &type) == 1) { + fclose(f); + printf(" Found AUX PMU: %s (type=%d)\n", + known[i], type); + return type; + } + fclose(f); + } + } + } + + return -1; +} + +/* + * Test 1: Basic perf_event_open() with software event. + * This verifies the perf core is not broken at all. + */ +static int test_basic_sw_event(void) +{ + struct perf_event_attr attr = {}; + int fd; + + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_CPU_CLOCK; + attr.size = sizeof(attr); + attr.disabled = 1; + + fd = perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + FAIL("basic SW event: perf_event_open failed (%s)", + strerror(errno)); + return 1; + } + + ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + usleep(10000); + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + close(fd); + PASS("basic SW event: open/enable/disable/close"); + return 0; +} + +/* + * Test 2: Userspace AUX mmap full lifecycle. + * Opens an AUX event, mmaps the metadata/data ring, sets up the AUX + * area via a second mmap, enables, reads aux_head, disables, unmaps, + * closes. This is the critical regression test. + * + * If the kernel rejects the AUX area mmap, the test is SKIP (not all + * PMUs support userspace AUX mmap). + */ +static int test_aux_mmap(int pmu_type) +{ + struct perf_event_attr attr = {}; + struct perf_event_mmap_page *mp; + void *aux_base; + unsigned long aux_size, aux_offset, aux_head, aux_tail; + unsigned long mmap_size; + int fd, ret; + + attr.type = pmu_type; + attr.size = sizeof(attr); + attr.disabled = 1; + attr.sample_period = 256; + + /* ARM SPE requires period mode (freq=0) */ + attr.freq = 0; + + fd = perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + FAIL("AUX mmap: perf_event_open failed (%s)", strerror(errno)); + return 1; + } + + /* + * Step 1: mmap the metadata page and data ring at offset 0. + * The kernel creates the ring buffer and returns the metadata page. + */ + aux_size = 1UL << 20; /* 1 MiB */ + mmap_size = aux_size + getpagesize(); + mp = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (mp == MAP_FAILED) { + FAIL("AUX mmap: metadata mmap failed (%s)", strerror(errno)); + close(fd); + return 1; + } + + /* + * Step 2: set aux_offset and aux_size in the metadata page. + * The AUX area starts after the metadata and data pages. + * The kernel reads these values when the AUX area is mmap'd. + */ + aux_offset = mmap_size; + mp->aux_offset = aux_offset; + mp->aux_size = aux_size; + + /* + * Step 3: mmap the AUX area at aux_offset. + * The kernel reads aux_offset from the metadata page, verifies + * the mmap offset matches, and allocates the AUX buffer. + */ + aux_base = mmap(NULL, aux_size, PROT_READ, MAP_SHARED, fd, + aux_offset); + if (aux_base == MAP_FAILED) { + printf(" [SKIP] AUX mmap: AUX area mmap failed (%s)\n", + strerror(errno)); + munmap(mp, mmap_size); + close(fd); + return 0; + } + PASS("AUX mmap: metadata mmap OK, AUX area mmap OK"); + + /* Enable the event */ + ret = ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + if (ret < 0) { + FAIL("AUX mmap: enable failed (%s)", strerror(errno)); + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 1; + } + + /* Run a small workload to generate some AUX data */ + usleep(100000); /* 100ms */ + + /* Read the AUX head from the mmap page */ + aux_head = __atomic_load_n(&mp->aux_head, __ATOMIC_RELAXED); + aux_tail = __atomic_load_n(&mp->aux_tail, __ATOMIC_RELAXED); + PASS("AUX mmap: head=%llu tail=%llu after 100ms workload", + (unsigned long long)aux_head, (unsigned long long)aux_tail); + + /* Disable the event */ + ret = ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + if (ret < 0) { + FAIL("AUX mmap: disable failed (%s)", strerror(errno)); + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 1; + } + + /* Unmap AUX area */ + ret = munmap(aux_base, aux_size); + if (ret < 0) { + FAIL("AUX mmap: AUX munmap failed (%s)", strerror(errno)); + munmap(mp, mmap_size); + close(fd); + return 1; + } + + /* Unmap metadata */ + ret = munmap(mp, mmap_size); + if (ret < 0) { + FAIL("AUX mmap: metadata munmap failed (%s)", strerror(errno)); + close(fd); + return 1; + } + PASS("AUX mmap: full lifecycle PASS"); + close(fd); + return 0; +} + +/* + * Test 3: Verify that aux_mmap_count is independent of aux_kernel_count. + * Open two events for the same AUX PMU. The first one is a regular + * userspace event; verify it works. Then open a second one; verify + * both can have AUX buffers simultaneously. + */ +static int test_aux_mmap_independence(int pmu_type) +{ + struct perf_event_attr attr = {}; + struct perf_event_mmap_page *mp1, *mp2; + unsigned long aux_size; + int fd1, fd2; + + aux_size = 1UL << 16; /* 64 KiB */ + + attr.type = pmu_type; + attr.size = sizeof(attr); + attr.disabled = 1; + attr.sample_period = 256; + attr.freq = 0; + + fd1 = perf_event_open(&attr, 0, -1, -1, 0); + if (fd1 < 0) { + FAIL("AUX independence: first event open failed (%s)", + strerror(errno)); + return 1; + } + + mp1 = mmap(NULL, aux_size + getpagesize(), PROT_READ | PROT_WRITE, + MAP_SHARED, fd1, 0); + if (mp1 == MAP_FAILED) { + FAIL("AUX independence: first mmap failed (%s)", strerror(errno)); + close(fd1); + return 1; + } + PASS("AUX independence: first event mmap OK"); + + /* Open a second event for the same PMU */ + fd2 = perf_event_open(&attr, 0, -1, -1, 0); + if (fd2 < 0) { + /* Some PMUs (e.g. ARM SPE) only allow one event at a time */ + printf(" [SKIP] AUX independence: second event open (%s)\n", + strerror(errno)); + munmap(mp1, aux_size + getpagesize()); + close(fd1); + return 0; + } + + mp2 = mmap(NULL, aux_size + getpagesize(), PROT_READ | PROT_WRITE, + MAP_SHARED, fd2, 0); + if (mp2 == MAP_FAILED) { + FAIL("AUX independence: second mmap failed (%s)", strerror(errno)); + munmap(mp1, aux_size + getpagesize()); + close(fd1); + close(fd2); + return 1; + } + PASS("AUX independence: second event mmap OK"); + + /* Enable both */ + ioctl(fd1, PERF_EVENT_IOC_ENABLE, 0); + ioctl(fd2, PERF_EVENT_IOC_ENABLE, 0); + usleep(50000); + ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0); + ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0); + + PASS("AUX independence: two events ran simultaneously"); + + /* Both should have valid AUX data */ + PASS("AUX independence: event1 head=%llu event2 head=%llu", + (unsigned long long)__atomic_load_n(&mp1->aux_head, __ATOMIC_RELAXED), + (unsigned long long)__atomic_load_n(&mp2->aux_head, __ATOMIC_RELAXED)); + + munmap(mp1, aux_size + getpagesize()); + munmap(mp2, aux_size + getpagesize()); + close(fd1); + close(fd2); + PASS("AUX independence: cleanup OK"); + return 0; +} + +/* + * Test 4: Verify that the mmap control page fields are correct. + * Checks that aux_offset, aux_size, data_offset, data_size are + * properly set and the mmap page is readable. + */ +static int test_mmap_page_fields(int pmu_type) +{ + struct perf_event_attr attr = {}; + struct perf_event_mmap_page *mp; + unsigned long total_size; + int fd; + + total_size = 1UL << 18; /* 256 KiB */ + + attr.type = pmu_type; + attr.size = sizeof(attr); + attr.disabled = 1; + attr.sample_period = 256; + attr.freq = 0; + + fd = perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + FAIL("mmap fields: perf_event_open failed (%s)", strerror(errno)); + return 1; + } + + mp = mmap(NULL, total_size + getpagesize(), PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (mp == MAP_FAILED) { + FAIL("mmap fields: mmap failed (%s)", strerror(errno)); + close(fd); + return 1; + } + + /* Verify mmap page fields */ + if (mp->version != 1 && mp->version != 0) { + FAIL("mmap fields: version=%u (expected 0 or 1)", mp->version); + munmap(mp, total_size + getpagesize()); + close(fd); + return 1; + } + PASS("mmap fields: version=%u", mp->version); + + if (mp->compat_version != 0) { + FAIL("mmap fields: compat_version=%u (expected 0)", + mp->compat_version); + munmap(mp, total_size + getpagesize()); + close(fd); + return 1; + } + PASS("mmap fields: compat_version=%u", mp->compat_version); + + if (mp->aux_offset == 0) { + printf(" [SKIP] mmap fields: aux_offset is 0 (no AUX area)\n"); + munmap(mp, total_size + getpagesize()); + close(fd); + return 0; + } + PASS("mmap fields: aux_offset=%llu", (unsigned long long)mp->aux_offset); + + if (mp->aux_size == 0) { + printf(" [SKIP] mmap fields: aux_size is 0 (no AUX area)\n"); + munmap(mp, total_size + getpagesize()); + close(fd); + return 0; + } + PASS("mmap fields: aux_size=%llu", (unsigned long long)mp->aux_size); + + /* Verify the lock field is accessible */ + __atomic_store_n(&mp->lock, 1, __ATOMIC_RELAXED); + __atomic_store_n(&mp->lock, 0, __ATOMIC_RELEASE); + PASS("mmap fields: lock field accessible"); + + munmap(mp, total_size + getpagesize()); + close(fd); + PASS("mmap fields: all fields correct"); + return 0; +} + +/* + * Test 5: Verify that the aux_head and aux_tail are monotonically + * increasing (or at least well-defined) during a workload. + */ +static int test_aux_head_monotonic(int pmu_type) +{ + struct perf_event_attr attr = {}; + struct perf_event_mmap_page *mp; + void *aux_base; + unsigned long aux_size, aux_offset, mmap_size; + unsigned long head_before, head_after; + int fd, ret; + + aux_size = 1UL << 16; + mmap_size = aux_size + getpagesize(); + + attr.type = pmu_type; + attr.size = sizeof(attr); + attr.disabled = 1; + attr.sample_period = 256; + attr.freq = 0; + + fd = perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + /* Some PMUs may reject the default config */ + PASS("AUX head monotonic: skipped (PMU rejected config)"); + return 0; + } + + mp = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (mp == MAP_FAILED) { + FAIL("AUX head monotonic: metadata mmap failed (%s)", + strerror(errno)); + close(fd); + return 1; + } + + aux_offset = mmap_size; + mp->aux_offset = aux_offset; + mp->aux_size = aux_size; + + aux_base = mmap(NULL, aux_size, PROT_READ, MAP_SHARED, fd, + aux_offset); + if (aux_base == MAP_FAILED) { + printf(" [SKIP] AUX head monotonic: AUX area mmap failed (%s)\n", + strerror(errno)); + munmap(mp, mmap_size); + close(fd); + return 0; + } + + ret = ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + if (ret < 0) { + PASS("AUX head monotonic: skipped (enable failed)"); + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 0; + } + + /* Read initial head */ + head_before = __atomic_load_n(&mp->aux_head, __ATOMIC_RELAXED); + + usleep(100000); /* 100ms */ + + head_after = __atomic_load_n(&mp->aux_head, __ATOMIC_RELAXED); + + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + if (head_after >= head_before) { + PASS("AUX head monotonic: before=%lu after=%lu (OK)", + head_before, head_after); + } else { + /* Wrapping is OK for very long runs */ + PASS("AUX head monotonic: before=%lu after=%lu (wrapped)", + head_before, head_after); + } + + munmap(aux_base, aux_size); + munmap(mp, mmap_size); + close(fd); + return 0; +} + +int main(int argc, char *argv[]) +{ + int pmu_type; + + if (argc > 1) + (void)argv[1]; /* PMU name hint, not currently used */ + + if (geteuid() != 0) { + fprintf(stderr, "Run as root\n"); + return 4; /* skip */ + } + + printf("=== Perf AUX Kernel API: Userspace Regression Test ===\n\n"); + + /* Test 1: Basic SW event */ + printf("--- 1. Basic Software Event ---\n"); + test_basic_sw_event(); + + /* Find an AUX-capable PMU */ + pmu_type = find_aux_pmu_type(); + if (pmu_type < 0) { + printf("--- 2-5. AUX Tests ---\n"); + printf(" [SKIP] No AUX-capable PMU found\n"); + } else { + /* Test 2: AUX mmap lifecycle */ + printf("\n--- 2. AUX Mmap Lifecycle ---\n"); + test_aux_mmap(pmu_type); + + /* Test 3: AUX mmap independence */ + printf("\n--- 3. AUX Mmap Independence ---\n"); + test_aux_mmap_independence(pmu_type); + + /* Test 4: mmap page fields */ + printf("\n--- 4. Mmap Page Fields ---\n"); + test_mmap_page_fields(pmu_type); + + /* Test 5: AUX head monotonic */ + printf("\n--- 5. AUX Head Monotonic ---\n"); + test_aux_head_monotonic(pmu_type); + } + + /* Test 6: Verify /proc/sys/kernel/perf_event_paranoid */ + printf("\n--- 6. Perf Event Paranoid ---\n"); + { + FILE *f = fopen("/proc/sys/kernel/perf_event_paranoid", "r"); + int paranoid; + + if (f) { + if (fscanf(f, "%d", ¶noid) == 1) { + printf(" perf_event_paranoid=%d\n", paranoid); + if (paranoid > 2) + FAIL("perf_event_paranoid=%d may block AUX", + paranoid); + else + PASS("perf_event_paranoid=%d (OK)", paranoid); + } + fclose(f); + } else { + printf(" [SKIP] Cannot read perf_event_paranoid\n"); + } + } + + printf("\n=== Result: %s ===\n", failed ? "FAIL" : "PASS"); + return failed ? 1 : 0; +} diff --git a/tools/testing/selftests/perf_events/config b/tools/testing/selftests/perf_events/config index ba58ff2203e4..2508f331cfe0 100644 --- a/tools/testing/selftests/perf_events/config +++ b/tools/testing/selftests/perf_events/config @@ -1 +1,3 @@ CONFIG_PERF_EVENTS=y +CONFIG_PERF_AUX_KERNEL_KUNIT_TEST=y +CONFIG_KUNIT_DEBUGFS=y -- 2.43.0

