* [RFC PATCH 0/5] perf/core: add AUX buffer kernel-consumer API
@ 2026-08-14 14:49 Kunwu Chan
2026-08-14 14:49 ` [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events Kunwu Chan
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Kunwu Chan @ 2026-08-14 14:49 UTC (permalink / raw)
To: corbet, skhan, peterz, mingo, acme, namhyung, mark.rutland,
alexander.shishkin, jolsa, irogers, adrian.hunter, james.clark,
kunwu.chan, lianux.mm
Cc: linux-doc, linux-kernel, linux-perf-users, linux-kselftest, sj
From: Kunwu Chan <kunwu.chan@gmail.com>
ARM SPE and other tracing PMUs write records directly to perf AUX
buffers. Unlike ordinary sampling PMUs, they do not necessarily
generate a perf_event_overflow() callback for each record. A kernel
consumer therefore needs to own and drain an AUX buffer rather than
rely on the overflow path.
Userspace obtains AUX storage through perf_event_open() and mmap().
Kernel events created by perf_event_create_kernel_counter() have no
public interface for allocating that storage: rb_alloc_aux() is
reached through perf_mmap().
This series adds a small perf-core API for that use case. Patches 1-2
add an in-kernel AUX owner, a setup/release pair, and producer-head,
consumer-tail, and copy helpers without exposing perf's internal page
array. Patches 3-5 add KUnit tests, a userspace regression test, and
a selftest orchestration script. The first expected consumer is a
DAMON ARM SPE backend, but this series is independent of DAMON and
does not add consumer-specific policy to perf.
The API follows the existing non-overwrite AUX protocol:
- setup validates the event, rejects non-power-of-two page counts and
negative watermark, allocates the ring buffer and AUX pages, and
attaches through perf's mmap-serialized lifecycle;
- release stops writers, drops the event->rb reference, and detaches
only a buffer owned by this API;
- head reads use smp_rmb() to pair with the producer's data-write
barrier;
- tail updates use smp_rmb() before and smp_mb() after to order data
reads before releasing space;
- copy validates the requested modular cursor interval against
[tail, head] before applying the ring mask and holds an AUX
reference while copying.
Testing
=======
Based on commit 917d558b151c ("perf/x86: Optimize ACR handling in
match_prev_assignment()") in the tip perf/core branch.
Built and tested on arm64 (Kunpeng 920, ARM SPE present). Each commit
compiles independently. Full Image build succeeds with
CONFIG_PERF_AUX_KERNEL_KUNIT_TEST=y.
KUnit (39 cases, dummy AUX PMU, zero hardware dependency):
pass:39 fail:0 skip:0
The dummy PMU registered in suite_init provides ->setup_aux/->free_aux
callbacks; setup_aux pre-fills pages with 0xAB so copy tests verify
data correctness byte-by-byte. Cases cover:
- setup/release lifecycle (12): non-power-of-2 rejection, pow2
acceptance, double-setup -EBUSY, parent event rejection, negative
watermark, explicit watermark, writer admission/blocking, no-op
release, setup/release roundtrip, multi-cycle, double release
- accessor contract (20): head initial/advances/no-rb, copy full
window/wrap/zero-len/data-correctness, copy rejects
rewound/future/oversized/null/already-consumed/no-rb, tail_set
valid/future/within-window/behind-ring/noop/consume-all/no-rb
- multi-cycle produce->copy->tail_set (1)
- perf_aux_output_end size=0, buffer-full stops producer (2)
- two-event independence, release isolation (2)
- user/kernel coexistence (1): simulated aux_mmap_count=1 event
and aux_kernel_count=1 event on the same PMU, verify mutual
non-interference and release isolation
- concurrent release (1): two kthreads concurrently call
perf_event_release_aux() on the same event, verifying the
refcount_dec_and_mutex_lock serialisation and detach-block
event->rb guard correctly handle the race
Selftest script (9 sections):
94 passed, 0 failed, 2 skipped -- Overall: PASS
Sections: build-time config, KUnit orchestration, userspace
regression, hardware perf record + dmesg, DAMON integration,
memory ordering static analysis, boundary validation, concurrency
safety, user/kernel AUX isolation.
Userspace C program:
9 PASS, 0 FAIL, 2 SKIP
The program follows the standard dual-mmap AUX protocol: mmap the
metadata/data ring at offset 0, set aux_offset/aux_size in the
metadata page, then mmap the AUX area at aux_offset. When the AUX
area mmap fails (e.g. PMU does not support it), the test is SKIP
rather than FAIL. ARM SPE single-event limitation is also SKIP.
Kunwu Chan (3):
perf/core: add AUX buffer ownership for kernel events
perf/core: add AUX ring accessors for kernel consumers
selftests/perf_events: add AUX kernel API selftest script
Lian Wang (2):
perf/core: add KUnit tests for AUX kernel-consumer API
selftests/perf_events: add userspace AUX regression test
.../userspace-api/perf_ring_buffer.rst | 56 +-
include/linux/perf_event.h | 18 +
kernel/events/Makefile | 1 +
kernel/events/aux_kernel_test.c | 1144 +++++++++++++++++
kernel/events/core.c | 139 ++
kernel/events/internal.h | 1 +
kernel/events/ring_buffer.c | 205 ++-
lib/Kconfig.debug | 15 +
tools/testing/selftests/perf_events/Makefile | 2 +
.../selftests/perf_events/aux_kernel.sh | 563 ++++++++
.../perf_events/aux_kernel_usermode.c | 546 ++++++++
tools/testing/selftests/perf_events/config | 2 +
12 files changed, 2687 insertions(+), 5 deletions(-)
create mode 100644 kernel/events/aux_kernel_test.c
create mode 100755 tools/testing/selftests/perf_events/aux_kernel.sh
create mode 100644 tools/testing/selftests/perf_events/aux_kernel_usermode.c
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events 2026-08-14 14:49 [RFC PATCH 0/5] perf/core: add AUX buffer kernel-consumer API Kunwu Chan @ 2026-08-14 14:49 ` Kunwu Chan 2026-08-14 15:04 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 2/5] perf/core: add AUX ring accessors for kernel consumers Kunwu Chan ` (3 subsequent siblings) 4 siblings, 1 reply; 11+ messages in thread From: Kunwu Chan @ 2026-08-14 14:49 UTC (permalink / raw) To: corbet, skhan, peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter, james.clark, kunwu.chan, lianux.mm Cc: linux-doc, linux-kernel, linux-perf-users, linux-kselftest, sj From: Kunwu Chan <kunwu.chan@gmail.com> Add an in-kernel AUX owner reference and setup/release helpers for kernel-created perf events that need an AUX buffer without a userspace mmap. The setup path validates that the event is a kernel event with no parent, rejects non-power-of-two page counts and negative watermark values, allocates the perf buffer and AUX pages, records the kernel owner, and attaches the buffer through the existing mmap-serialized helper. The release path stops AUX writers, frees AUX storage, drops the event->rb reference via ring_buffer_put(), and detaches only buffers owned by this API. Keep aux_mmap_count dedicated to userspace mappings. perf_aux_output_begin() accepts a writer while either a userspace or kernel owner remains, preserving the existing teardown ordering. Co-developed-by: Lian Wang (Processmission) <lianux.mm@gmail.com> Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com> Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com> --- include/linux/perf_event.h | 11 +++ kernel/events/core.c | 139 ++++++++++++++++++++++++++++++++++++ kernel/events/internal.h | 1 + kernel/events/ring_buffer.c | 11 +-- 4 files changed, 158 insertions(+), 4 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 48d851fbd8ea..4070a725d21f 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1254,6 +1254,17 @@ perf_event_create_kernel_counter(struct perf_event_attr *attr, perf_overflow_handler_t callback, void *context); +/* + * AUX ring-buffer support for kernel-created events (no user mmap). + * perf_event_setup_aux() allocates the buffer the PMU writes into via + * perf_aux_output_begin()/perf_aux_output_end(); the paired + * perf_event_release_aux() must be called before + * perf_event_release_kernel(). + */ +extern int perf_event_setup_aux(struct perf_event *event, int nr_pages, + long watermark); +extern void perf_event_release_aux(struct perf_event *event); + extern void perf_pmu_migrate_context(struct pmu *pmu, int src_cpu, int dst_cpu); extern int perf_event_read_local(struct perf_event *event, u64 *value, diff --git a/kernel/events/core.c b/kernel/events/core.c index 4638544205f2..da6225ca144a 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7122,6 +7122,145 @@ static void perf_mmap_close(struct vm_area_struct *vma) ring_buffer_put(rb); /* could be last */ } +/* + * perf_event_setup_aux()/perf_event_release_aux() - AUX buffer support for + * kernel-created perf events (perf_event_create_kernel_counter()). + * + * perf_mmap()/perf_mmap_close() build and tear down AUX buffers for + * user-space events; kernel consumers (e.g. DAMON's ARM SPE backend) have no + * mmap, so they need this symmetric pair to get a buffer the PMU can write + * into via perf_aux_output_begin()/perf_aux_output_end(). + * + * The buffer holds an AUX owner reference (aux_kernel_count = 1) so + * perf_aux_output_begin() admits writers and so it is torn down here + * rather than by perf_mmap_close(). perf_event_release_aux() also detaches + * the event's normal ring-buffer reference before the event is released. + */ + +/** + * perf_event_setup_aux() - Allocate an AUX ring buffer for an event. + * @event: Kernel-created perf event (must not have an rb yet). + * @nr_pages: AUX buffer size in pages; power of two, >= 1. + * @watermark: AUX watermark; 0 selects the perf default (half the buffer). + * + * The buffer is non-overwrite streaming (RING_BUFFER_WRITABLE): the PMU + * pauses when the buffer is full until the consumer advances the tail. + * Must be called before the event is enabled. The PMU's ->setup_aux() + * callback (e.g. arm_spe_pmu_setup_aux) validates the size and maps the + * pages for hardware writes. The ring buffer is allocated with zero + * data pages, so PERF_RECORD_AUX events are not recorded; this is + * intentional for kernel consumers that drain trace data directly. + * Returns 0 on success, -errno otherwise. + */ +int perf_event_setup_aux(struct perf_event *event, int nr_pages, + long watermark) +{ + struct perf_buffer *rb; + int ret; + + if (!is_kernel_event(event) || event->parent || + !is_power_of_2(nr_pages) || watermark < 0) + return -EINVAL; + + mutex_lock(&event->mmap_mutex); + if (event->rb) { + ret = -EBUSY; + goto out_unlock; + } + + rb = rb_alloc(0, 0, event->cpu, 0); + if (!rb) { + ret = -ENOMEM; + goto out_unlock; + } + + /* AUX area sits right after the user (control) page. */ + ret = rb_alloc_aux(rb, event, 1, nr_pages, watermark, + RING_BUFFER_WRITABLE); + if (ret) + goto err_put; + + /* + * No user-space mmap exists for this buffer; record an in-kernel + * AUX owner (aux_kernel_count) instead so perf_aux_output_begin() + * admits writers and perf_event_release_aux() performs the teardown + * that perf_mmap_close() would otherwise do. + */ + refcount_set(&rb->aux_kernel_count, 1); + + /* Transfer the allocation reference to the event. */ + ring_buffer_attach(event, rb); + mutex_unlock(&event->mmap_mutex); + + return 0; + +err_put: + ring_buffer_put(rb); +out_unlock: + mutex_unlock(&event->mmap_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(perf_event_setup_aux); + +/** + * perf_event_release_aux() - Tear down an AUX buffer for an event. + * @event: Event with an AUX buffer allocated by perf_event_setup_aux(). + * + * Stops any active AUX writers, frees the AUX pages, and detaches the ring + * buffer from the event. Must be called before perf_event_release_kernel(). + * Safe to call with a missing rb (no-op). A ring buffer not allocated by + * perf_event_setup_aux() is left attached. + * + * The caller must quiesce AUX consumers before releasing the buffer; + * active AUX references at release time indicate a violated lifetime + * contract (see the aux_refcount WARN below). + */ +void perf_event_release_aux(struct perf_event *event) +{ + struct perf_buffer *rb; + + if (!is_kernel_event(event) || event->parent) + return; + + rb = ring_buffer_get(event); + if (!rb) + return; + + /* Do not detach a ring buffer that this API does not own. */ + if (!rb_has_aux(rb) || !refcount_read(&rb->aux_kernel_count)) + goto out_put; + + if (refcount_dec_and_mutex_lock(&rb->aux_kernel_count, &rb->aux_mutex)) { + /* + * Stop all AUX events writing to this buffer so the pages + * can be freed; after aux_kernel_count drops to zero they + * won't start any more (see perf_aux_output_begin()). + */ + perf_pmu_output_stop(event); + + rb_free_aux(rb); + WARN_ON_ONCE(refcount_read(&rb->aux_refcount)); + mutex_unlock(&rb->aux_mutex); + } + + /* + * Detach the ring buffer from the event. This runs even if the + * refcount_dec_and_mutex_lock above lost the race to another + * concurrent release caller; the mmap_mutex serialisation and the + * event->rb == rb check ensure only one caller performs the detach. + */ + mutex_lock(&event->mmap_mutex); + if (event->rb == rb) { + ring_buffer_attach(event, NULL); + ring_buffer_put(rb); /* drop the event->rb reference */ + } + mutex_unlock(&event->mmap_mutex); + +out_put: + ring_buffer_put(rb); /* the temporary reference from ring_buffer_get() */ +} +EXPORT_SYMBOL_GPL(perf_event_release_aux); + static vm_fault_t perf_mmap_pfn_mkwrite(struct vm_fault *vmf) { /* The first page is the user control page, others are read-only. */ diff --git a/kernel/events/internal.h b/kernel/events/internal.h index c03c4f2eea57..7bcb60827514 100644 --- a/kernel/events/internal.h +++ b/kernel/events/internal.h @@ -48,6 +48,7 @@ struct perf_buffer { int aux_nr_pages; int aux_overwrite; refcount_t aux_mmap_count; + refcount_t aux_kernel_count; /* in-kernel AUX owner */ unsigned long aux_mmap_locked; void (*free_aux)(void *); refcount_t aux_refcount; diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index 9fe92161715e..be57007a1b79 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -395,14 +395,17 @@ void *perf_aux_output_begin(struct perf_output_handle *handle, goto err; /* - * If aux_mmap_count is zero, the aux buffer is in perf_mmap_close(), - * about to get freed, so we leave immediately. + * If no AUX owner remains, the buffer is in perf_mmap_close() or + * perf_event_release_aux(), about to get freed, so we leave + * immediately. aux_mmap_count tracks user-space mmap owners; + * aux_kernel_count tracks in-kernel owners (perf_event_setup_aux()). * - * Checking rb::aux_mmap_count and rb::refcount has to be done in + * Checking the AUX owner counts and rb::refcount has to be done in * the same order, see perf_mmap_close. Otherwise we end up freeing * aux pages in this path, which is a bug, because in_atomic(). */ - if (!refcount_read(&rb->aux_mmap_count)) + if (!refcount_read(&rb->aux_mmap_count) && + !refcount_read(&rb->aux_kernel_count)) goto err; if (!refcount_inc_not_zero(&rb->aux_refcount)) -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events 2026-08-14 14:49 ` [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events Kunwu Chan @ 2026-08-14 15:04 ` sashiko-bot 0 siblings, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-08-14 15:04 UTC (permalink / raw) To: Kunwu Chan; +Cc: linux-perf-users Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Double `ring_buffer_put()` in `perf_event_release_aux()` leads to refcount underflow and use-after-free of the ring buffer. - [High] Concurrent detach of `event->rb` causes a NULL pointer dereference inside `perf_pmu_output_stop()`. -- --- Patch [1]: [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events --- commit 34b60be57b99a06be2e537e647853e4c5c20c8c2 Author: Kunwu Chan <kunwu.chan@gmail.com> perf/core: add AUX buffer ownership for kernel events This commit adds an in-kernel AUX owner reference and setup/release helpers for kernel-created perf events. It introduces perf_event_setup_aux() and perf_event_release_aux() to handle AUX buffer lifecycle without requiring a userspace mmap. > diff --git a/kernel/events/core.c b/kernel/events/core.c > --- a/kernel/events/core.c > +++ b/kernel/events/core.c [ ... ] > +void perf_event_release_aux(struct perf_event *event) > +{ > + struct perf_buffer *rb; > + > + if (!is_kernel_event(event) || event->parent) > + return; > + > + rb = ring_buffer_get(event); > + if (!rb) > + return; > + > + /* Do not detach a ring buffer that this API does not own. */ > + if (!rb_has_aux(rb) || !refcount_read(&rb->aux_kernel_count)) > + goto out_put; > + > + if (refcount_dec_and_mutex_lock(&rb->aux_kernel_count, &rb->aux_mutex)) { > + /* > + * Stop all AUX events writing to this buffer so the pages > + * can be freed; after aux_kernel_count drops to zero they > + * won't start any more (see perf_aux_output_begin()). > + */ > + perf_pmu_output_stop(event); [Severity: High] Is there a race condition here if multiple threads call this concurrently? If Thread 1 enters this block and calls perf_pmu_output_stop(), it accesses event->rb directly: static void perf_pmu_output_stop(struct perf_event *event) { ... list_for_each_entry_rcu(iter, &event->rb->event_list, rb_entry) { Meanwhile, Thread 2 could fail the aux_kernel_count decrement, skip the block, and immediately detach the buffer: if (event->rb == rb) { ring_buffer_attach(event, NULL); Since ring_buffer_attach(event, NULL) sets event->rb to NULL, could Thread 1 dereference a NULL pointer inside perf_pmu_output_stop()? > + > + rb_free_aux(rb); > + WARN_ON_ONCE(refcount_read(&rb->aux_refcount)); > + mutex_unlock(&rb->aux_mutex); > + } > + > + /* > + * Detach the ring buffer from the event. This runs even if the > + * refcount_dec_and_mutex_lock above lost the race to another > + * concurrent release caller; the mmap_mutex serialisation and the > + * event->rb == rb check ensure only one caller performs the detach. > + */ > + mutex_lock(&event->mmap_mutex); > + if (event->rb == rb) { > + ring_buffer_attach(event, NULL); > + ring_buffer_put(rb); /* drop the event->rb reference */ [Severity: High] Does this result in a double-free? Looking at ring_buffer_attach(), it already drops the reference to the old buffer when it is replaced: static void ring_buffer_attach(struct perf_event *event, struct perf_buffer *rb) { ... rcu_assign_pointer(event->rb, rb); if (old_rb) { ring_buffer_put(old_rb); ... } If ring_buffer_attach(event, NULL) already calls ring_buffer_put() on the detached buffer, will calling ring_buffer_put(rb) again immediately after cause the refcount to underflow and prematurely free the ring buffer? > + } > + mutex_unlock(&event->mmap_mutex); > + > +out_put: > + ring_buffer_put(rb); /* the temporary reference from ring_buffer_get() */ > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260814144927.489172-1-kunwu.chan@linux.dev?part=1 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 2/5] perf/core: add AUX ring accessors for kernel consumers 2026-08-14 14:49 [RFC PATCH 0/5] perf/core: add AUX buffer kernel-consumer API Kunwu Chan 2026-08-14 14:49 ` [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events Kunwu Chan @ 2026-08-14 14:49 ` Kunwu Chan 2026-08-14 14:59 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API Kunwu Chan ` (2 subsequent siblings) 4 siblings, 1 reply; 11+ messages in thread From: Kunwu Chan @ 2026-08-14 14:49 UTC (permalink / raw) To: corbet, skhan, peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter, james.clark, kunwu.chan, lianux.mm Cc: linux-doc, linux-kernel, linux-perf-users, linux-kselftest, sj From: Kunwu Chan <kunwu.chan@gmail.com> Add kernel-consumer accessors for the published AUX producer head, the consumer tail, and copying a possibly wrapped AUX interval without exposing perf's internal page array. Use the same memory ordering as mmap consumers: order data reads after the published head with smp_rmb() and order completed data reads before advancing the tail with smp_mb(). Validate cursor distances in the absolute cursor domain before applying the ring mask, so rewound, already-consumed, and future windows are rejected even across unsigned cursor wrap. Hold an AUX reference while copying so storage cannot disappear under the consumer. Restrict the helpers to buffers owned by the kernel AUX setup API. Document the API in Documentation/userspace-api/perf_ring_buffer.rst (§3.4). Co-developed-by: Lian Wang (Processmission) <lianux.mm@gmail.com> Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com> Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com> --- .../userspace-api/perf_ring_buffer.rst | 56 ++++- include/linux/perf_event.h | 7 + kernel/events/ring_buffer.c | 194 ++++++++++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) diff --git a/Documentation/userspace-api/perf_ring_buffer.rst b/Documentation/userspace-api/perf_ring_buffer.rst index dc71544532ce..8333d2791878 100644 --- a/Documentation/userspace-api/perf_ring_buffer.rst +++ b/Documentation/userspace-api/perf_ring_buffer.rst @@ -26,6 +26,7 @@ Perf ring buffer 3.1 The relationship between AUX and regular ring buffers 3.2 AUX events 3.3 Snapshot mode + 3.4 Kernel-consumer AUX buffer access 1. Introduction @@ -827,4 +828,57 @@ mode. | AUX Ring buffer 3 | <- aux_head +---------------------------------------+ - Figure 9. Snapshot with system wide mode + Figure 9. Snapshot with system wide mode + +3.4 Kernel-consumer AUX buffer access +------------------------------------- + +The AUX ring buffer is normally consumed from user space via mmap() +on the perf event fd. Some tracing PMUs (e.g. ARM SPE) write trace +records directly to the AUX buffer without generating a +perf_event_overflow() callback for each record. A kernel consumer +therefore needs to own and drain the AUX buffer itself rather than +rely on the overflow path. + +The perf core provides five exported functions for in-kernel +consumers that do not have a user-space mmap: + + - ``perf_event_setup_aux(event, nr_pages, watermark)`` — allocate + an AUX ring buffer for a kernel-created perf event. The event + must be created by ``perf_event_create_kernel_counter()``; it + must not have a parent or an existing ring buffer. ``nr_pages`` + must be a power of two and ``watermark`` must be non-negative + (0 selects half the buffer). + + - ``perf_event_release_aux(event)`` — tear down the AUX buffer + allocated by ``perf_event_setup_aux()``. Must be called before + ``perf_event_release_kernel()``. Safe to call on an event that + never had an AUX buffer (no-op). A ring buffer not owned by the + kernel AUX API is left attached. + + - ``perf_event_aux_head(event)`` — read the published producer + head. Returns 0 if the event has no ring buffer. + + - ``perf_event_aux_tail_set(event, tail)`` — advance the consumer + tail. The new tail must be within the current ``[old_tail, + head]`` window; an out-of-range tail is rejected with ``-EINVAL``. + + - ``perf_event_aux_copy(event, from, to, buf)`` — copy a possibly + wrapped AUX interval into a linear buffer. The ``from``/``to`` + cursors are absolute and must lie within ``[tail, head]``. + +The owner reference is tracked by ``aux_kernel_count`` on the +``perf_buffer``, separate from the userspace ``aux_mmap_count``. +``perf_aux_output_begin()`` admits a writer while either owner +count is non-zero, so a kernel consumer and a userspace consumer +on different events for the same PMU do not interfere. + +A userspace mmap and a kernel ``perf_event_setup_aux()`` on the +*same* event cannot coexist: the second call finds ``event->rb`` +already set and returns ``-EBUSY``. + +Memory ordering follows the same protocol as the userspace AUX +mmap consumer: ``perf_event_aux_head()`` uses ``smp_rmb()`` to pair +with the producer's data-write barrier before publishing ``aux_head``, +and ``perf_event_aux_tail_set()`` uses ``smp_mb()`` to order prior +data reads before advancing ``aux_tail``. diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 4070a725d21f..800638f70628 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1265,6 +1265,13 @@ extern int perf_event_setup_aux(struct perf_event *event, int nr_pages, long watermark); extern void perf_event_release_aux(struct perf_event *event); +/* AUX ring accessors for kernel consumers (no user-space mmap). */ +extern unsigned long perf_event_aux_head(struct perf_event *event); +extern int perf_event_aux_tail_set(struct perf_event *event, + unsigned long tail); +extern long perf_event_aux_copy(struct perf_event *event, unsigned long from, + unsigned long to, void *buf); + extern void perf_pmu_migrate_context(struct pmu *pmu, int src_cpu, int dst_cpu); extern int perf_event_read_local(struct perf_event *event, u64 *value, diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index be57007a1b79..6608df879d7f 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -580,6 +580,200 @@ void *perf_get_aux(struct perf_output_handle *handle) } EXPORT_SYMBOL_GPL(perf_get_aux); +/* + * perf_event_aux_head()/perf_event_aux_tail_set()/perf_event_aux_copy() - + * AUX ring accessors for kernel consumers (e.g. DAMON's ARM SPE backend). + * + * The write cursor rb->aux_head is maintained by the PMU driver via + * perf_aux_output_end(), which also publishes it to user_page->aux_head; + * the consumer cursor lives in user_page->aux_tail (same absolute cursor + * domain). The kernel consumer has no mmap, so these are the counterpart + * of the user-space mmap protocol. + * + * As in the user-space protocol, data visibility is the producer's duty: + * the PMU driver must make AUX data visible before calling + * perf_aux_output_end(), which then publishes the new head. Kernel + * consumers use the same read- and full-barrier ordering as mmap consumers. + */ + +static bool rb_has_kernel_aux(struct perf_buffer *rb) +{ + return rb_has_aux(rb) && refcount_read(&rb->aux_kernel_count); +} + +/** + * perf_event_aux_head() - Return the event's absolute AUX write cursor. + * @event: Event with an AUX buffer (perf_event_setup_aux()). + * + * Returns the current rb->aux_head, or 0 if the event has no ring buffer. + */ +unsigned long perf_event_aux_head(struct perf_event *event) +{ + struct perf_buffer *rb = ring_buffer_get(event); + unsigned long head = 0; + + if (rb && rb_has_kernel_aux(rb)) { + head = READ_ONCE(rb->user_page->aux_head); + /* Pairs with the producer's AUX-data write barrier. */ + smp_rmb(); + } + if (rb) + ring_buffer_put(rb); + return head; +} +EXPORT_SYMBOL_GPL(perf_event_aux_head); + +/** + * perf_event_aux_tail_set() - Advance the event's AUX consumer cursor. + * @event: Event with an AUX buffer. + * @tail: New absolute tail; must be within the current AUX window. + * + * Frees the consumed space so perf_aux_output_begin() can compute space + * again for the PMU writer. The new tail must refer to a valid position + * within the current AUX window (the last ring of produced data); an + * out-of-window tail would corrupt the free-space computation in + * perf_aux_output_begin() and let the producer overwrite unconsumed data. + * + * If a non-overwrite buffer becomes full, the producer may be stopped; + * advancing the tail alone does not resume it, and the consumer is + * responsible for re-enabling the event if needed (as user-space AUX + * consumers do). + * + * Returns 0 on success, -ENOENT if the event has no ring buffer, -EINVAL + * on an out-of-range tail. + */ +int perf_event_aux_tail_set(struct perf_event *event, unsigned long tail) +{ + struct perf_buffer *rb = ring_buffer_get(event); + unsigned long advance, aux_size, head, old_tail; + int ret = -EINVAL; + + if (!rb) + return -ENOENT; + + if (!rb_has_kernel_aux(rb)) { + ret = -ENOENT; + goto out; + } + + aux_size = (unsigned long)rb->aux_nr_pages << PAGE_SHIFT; + old_tail = READ_ONCE(rb->user_page->aux_tail); + /* + * Pairs with the producer's AUX-data write barrier in + * perf_aux_output_end() before it publishes aux_head. + */ + smp_rmb(); + head = READ_ONCE(rb->user_page->aux_head); + advance = tail - old_tail; + + /* Modular distances keep the check valid when a cursor wraps. */ + if (head - old_tail <= aux_size && advance <= head - old_tail) { + /* Order all prior AUX data reads before releasing the space. */ + smp_mb(); + WRITE_ONCE(rb->user_page->aux_tail, tail); + ret = 0; + } + +out: + ring_buffer_put(rb); + return ret; +} +EXPORT_SYMBOL_GPL(perf_event_aux_tail_set); + +/** + * perf_event_aux_copy() - Copy an AUX window into a linear buffer. + * @event: Event with an AUX buffer. + * @from: Absolute start cursor (inclusive). + * @to: Absolute end cursor (exclusive). + * @buf: Destination; must hold (to - from) bytes. + * + * Handles wrap-around within the ring. Returns the number of bytes + * copied, or -errno. The window must not exceed the ring size; this is + * enforced below, so a buggy consumer cannot silently read garbage. + */ +long perf_event_aux_copy(struct perf_event *event, unsigned long from, + unsigned long to, void *buf) +{ + struct perf_buffer *rb = ring_buffer_get(event); + unsigned long aux_size, available, head, len, start, tail, tocopy; + long ret; + + if (!rb) + return -ENOENT; + + if (!rb_has_kernel_aux(rb)) { + ret = -ENOENT; + goto out; + } + + /* + * AUX storage may be referenced from both producer and consumer + * contexts (see rb_alloc_aux): a concurrent perf_event_release_aux() + * may free it once the last writer drops the AUX reference. Keep + * it alive for the duration of the copy. + */ + if (!refcount_inc_not_zero(&rb->aux_refcount)) { + ret = -ENOENT; + goto out; + } + + if (!buf) { + ret = -EINVAL; + goto out_aux; + } + + aux_size = (unsigned long)rb->aux_nr_pages << PAGE_SHIFT; + tail = READ_ONCE(rb->user_page->aux_tail); + head = READ_ONCE(rb->user_page->aux_head); + /* Pairs with the producer's AUX-data write barrier. */ + smp_rmb(); + available = head - tail; + start = from - tail; + len = to - from; + + /* + * Validate modular distances before masking. The requested window + * must be wholly contained in the current [tail, head] interval; + * this rejects rewound, already-consumed, and future cursors while + * remaining correct across unsigned cursor wrap. + */ + if (available > aux_size || start > available || + len > available - start) { + ret = -EINVAL; + goto out_aux; + } + if (!len) { + ret = 0; + goto out_aux; + } + + from &= aux_size - 1; + to &= aux_size - 1; + ret = 0; + + do { + tocopy = PAGE_SIZE - offset_in_page(from); + if (to > from) + tocopy = min(tocopy, to - from); + if (!tocopy) + break; + + memcpy(buf + ret, rb->aux_pages[from >> PAGE_SHIFT] + + offset_in_page(from), tocopy); + + ret += tocopy; + from += tocopy; + from &= aux_size - 1; + } while (to != from); + +out_aux: + rb_free_aux(rb); +out: + ring_buffer_put(rb); + return ret; +} +EXPORT_SYMBOL_GPL(perf_event_aux_copy); + /* * Copy out AUX data from an AUX handle. */ -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 2/5] perf/core: add AUX ring accessors for kernel consumers 2026-08-14 14:49 ` [RFC PATCH 2/5] perf/core: add AUX ring accessors for kernel consumers Kunwu Chan @ 2026-08-14 14:59 ` sashiko-bot 0 siblings, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-08-14 14:59 UTC (permalink / raw) To: Kunwu Chan; +Cc: linux-perf-users > From: Kunwu Chan <kunwu.chan@gmail.com> > > Add kernel-consumer accessors for the published AUX producer head, the > consumer tail, and copying a possibly wrapped AUX interval without exposing > perf's internal page array. > > Use the same memory ordering as mmap consumers: order data reads after the > published head with smp_rmb() and order completed data reads before > advancing the tail with smp_mb(). Validate cursor distances in the > absolute cursor domain before applying the ring mask, so rewound, > already-consumed, and future windows are rejected even across unsigned > cursor wrap. > > Hold an AUX reference while copying so storage cannot disappear under the > consumer. Restrict the helpers to buffers owned by the kernel AUX setup > API. Document the API in > Documentation/userspace-api/perf_ring_buffer.rst (§3.4). > > Co-developed-by: Lian Wang (Processmission) <lianux.mm@gmail.com> > Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com> > Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260814144927.489172-1-kunwu.chan@linux.dev?part=2 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API 2026-08-14 14:49 [RFC PATCH 0/5] perf/core: add AUX buffer kernel-consumer API Kunwu Chan 2026-08-14 14:49 ` [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events Kunwu Chan 2026-08-14 14:49 ` [RFC PATCH 2/5] perf/core: add AUX ring accessors for kernel consumers Kunwu Chan @ 2026-08-14 14:49 ` Kunwu Chan 2026-08-14 15:02 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test Kunwu Chan 2026-08-14 14:49 ` [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script Kunwu Chan 4 siblings, 1 reply; 11+ messages in thread From: Kunwu Chan @ 2026-08-14 14:49 UTC (permalink / raw) To: corbet, skhan, peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter, james.clark, kunwu.chan, lianux.mm Cc: linux-doc, linux-kernel, linux-perf-users, linux-kselftest, sj From: Lian Wang <lianux.mm@gmail.com> Add a KUnit test suite (perf_aux_kernel) for the perf_event_setup_aux(), perf_event_release_aux(), perf_event_aux_head(), perf_event_aux_tail_set() and perf_event_aux_copy() functions. A self-contained dummy AUX PMU is registered in suite_init so that every test is deterministic and needs no hardware. The dummy setup_aux pre-fills all pages with a recognisable pattern (0xAB); aux_test_produce() advances the published head via perf_aux_output_begin/end, and copy tests verify the pattern. 39 test cases cover setup/release lifecycle, writer admission and blocking, head/tail/copy accessor contract validation (including wrap-around, boundary cursors, and data correctness), buffer-full behaviour, negative watermark rejection, independence of two events on the same PMU, user/kernel coexistence simulation, and concurrent release on the same event. Gated by CONFIG_PERF_AUX_KERNEL_KUNIT_TEST. Co-developed-by: Kunwu Chan <kunwu.chan@gmail.com> Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com> Signed-off-by: Lian Wang <lianux.mm@gmail.com> --- kernel/events/Makefile | 1 + kernel/events/aux_kernel_test.c | 1144 +++++++++++++++++++++++++++++++ lib/Kconfig.debug | 15 + 3 files changed, 1160 insertions(+) create mode 100644 kernel/events/aux_kernel_test.c diff --git a/kernel/events/Makefile b/kernel/events/Makefile index 91a62f566743..592604db2c76 100644 --- a/kernel/events/Makefile +++ b/kernel/events/Makefile @@ -3,4 +3,5 @@ obj-y := core.o ring_buffer.o callchain.o obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o obj-$(CONFIG_HW_BREAKPOINT_KUNIT_TEST) += hw_breakpoint_test.o +obj-$(CONFIG_PERF_AUX_KERNEL_KUNIT_TEST) += aux_kernel_test.o obj-$(CONFIG_UPROBES) += uprobes.o diff --git a/kernel/events/aux_kernel_test.c b/kernel/events/aux_kernel_test.c new file mode 100644 index 000000000000..0b5f787c5979 --- /dev/null +++ b/kernel/events/aux_kernel_test.c @@ -0,0 +1,1144 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the perf AUX kernel-consumer API. + * + * Tests the exported functions: + * - perf_event_setup_aux() / perf_event_release_aux() + * - perf_event_aux_head() / perf_event_aux_tail_set() / perf_event_aux_copy() + * + * A self-contained dummy AUX PMU is registered in suite_init so that + * every test is deterministic and requires no hardware. The dummy + * setup_aux pre-fills all pages with a recognisable pattern (0xAB); + * aux_test_produce() advances the published head via + * perf_aux_output_begin/end, and copy tests verify the pattern. + * + * Run via debugfs: + * echo run > /sys/kernel/debug/kunit/perf_aux_kernel/run + * cat /sys/kernel/debug/kunit/perf_aux_kernel/results + */ + +#include <kunit/test.h> +#include <linux/kthread.h> +#include <linux/perf_event.h> +#include <linux/slab.h> +#include <linux/smp.h> +#include <linux/string.h> +#include <linux/wait.h> +#include "internal.h" + +/* ---- Dummy AUX PMU ---- */ + +static struct pmu dummy_aux_pmu; +static int dummy_aux_pmu_type = -1; + +static int dummy_aux_event_init(struct perf_event *event) +{ + return 0; +} + +static void dummy_aux_event_read(struct perf_event *event) { } + +static int dummy_aux_event_add(struct perf_event *event, int mode) +{ + return 0; +} + +static void dummy_aux_event_del(struct perf_event *event, int mode) { } + +static void dummy_aux_event_start(struct perf_event *event, int mode) { } + +static void dummy_aux_event_stop(struct perf_event *event, int mode) { } + +static void *dummy_aux_setup_aux(struct perf_event *event, void **pages, + int nr_pages, bool overwrite) +{ + int i; + + for (i = 0; i < nr_pages; i++) + memset(pages[i], 0xAB, PAGE_SIZE); + + return pages; +} + +static void dummy_aux_free_aux(void *priv) +{ + /* aux_priv is the pages array; __rb_free_aux frees it via kfree. */ +} + +static int dummy_aux_pmu_register(void) +{ + int ret; + + if (dummy_aux_pmu_type >= 0) + return 0; + + memset(&dummy_aux_pmu, 0, sizeof(dummy_aux_pmu)); + dummy_aux_pmu.event_init = dummy_aux_event_init; + dummy_aux_pmu.add = dummy_aux_event_add; + dummy_aux_pmu.del = dummy_aux_event_del; + dummy_aux_pmu.start = dummy_aux_event_start; + dummy_aux_pmu.stop = dummy_aux_event_stop; + dummy_aux_pmu.read = dummy_aux_event_read; + dummy_aux_pmu.setup_aux = dummy_aux_setup_aux; + dummy_aux_pmu.free_aux = dummy_aux_free_aux; + dummy_aux_pmu.capabilities = PERF_PMU_CAP_ITRACE; + dummy_aux_pmu.task_ctx_nr = perf_sw_context; + + ret = perf_pmu_register(&dummy_aux_pmu, "dummy_aux", -1); + if (ret) + return ret; + + dummy_aux_pmu_type = dummy_aux_pmu.type; + return 0; +} + +static void dummy_aux_pmu_unregister(void) +{ + if (dummy_aux_pmu_type >= 0) + perf_pmu_unregister(&dummy_aux_pmu); + dummy_aux_pmu_type = -1; +} + +static int suite_init(struct kunit_suite *suite) +{ + return dummy_aux_pmu_register(); +} + +static void suite_exit(struct kunit_suite *suite) +{ + dummy_aux_pmu_unregister(); +} + +/* ---- Helpers ---- */ + +static struct perf_event *aux_test_create_event(void) +{ + struct perf_event_attr attr = {}; + + attr.type = dummy_aux_pmu_type; + attr.size = sizeof(attr); + attr.disabled = 1; + + return perf_event_create_kernel_counter(&attr, raw_smp_processor_id(), + NULL, NULL, NULL); +} + +/* + * Advance the AUX head by @bytes via perf_aux_output_begin/end. + * The pages are pre-filled with 0xAB in dummy_aux_setup_aux, so + * the data is already present; this function only publishes it. + */ +static unsigned long aux_test_produce(struct perf_event *event, unsigned long bytes) +{ + struct perf_output_handle handle; + unsigned long head, written = 0; + + while (written < bytes) { + unsigned long chunk = min_t(unsigned long, bytes - written, 1024); + + if (!perf_aux_output_begin(&handle, event)) + break; + chunk = min_t(unsigned long, chunk, handle.size); + perf_aux_output_end(&handle, chunk); + written += chunk; + } + + head = perf_event_aux_head(event); + return head; +} + +/* ---- Patch-1: Setup / release tests ---- */ + +static void test_setup_rejects_non_power_of_two(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 3, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + ret = perf_event_setup_aux(event, 0, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + ret = perf_event_setup_aux(event, 6, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_kernel(event); +} + +static void test_setup_accepts_power_of_two(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int i, ret; + int pages[] = { 1, 2, 4, 8, 16, 32, 64 }; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + for (i = 0; i < ARRAY_SIZE(pages); i++) { + ret = perf_event_setup_aux(event, pages[i], 0); + KUNIT_EXPECT_EQ_MSG(test, 0, ret, + "setup_aux(%d pages) expected 0, got %d", + pages[i], ret); + if (ret == 0) + perf_event_release_aux(event); + } + + perf_event_release_kernel(event); +} + +static void test_setup_rejects_double(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, -EBUSY, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_setup_parent_event_rejected(struct kunit *test) +{ + struct perf_event *parent = aux_test_create_event(); + struct perf_event *child = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, parent); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, child); + + child->parent = parent; + + ret = perf_event_setup_aux(child, 1, 0); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + child->parent = NULL; + + perf_event_release_kernel(child); + perf_event_release_kernel(parent); +} + +static void test_writer_admitted_after_setup(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_writer_blocked_after_release(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + struct perf_output_handle handle; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + + addr = perf_aux_output_begin(&handle, event); + KUNIT_EXPECT_NULL(test, addr); + + perf_event_release_kernel(event); +} + +static void test_release_noop_without_rb(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + perf_event_release_aux(event); + KUNIT_EXPECT_TRUE(test, true); + + perf_event_release_kernel(event); +} + +static void test_setup_release_roundtrip(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + KUNIT_EXPECT_TRUE(test, true); + + perf_event_release_kernel(event); +} + +static void test_multi_setup_release_cycle(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int i, ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + for (i = 0; i < 3; i++) { + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_ASSERT_EQ_MSG(test, 0, ret, + "cycle %d: setup_aux failed", i); + perf_event_release_aux(event); + } + + perf_event_release_kernel(event); +} + +static void test_double_release_clean(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_aux(event); + KUNIT_EXPECT_TRUE(test, true); + + perf_event_release_kernel(event); +} + +/* ---- Patch-2: Accessor tests ---- */ + +static void test_head_initial_zero(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = perf_event_aux_head(event); + KUNIT_EXPECT_EQ(test, 0, head); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_head_advances_after_produce(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_full_window(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + const unsigned long buf_size = PAGE_SIZE; + void *buf; + unsigned long head; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, buf_size / 2); + KUNIT_EXPECT_GT(test, head, 0); + + buf = kunit_kmalloc(test, head, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied = perf_event_aux_copy(event, 0, head, buf); + KUNIT_EXPECT_EQ_MSG(test, (long)head, copied, + "copy(0, %lu) returned %ld, expected %lu", + head, copied, head); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_wrap(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + const unsigned long buf_size = 2 * PAGE_SIZE; + void *buf; + unsigned long head, tail; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 2, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Fill the buffer to near-full */ + head = aux_test_produce(event, buf_size); + KUNIT_EXPECT_GT(test, head, 0); + + /* Consume half to free space */ + tail = head / 2; + ret = perf_event_aux_tail_set(event, tail); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Produce more — head now wraps past buf_size */ + head = aux_test_produce(event, buf_size); + KUNIT_EXPECT_GT(test, head, buf_size); + + /* Copy a window that spans the ring wrap point */ + buf = kunit_kmalloc(test, buf_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied = perf_event_aux_copy(event, tail, head, buf); + KUNIT_EXPECT_GT(test, copied, 0); + KUNIT_EXPECT_LE(test, copied, (long)buf_size); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_zero_len(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + buf = kunit_kmalloc(test, 1, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied = perf_event_aux_copy(event, 0, 0, buf); + KUNIT_EXPECT_EQ(test, 0, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_rewound(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + const unsigned long buf_size = PAGE_SIZE; + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + aux_test_produce(event, buf_size / 2); + + buf = kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* from < to: rewound cursor */ + copied = perf_event_aux_copy(event, 100, 50, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_future(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + unsigned long head; + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + buf = kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* to > head: future cursor */ + copied = perf_event_aux_copy(event, head, head + 64, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_oversized(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + const unsigned long buf_size = PAGE_SIZE; + void *buf; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + aux_test_produce(event, 512); + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* window > buf_size */ + copied = perf_event_aux_copy(event, 0, 2 * buf_size, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_null_buf(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + copied = perf_event_aux_copy(event, 0, 0, NULL); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_no_rb_enoent(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + void *buf; + long ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + buf = kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + ret = perf_event_aux_copy(event, 0, 64, buf); + KUNIT_EXPECT_EQ(test, -ENOENT, ret); + + perf_event_release_kernel(event); +} + +static void test_tail_set_valid_frees_space(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + ret = perf_event_aux_tail_set(event, head / 2); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_rejects_future(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* tail=1 when head=0: future */ + ret = perf_event_aux_tail_set(event, 1); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_accepts_within_ring_window(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + const unsigned long buf_size = PAGE_SIZE; + unsigned long head, tail; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Produce enough data so head - tail <= aux_size for a large tail */ + head = aux_test_produce(event, buf_size / 2); + KUNIT_EXPECT_GT(test, head, 0); + + /* + * Set tail to a non-trivial value within the [0, head] window. + * This is NOT a no-op: old_tail=0, tail=head/4, advance=head/4. + */ + tail = head / 4; + ret = perf_event_aux_tail_set(event, tail); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_rejects_behind_ring(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + const unsigned long buf_size = PAGE_SIZE; + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, buf_size / 2); + KUNIT_EXPECT_GT(test, head, 0); + + /* + * tail more than one ring behind head: head - tail > aux_size. + * This should be rejected by the hardened code. + */ + ret = perf_event_aux_tail_set(event, head + buf_size + 1); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_no_rb_enoent(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_aux_tail_set(event, 0); + KUNIT_EXPECT_EQ(test, -ENOENT, ret); + + perf_event_release_kernel(event); +} + +static void test_head_no_rb_zero(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + unsigned long head; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + head = perf_event_aux_head(event); + KUNIT_EXPECT_EQ(test, 0, head); + + perf_event_release_kernel(event); +} + +/* ---- Additional boundary and correctness tests ---- */ + +static void test_setup_rejects_negative_watermark(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, -1); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + ret = perf_event_setup_aux(event, 1, -4096); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + perf_event_release_kernel(event); +} + +static void test_tail_set_noop(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret = perf_event_aux_tail_set(event, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_tail_set_consume_all(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + unsigned long head; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + ret = perf_event_aux_tail_set(event, head); + KUNIT_EXPECT_EQ(test, 0, ret); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_data_correctness(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + void *buf; + unsigned long head; + long copied; + int ret, i; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + buf = kunit_kmalloc(test, head, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied = perf_event_aux_copy(event, 0, head, buf); + KUNIT_EXPECT_EQ(test, (long)head, copied); + + for (i = 0; i < copied; i++) { + KUNIT_EXPECT_EQ_MSG(test, 0xAB, ((u8 *)buf)[i], + "data mismatch at offset %d", i); + if (((u8 *)buf)[i] != 0xAB) + break; + } + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_multi_produce_copy_cycle(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + void *buf; + unsigned long head, tail = 0; + long copied; + int ret, cycle; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + for (cycle = 0; cycle < 3; cycle++) { + head = aux_test_produce(event, 256); + KUNIT_EXPECT_GT(test, head, tail); + + copied = perf_event_aux_copy(event, tail, head, buf); + KUNIT_EXPECT_EQ(test, (long)(head - tail), copied); + + ret = perf_event_aux_tail_set(event, head); + KUNIT_EXPECT_EQ(test, 0, ret); + tail = head; + } + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_copy_rejects_already_consumed(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + void *buf; + unsigned long head, tail; + long copied; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, 512); + KUNIT_EXPECT_GT(test, head, 0); + + tail = head / 2; + ret = perf_event_aux_tail_set(event, tail); + KUNIT_EXPECT_EQ(test, 0, ret); + + buf = kunit_kmalloc(test, 64, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + copied = perf_event_aux_copy(event, tail - 1, tail, buf); + KUNIT_EXPECT_EQ(test, -EINVAL, copied); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_output_end_zero_size(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + struct perf_output_handle handle; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + addr = perf_aux_output_begin(&handle, event); + KUNIT_EXPECT_NOT_NULL(test, addr); + + perf_aux_output_end(&handle, 0); + KUNIT_EXPECT_EQ(test, 0, perf_event_aux_head(event)); + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_buffer_full_stops_producer(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + struct perf_output_handle handle; + const unsigned long buf_size = PAGE_SIZE; + unsigned long head; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head = aux_test_produce(event, buf_size); + KUNIT_EXPECT_GT(test, head, 0); + + addr = perf_aux_output_begin(&handle, event); + if (addr) { + KUNIT_EXPECT_EQ(test, 0, handle.size); + perf_aux_output_end(&handle, 0); + } + + perf_event_release_aux(event); + perf_event_release_kernel(event); +} + +static void test_setup_with_explicit_watermark(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 2, 4096); + KUNIT_EXPECT_EQ(test, 0, ret); + perf_event_release_aux(event); + + ret = perf_event_setup_aux(event, 4, 8192); + KUNIT_EXPECT_EQ(test, 0, ret); + perf_event_release_aux(event); + + perf_event_release_kernel(event); +} + +static void test_two_events_independent(struct kunit *test) +{ + struct perf_event *ev_a = aux_test_create_event(); + struct perf_event *ev_b = aux_test_create_event(); + unsigned long head_a, head_b; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_a); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_b); + + ret = perf_event_setup_aux(ev_a, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret = perf_event_setup_aux(ev_b, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head_a = aux_test_produce(ev_a, 512); + head_b = perf_event_aux_head(ev_b); + + KUNIT_EXPECT_GT(test, head_a, 0); + KUNIT_EXPECT_EQ(test, 0, head_b); + + head_b = aux_test_produce(ev_b, 256); + KUNIT_EXPECT_GT(test, head_b, 0); + KUNIT_EXPECT_EQ(test, head_a, perf_event_aux_head(ev_a)); + + perf_event_release_aux(ev_a); + perf_event_release_aux(ev_b); + perf_event_release_kernel(ev_a); + perf_event_release_kernel(ev_b); +} + +static void test_release_one_does_not_affect_other(struct kunit *test) +{ + struct perf_event *ev_a = aux_test_create_event(); + struct perf_event *ev_b = aux_test_create_event(); + unsigned long head_b; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_a); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_b); + + ret = perf_event_setup_aux(ev_a, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ret = perf_event_setup_aux(ev_b, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + head_b = aux_test_produce(ev_b, 512); + KUNIT_EXPECT_GT(test, head_b, 0); + + perf_event_release_aux(ev_a); + + KUNIT_EXPECT_EQ(test, head_b, perf_event_aux_head(ev_b)); + KUNIT_EXPECT_GT(test, aux_test_produce(ev_b, 256), head_b); + + perf_event_release_aux(ev_b); + perf_event_release_kernel(ev_a); + perf_event_release_kernel(ev_b); +} + +/* + * Test: kernel consumer and simulated userspace consumer coexist on + * the same PMU without interfering. + * + * Event A uses aux_kernel_count (via perf_event_setup_aux). + * Event B swaps its refcounts to aux_mmap_count=1 to simulate a + * userspace mmap'd consumer. Both rings on the same dummy PMU. + * + * - produce on A → B's head is unaffected + * - produce on B → A's head is unaffected + * - release A (kernel) → B still produces + */ +static void test_user_kernel_coexistence(struct kunit *test) +{ + struct perf_event *ev_a = aux_test_create_event(); + struct perf_event *ev_b = aux_test_create_event(); + struct perf_output_handle handle_a, handle_b; + unsigned long a_head_before, b_head_before; + void *addr; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_a); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ev_b); + + /* Event A: kernel consumer */ + ret = perf_event_setup_aux(ev_a, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Event B: start with kernel setup, then simulate userspace */ + ret = perf_event_setup_aux(ev_b, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* Simulate userspace ownership: swap refcounts on B's rb. + * perf_mmap() would set aux_mmap_count=1 for a userspace event. + */ + refcount_set(&ev_b->rb->aux_mmap_count, 1); + refcount_set(&ev_b->rb->aux_kernel_count, 0); + + /* Produce on A — B's head stays 0 */ + addr = perf_aux_output_begin(&handle_a, ev_a); + KUNIT_EXPECT_NOT_NULL(test, addr); + perf_aux_output_end(&handle_a, 512); + KUNIT_EXPECT_GT(test, ev_a->rb->aux_head, 0); + KUNIT_EXPECT_EQ(test, 0, ev_b->rb->aux_head); + + /* Produce on B — A's head unchanged */ + a_head_before = ev_a->rb->aux_head; + addr = perf_aux_output_begin(&handle_b, ev_b); + KUNIT_EXPECT_NOT_NULL(test, addr); + perf_aux_output_end(&handle_b, 256); + KUNIT_EXPECT_GT(test, ev_b->rb->aux_head, 0); + KUNIT_EXPECT_EQ(test, a_head_before, ev_a->rb->aux_head); + + /* Release A (kernel) — B still produces */ + b_head_before = ev_b->rb->aux_head; + perf_event_release_aux(ev_a); + addr = perf_aux_output_begin(&handle_b, ev_b); + KUNIT_EXPECT_NOT_NULL(test, addr); + perf_aux_output_end(&handle_b, 128); + KUNIT_EXPECT_GT(test, ev_b->rb->aux_head, b_head_before); + + /* Cleanup B: restore kernel ownership for proper release */ + refcount_set(&ev_b->rb->aux_kernel_count, 1); + refcount_set(&ev_b->rb->aux_mmap_count, 0); + perf_event_release_aux(ev_b); + perf_event_release_kernel(ev_a); + perf_event_release_kernel(ev_b); +} + +/* + * Test: two kthreads concurrently call perf_event_release_aux() on + * the same event. Verifies that the refcount_dec_and_mutex_lock + * serialisation and the event->rb == rb guard in the detach block + * correctly handle the race without crashing or leaking. + */ +struct concurrent_release_ctx { + struct perf_event *event; + atomic_t count; + wait_queue_head_t wq; +}; + +static int concurrent_release_thread(void *data) +{ + struct concurrent_release_ctx *ctx = data; + + perf_event_release_aux(ctx->event); + + if (atomic_dec_and_test(&ctx->count)) + wake_up(&ctx->wq); + return 0; +} + +static void test_concurrent_release(struct kunit *test) +{ + struct perf_event *event = aux_test_create_event(); + struct concurrent_release_ctx ctx; + struct task_struct *threads[2]; + int ret, i; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); + + ret = perf_event_setup_aux(event, 1, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + ctx.event = event; + atomic_set(&ctx.count, ARRAY_SIZE(threads)); + init_waitqueue_head(&ctx.wq); + + for (i = 0; i < ARRAY_SIZE(threads); i++) { + threads[i] = kthread_run(concurrent_release_thread, &ctx, + "aux_crel_%d", i); + if (IS_ERR_OR_NULL(threads[i])) { + kunit_skip(test, "failed to create kthread"); + while (--i >= 0) + kthread_stop(threads[i]); + perf_event_release_aux(event); + perf_event_release_kernel(event); + return; + } + } + + wait_event(ctx.wq, atomic_read(&ctx.count) == 0); + + perf_event_release_kernel(event); +} + +static struct kunit_case aux_kernel_test_cases[] = { + KUNIT_CASE(test_setup_rejects_non_power_of_two), + KUNIT_CASE(test_setup_accepts_power_of_two), + KUNIT_CASE(test_setup_rejects_double), + KUNIT_CASE(test_setup_parent_event_rejected), + KUNIT_CASE(test_setup_rejects_negative_watermark), + KUNIT_CASE(test_setup_with_explicit_watermark), + KUNIT_CASE(test_writer_admitted_after_setup), + KUNIT_CASE(test_writer_blocked_after_release), + KUNIT_CASE(test_release_noop_without_rb), + KUNIT_CASE(test_setup_release_roundtrip), + KUNIT_CASE(test_multi_setup_release_cycle), + KUNIT_CASE(test_double_release_clean), + KUNIT_CASE(test_two_events_independent), + KUNIT_CASE(test_release_one_does_not_affect_other), + KUNIT_CASE(test_user_kernel_coexistence), + KUNIT_CASE(test_concurrent_release), + KUNIT_CASE(test_head_initial_zero), + KUNIT_CASE(test_head_advances_after_produce), + KUNIT_CASE(test_head_no_rb_zero), + KUNIT_CASE(test_copy_full_window), + KUNIT_CASE(test_copy_wrap), + KUNIT_CASE(test_copy_zero_len), + KUNIT_CASE(test_copy_data_correctness), + KUNIT_CASE(test_copy_rejects_rewound), + KUNIT_CASE(test_copy_rejects_future), + KUNIT_CASE(test_copy_rejects_oversized), + KUNIT_CASE(test_copy_rejects_null_buf), + KUNIT_CASE(test_copy_rejects_already_consumed), + KUNIT_CASE(test_copy_no_rb_enoent), + KUNIT_CASE(test_tail_set_valid_frees_space), + KUNIT_CASE(test_tail_set_rejects_future), + KUNIT_CASE(test_tail_set_accepts_within_ring_window), + KUNIT_CASE(test_tail_set_rejects_behind_ring), + KUNIT_CASE(test_tail_set_noop), + KUNIT_CASE(test_tail_set_consume_all), + KUNIT_CASE(test_tail_set_no_rb_enoent), + KUNIT_CASE(test_multi_produce_copy_cycle), + KUNIT_CASE(test_output_end_zero_size), + KUNIT_CASE(test_buffer_full_stops_producer), + {}, +}; + +static struct kunit_suite aux_kernel_test_suite = { + .name = "perf_aux_kernel", + .suite_init = suite_init, + .suite_exit = suite_exit, + .test_cases = aux_kernel_test_cases, +}; + +kunit_test_suite(aux_kernel_test_suite); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1244dcac2294..30f4ab1dd791 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3102,6 +3102,21 @@ config HW_BREAKPOINT_KUNIT_TEST If unsure, say N. +config PERF_AUX_KERNEL_KUNIT_TEST + bool "Test perf AUX kernel-consumer API" if !KUNIT_ALL_TESTS + depends on PERF_EVENTS=y + depends on KUNIT=y + default KUNIT_ALL_TESTS + help + Tests for the perf AUX kernel-consumer API functions: + perf_event_setup_aux(), perf_event_release_aux(), + perf_event_aux_head(), perf_event_aux_tail_set(), and + perf_event_aux_copy(). Error-path contract tests work + without hardware; happy-path tests require an AUX-capable + PMU (ARM SPE, Intel PT, etc.). + + If unsure, say N. + source "lib/crypto/tests/Kconfig" config SIPHASH_KUNIT_TEST -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API 2026-08-14 14:49 ` [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API Kunwu Chan @ 2026-08-14 15:02 ` sashiko-bot 0 siblings, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-08-14 15:02 UTC (permalink / raw) To: Kunwu Chan; +Cc: linux-perf-users Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider: - [High] KUnit assertions (`KUNIT_ASSERT_*`) and `kunit_skip()` abort tests immediately, leaking resources and causing stack Use-After-Free. - [High] Race between `atomic_dec_and_test()` and lockless `wait_event()` causes a stack Use-After-Free in `test_concurrent_release()`. - [High] Test cases use non-fatal `KUNIT_EXPECT_*` macros for critical setup steps, resulting in uninitialized stack usage and NULL pointer dereferences. - [High] `test_concurrent_release` triggers a `refcount_t` underflow kernel WARNING via concurrent `perf_event_release_aux()` calls. - [High] Error path in `test_concurrent_release` calls `kthread_stop()` on natively exiting threads, leading to Use-After-Free. - [Medium] Dead code in `test_buffer_full_stops_producer` masks actual API behavior and fails to enforce expected outcomes. -- --- Patch [3]: [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API --- commit 7ac72ec86c4a7abdecf89cc4cddc373879a7f3b2 Author: Lian Wang <lianux.mm@gmail.com> perf/core: add KUnit tests for AUX kernel-consumer API Adds a KUnit test suite (perf_aux_kernel) for the perf_event_setup_aux() and related functions. Registers a self-contained dummy AUX PMU to run tests deterministically without hardware. Includes 39 test cases covering lifecycle, writers, ring buffer accessors, and concurrency. > diff --git a/kernel/events/aux_kernel_test.c b/kernel/events/aux_kernel_test.c > new file mode 100644 > index 0000000000000..0b5f787c59799 > --- /dev/null > +++ b/kernel/events/aux_kernel_test.c [ ... ] > +static void test_copy_full_window(struct kunit *test) > +{ > + struct perf_event *event = aux_test_create_event(); [ ... ] > + ret = perf_event_setup_aux(event, 1, 0); > + KUNIT_EXPECT_EQ(test, 0, ret); > + > + head = aux_test_produce(event, buf_size / 2); > + KUNIT_EXPECT_GT(test, head, 0); > + > + buf = kunit_kmalloc(test, head, GFP_KERNEL); > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); [Severity: High] Does this assertion leak the perf_event structure? KUnit assertions like KUNIT_ASSERT_NOT_ERR_OR_NULL abort the test immediately by throwing a KUnit exception. If this allocation fails after aux_test_create_event() succeeds, the test will abort without calling perf_event_release_kernel(), causing a permanent resource leak. [ ... ] > +static void test_buffer_full_stops_producer(struct kunit *test) > +{ [ ... ] > + head = aux_test_produce(event, buf_size); > + KUNIT_EXPECT_GT(test, head, 0); > + > + addr = perf_aux_output_begin(&handle, event); > + if (addr) { > + KUNIT_EXPECT_EQ(test, 0, handle.size); > + perf_aux_output_end(&handle, 0); > + } [Severity: Medium] Does this block fail to verify the expected behavior? When the buffer is full, perf_aux_output_begin() correctly returns NULL. Because addr is NULL, this conditional block is skipped entirely, turning the core validation into dead code. This allows the test to silently pass even if perf_aux_output_begin() incorrectly returns a non-NULL pointer with an arbitrary size. Would it be better to explicitly assert KUNIT_EXPECT_NULL(test, addr)? [ ... ] > +static void test_user_kernel_coexistence(struct kunit *test) > +{ [ ... ] > + /* Event B: start with kernel setup, then simulate userspace */ > + ret = perf_event_setup_aux(ev_b, 1, 0); > + KUNIT_EXPECT_EQ(test, 0, ret); > + > + /* Simulate userspace ownership: swap refcounts on B's rb. > + * perf_mmap() would set aux_mmap_count=1 for a userspace event. > + */ > + refcount_set(&ev_b->rb->aux_mmap_count, 1); [Severity: High] Can this result in a NULL pointer dereference? The KUNIT_EXPECT_EQ macro logs failures but allows execution to continue. If perf_event_setup_aux() fails, ev_b->rb will be NULL. The test then proceeds to evaluate refcount_set(&ev_b->rb->aux_mmap_count, 1), which dereferences it. Should KUNIT_ASSERT_EQ be used for critical setup steps? [ ... ] > + /* Produce on A — B's head stays 0 */ > + addr = perf_aux_output_begin(&handle_a, ev_a); > + KUNIT_EXPECT_NOT_NULL(test, addr); > + perf_aux_output_end(&handle_a, 512); [Severity: High] Can this sequence cause a kernel panic? Similar to the previous setup step, if perf_aux_output_begin() fails and returns NULL, handle_a remains uninitialized since KUNIT_EXPECT_NOT_NULL does not halt the test. Continuing past the failure passes this uninitialized handle to perf_aux_output_end(), which will dereference garbage data from handle_a.rb. [ ... ] > +struct concurrent_release_ctx { > + struct perf_event *event; > + atomic_t count; > + wait_queue_head_t wq; > +}; > + > +static int concurrent_release_thread(void *data) > +{ > + struct concurrent_release_ctx *ctx = data; > + > + perf_event_release_aux(ctx->event); > + > + if (atomic_dec_and_test(&ctx->count)) > + wake_up(&ctx->wq); > + return 0; > +} [Severity: High] Can decoupling the atomic decrement and the wake_up() call create a stack use-after-free window? The main thread waits for worker threads using wait_event(), which evaluates its condition locklessly. If the last worker thread executes atomic_dec_and_test(&ctx->count), setting it to 0, the main thread can observe the 0, break out of wait_event(), and return from the function before the worker executes wake_up(&ctx->wq). Returning from test_concurrent_release() destroys the ctx struct on the stack, meaning the worker thread will then call wake_up() on a destroyed stack frame. Could this be avoided by using the struct completion API, which is explicitly designed to prevent this race for on-stack waiters? [ ... ] > +static void test_concurrent_release(struct kunit *test) > +{ > + struct perf_event *event = aux_test_create_event(); > + struct concurrent_release_ctx ctx; > + struct task_struct *threads[2]; > + int ret, i; [ ... ] > + for (i = 0; i < ARRAY_SIZE(threads); i++) { > + threads[i] = kthread_run(concurrent_release_thread, &ctx, > + "aux_crel_%d", i); [Severity: High] Does intentionally launching concurrent releases here trigger a refcount_t underflow warning? In perf_event_release_aux() in core.c, the check !refcount_read(&rb->aux_kernel_count) is performed locklessly. Both worker threads can read aux_kernel_count as 1 and proceed to call refcount_dec_and_mutex_lock(). The first thread successfully decrements the count from 1 to 0. The second thread subsequently calls refcount_dec_and_mutex_lock() on a count of 0, which triggers a REFCOUNT_WARN("underflow; use-after-free.") inside __refcount_dec_and_test(). Is it intended for a KUnit test to trigger kernel warnings on a successful path? > + if (IS_ERR_OR_NULL(threads[i])) { > + kunit_skip(test, "failed to create kthread"); [Severity: High] Can kunit_skip() cause a stack use-after-free if a thread is already running? kunit_skip() terminates the test immediately by throwing a KUnit exception, bypassing any subsequent cleanup code. This means the test function returns instantly, destroying the ctx struct allocated on the main thread's stack. If an earlier thread was successfully spawned and is still running, it will access ctx->event and ctx->count on the destroyed stack frame. > + while (--i >= 0) > + kthread_stop(threads[i]); [Severity: High] Does kthread_stop() cause a use-after-free on natively exiting threads? The concurrent_release_thread function does not check kthread_should_stop(); it simply executes and naturally returns 0. When a thread naturally exits, its task_struct is reaped. Calling kthread_stop() here on a potentially reaped task_struct will access freed memory inside get_task_struct(). While currently masked as dead code due to kunit_skip() aborting execution above, this cleanup logic appears unsafe. > + perf_event_release_aux(event); > + perf_event_release_kernel(event); > + return; > + } > + } -- Sashiko AI review · https://sashiko.dev/#/patchset/20260814144927.489172-1-kunwu.chan@linux.dev?part=3 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test 2026-08-14 14:49 [RFC PATCH 0/5] perf/core: add AUX buffer kernel-consumer API Kunwu Chan ` (2 preceding siblings ...) 2026-08-14 14:49 ` [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API Kunwu Chan @ 2026-08-14 14:49 ` Kunwu Chan 2026-08-14 14:59 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script Kunwu Chan 4 siblings, 1 reply; 11+ messages in thread From: Kunwu Chan @ 2026-08-14 14:49 UTC (permalink / raw) To: corbet, skhan, peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter, james.clark, kunwu.chan, lianux.mm Cc: linux-doc, linux-kernel, linux-perf-users, linux-kselftest, sj From: Lian Wang <lianux.mm@gmail.com> 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 <kunwu.chan@gmail.com> Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com> Signed-off-by: Lian Wang <lianux.mm@gmail.com> --- 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 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test 2026-08-14 14:49 ` [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test Kunwu Chan @ 2026-08-14 14:59 ` sashiko-bot 0 siblings, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-08-14 14:59 UTC (permalink / raw) To: Kunwu Chan; +Cc: linux-perf-users Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Multiple test functions have fundamental logic flaws that cause them to vacuously pass or unconditionally skip, failing to validate the AUX buffer behavior they are intended to test. -- --- Patch [4]: [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test --- commit d0e63b73f16bd8ebf0771524fc82ef084c85ea92 Author: Lian Wang <lianux.mm@gmail.com> selftests/perf_events: add userspace AUX regression test This commit adds a standalone C program to test the perf event AUX userspace mmap protocol. It opens a perf event, mmaps the metadata/data rings, and sets up the AUX area to ensure kernel-side changes do not break existing behavior. > 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 0000000000000..c3d2323d08797 > --- /dev/null > +++ b/tools/testing/selftests/perf_events/aux_kernel_usermode.c [ ... ] > @@ -267,23 +267,23 @@ static int test_aux_mmap_independence(int pmu_type) > 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); [Severity: Medium] Does test_aux_mmap_independence() actually allocate an AUX buffer? This code only mmaps the standard perf metadata/data ring. It appears to omit the setup and second mmap() calls required to actually allocate an AUX buffer for either file descriptor. [ ... ] > @@ -359,20 +359,20 @@ static int test_mmap_page_fields(int pmu_type) > 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; > } [Severity: Medium] Does this cause test_mmap_page_fields() to unconditionally skip? The aux_offset is a user-space defined field, but this checks it immediately after the primary mmap() when the kernel has zero-initialized the page. [ ... ] > @@ -411,20 +411,20 @@ static int test_aux_head_monotonic(int pmu_type) > > 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; > } [Severity: Medium] Should this use a skip or fail mechanism instead? Returning PASS on a failure to open the event could mask regressions and inflate the pass rate. [ ... ] > @@ -443,20 +443,20 @@ static int test_aux_head_monotonic(int pmu_type) > 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; > } [Severity: Medium] Similarly, should this report a skip or failure instead of PASS when the event fails to enable? [ ... ] > @@ -453,20 +453,20 @@ static int test_aux_head_monotonic(int pmu_type) > } > > /* 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); > } [Severity: Medium] How does this verify that the head was monotonically increasing? Since both branches result in PASS, the test doesn't seem to assert any failure condition or ensure that AUX data was actually generated. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260814144927.489172-1-kunwu.chan@linux.dev?part=4 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script 2026-08-14 14:49 [RFC PATCH 0/5] perf/core: add AUX buffer kernel-consumer API Kunwu Chan ` (3 preceding siblings ...) 2026-08-14 14:49 ` [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test Kunwu Chan @ 2026-08-14 14:49 ` Kunwu Chan 2026-08-14 14:56 ` sashiko-bot 4 siblings, 1 reply; 11+ messages in thread From: Kunwu Chan @ 2026-08-14 14:49 UTC (permalink / raw) To: corbet, skhan, peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter, james.clark, kunwu.chan, lianux.mm Cc: linux-doc, linux-kernel, linux-perf-users, linux-kselftest, sj From: Kunwu Chan <kunwu.chan@gmail.com> Add a shell script that runs the perf_aux_kernel KUnit suite through debugfs, builds and runs the userspace regression program, and performs static-analysis checks on the source tree (memory barriers, refcount types, boundary validation, EXPORT_SYMBOL_GPL coverage). When an AUX-capable PMU is present, the script also runs a live perf record and checks dmesg for errors. Usage: sudo ./aux_kernel.sh [--pmu arm_spe_0] Co-developed-by: Lian Wang (Processmission) <lianux.mm@gmail.com> Signed-off-by: Lian Wang (Processmission) <lianux.mm@gmail.com> Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com> --- tools/testing/selftests/perf_events/Makefile | 1 + .../selftests/perf_events/aux_kernel.sh | 563 ++++++++++++++++++ 2 files changed, 564 insertions(+) create mode 100755 tools/testing/selftests/perf_events/aux_kernel.sh diff --git a/tools/testing/selftests/perf_events/Makefile b/tools/testing/selftests/perf_events/Makefile index fbafbd43f097..9b82c7ee1238 100644 --- a/tools/testing/selftests/perf_events/Makefile +++ b/tools/testing/selftests/perf_events/Makefile @@ -4,4 +4,5 @@ LDFLAGS += -lpthread TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal mmap TEST_GEN_PROGS += aux_kernel_usermode +TEST_PROGS += aux_kernel.sh include ../lib.mk diff --git a/tools/testing/selftests/perf_events/aux_kernel.sh b/tools/testing/selftests/perf_events/aux_kernel.sh new file mode 100755 index 000000000000..431f59ef02e7 --- /dev/null +++ b/tools/testing/selftests/perf_events/aux_kernel.sh @@ -0,0 +1,563 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Perf AUX Kernel-Consumer API - Comprehensive Selftest +# +# Validates the perf-core AUX kernel-consumer API added by the +# perf_aux_support patch series: +# - perf_event_setup_aux() / perf_event_release_aux() +# - perf_event_aux_head() / perf_event_aux_tail_set() / perf_event_aux_copy() +# +# Test layers: +# 1. Build-time checks (Kconfig, Makefile, symbol exports, source) +# 2. KUnit suite (API contract: error paths, lifecycle, validation) +# 3. Userspace AUX regression (mmap AUX still works, aux_mmap_count +# independent, userspace+kernel coexistence) +# 4. Hardware AUX live test (if ARM SPE or Intel PT is available) +# 5. DAMON integration verification +# 6. Memory ordering/barrier static analysis +# 7. Boundary validation static analysis +# 8. Concurrency safety checks +# 9. User/kernel AUX isolation checks +# +# Usage: sudo ./aux_kernel.sh [--pmu arm_spe_0|intel_pt] +# +# Requirements: +# - CONFIG_PERF_EVENTS=y +# - CONFIG_PERF_AUX_KERNEL_KUNIT_TEST=y and CONFIG_KUNIT_DEBUGFS=y +# - Root privileges for most sections + +set -e +PASSED=0; FAILED=0; SKIPPED=0 +pass() { echo " [PASS] $1"; PASSED=$((PASSED + 1)); } +fail() { echo " [FAIL] $1"; FAILED=$((FAILED + 1)); } +skip() { echo " [SKIP] $*"; SKIPPED=$((SKIPPED + 1)); } + +RESULTS_DIR="/tmp/perf_aux_kernel_test_$$" +mkdir -p "$RESULTS_DIR" +exec > >(tee "$RESULTS_DIR/output.log") 2>&1 + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)" +if [[ -z "$ROOT" ]]; then + ROOT="/usr/src/linux" +fi + +PMU_ARG="${1:-}" +if [[ "$PMU_ARG" == "--pmu" ]]; then + PMU_NAME="${2:-arm_spe_0}" +elif [[ -n "$PMU_ARG" ]]; then + PMU_NAME="$PMU_ARG" +else + PMU_NAME="arm_spe_0" +fi + +echo "==========================================" +echo " Perf AUX Kernel-Consumer API Test" +echo "==========================================" +echo "Kernel tree: $ROOT" +echo "Results dir: $RESULTS_DIR" +echo "Target PMU: $PMU_NAME" +echo "Script dir: $SCRIPT_DIR" +echo "" + +# ---- Section 1: Build-time checks ---- +echo "--- 1. Build-time Configuration ---" + +# Check that the KUnit test is built +KUNIT_SUITE="/sys/kernel/debug/kunit/perf_aux_kernel" +if [[ -d "$KUNIT_SUITE" ]]; then + pass "KUnit suite 'perf_aux_kernel' is registered" +else + skip "KUnit suite 'perf_aux_kernel' not registered" \ + "(CONFIG_PERF_AUX_KERNEL_KUNIT_TEST not set)" +fi + +# Check that required symbols are exported +SYMBOLS_FILE="/proc/kallsyms" +if [[ -r "$SYMBOLS_FILE" ]]; then + for sym in perf_event_setup_aux perf_event_release_aux \ + perf_event_aux_head perf_event_aux_tail_set perf_event_aux_copy; do + if grep -q "$sym" "$SYMBOLS_FILE" 2>/dev/null; then + pass "Symbol exported: $sym" + else + fail "Symbol not exported: $sym (patch not applied?)" + fi + done +else + skip "Symbol check" "/proc/kallsyms not readable" +fi + +# Check that the source files exist +SRC_EVENTS="$ROOT/kernel/events" +for f in core.c ring_buffer.c internal.h; do + if [[ -f "$SRC_EVENTS/$f" ]]; then + pass "Source file: kernel/events/$f" + else + fail "Source file: kernel/events/$f missing" + fi +done + +# Check that the KUnit test source exists +if [[ -f "$SRC_EVENTS/aux_kernel_test.c" ]]; then + pass "Source file: kernel/events/aux_kernel_test.c" +else + skip "Source file: kernel/events/aux_kernel_test.c not found" +fi + +# Check that aux_kernel_count is in internal.h +if grep -q 'aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/null; then + pass "struct perf_buffer: aux_kernel_count field present" +else + fail "struct perf_buffer: aux_kernel_count field missing" +fi + +# Check that aux_kernel_count is used in perf_aux_output_begin() +if grep -q 'aux_kernel_count' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "ring_buffer.c: aux_kernel_count guard in perf_aux_output_begin()" +else + fail "ring_buffer.c: aux_kernel_count guard missing in perf_aux_output_begin()" +fi + +# Check that the new API functions are in core.c / ring_buffer.c +for func in perf_event_setup_aux perf_event_release_aux; do + if grep -q "int $func\|void $func" "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "core.c: $func() defined" + else + fail "core.c: $func() not found" + fi +done +for func in perf_event_aux_head perf_event_aux_tail_set perf_event_aux_copy; do + if grep -q "$func" "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "ring_buffer.c: $func() defined" + else + fail "ring_buffer.c: $func() not found" + fi +done + +# Check that EXPORT_SYMBOL_GPL is present +for func in perf_event_setup_aux perf_event_release_aux \ + perf_event_aux_head perf_event_aux_tail_set perf_event_aux_copy; do + if grep -q "EXPORT_SYMBOL_GPL($func)" "$SRC_EVENTS/core.c" \ + "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "EXPORT_SYMBOL_GPL: $func" + else + fail "EXPORT_SYMBOL_GPL: $func not found" + fi +done + +# ---- Section 2: KUnit API contract tests ---- +echo "" +echo "--- 2. KUnit API Contract Tests ---" + +KUNIT_DIR="/sys/kernel/debug/kunit/perf_aux_kernel" +if [[ -d "$KUNIT_DIR" ]]; then + if [[ -f "$KUNIT_DIR/run" ]]; then + echo run > "$KUNIT_DIR/run" 2>/dev/null || true + fi + RES="$KUNIT_DIR/results" + if [[ -f "$RES" ]]; then + cp "$RES" "$RESULTS_DIR/kunit-results.log" + + if grep -Eq "^[[:space:]]*not ok" "$RES"; then + fail "KUnit API contract suite has failing cases" + grep -E "^[[:space:]]*not ok" "$RES" | sed 's/^/ /' + elif grep -Eq "^[[:space:]]*ok " "$RES"; then + pass "KUnit API contract suite: all cases pass" + else + fail "KUnit API contract suite produced no completed cases" + fi + echo " $(grep '# Totals:' "$RES" | tail -1)" + + # Spot-check all 38 test cases (deterministic, dummy PMU) + for c in "test_setup_rejects_non_power_of_two" \ + "test_setup_accepts_power_of_two" \ + "test_setup_rejects_double" \ + "test_setup_parent_event_rejected" \ + "test_setup_rejects_negative_watermark" \ + "test_setup_with_explicit_watermark" \ + "test_writer_admitted_after_setup" \ + "test_writer_blocked_after_release" \ + "test_release_noop_without_rb" \ + "test_setup_release_roundtrip" \ + "test_multi_setup_release_cycle" \ + "test_double_release_clean" \ + "test_two_events_independent" \ + "test_release_one_does_not_affect_other" \ + "test_user_kernel_coexistence" \ + "test_concurrent_release" \ + "test_head_initial_zero" \ + "test_head_advances_after_produce" \ + "test_head_no_rb_zero" \ + "test_copy_full_window" \ + "test_copy_wrap" \ + "test_copy_zero_len" \ + "test_copy_data_correctness" \ + "test_copy_rejects_rewound" \ + "test_copy_rejects_future" \ + "test_copy_rejects_oversized" \ + "test_copy_rejects_null_buf" \ + "test_copy_rejects_already_consumed" \ + "test_copy_no_rb_enoent" \ + "test_tail_set_valid_frees_space" \ + "test_tail_set_rejects_future" \ + "test_tail_set_accepts_within_ring_window" \ + "test_tail_set_rejects_behind_ring" \ + "test_tail_set_noop" \ + "test_tail_set_consume_all" \ + "test_tail_set_no_rb_enoent" \ + "test_multi_produce_copy_cycle" \ + "test_output_end_zero_size" \ + "test_buffer_full_stops_producer"; do + if grep -Eq "^[[:space:]]*ok .*$c" "$RES"; then + pass "KUnit case: $c" + else + fail "KUnit case: $c" + fi + done + else + skip "KUnit results" "no results file (suite did not run)" + fi +else + skip "KUnit API contract" "CONFIG_PERF_AUX_KERNEL_KUNIT_TEST" \ + "or CONFIG_KUNIT_DEBUGFS missing" +fi + +# ---- Section 3: Userspace AUX regression (C program) ---- +echo "" +echo "--- 3. Userspace AUX Regression (api-level) ---" + +# Build and run the C regression test program +UMODE_SRC="$SCRIPT_DIR/aux_kernel_usermode.c" +UMODE_BIN="$SCRIPT_DIR/aux_kernel_usermode" + +if [[ -f "$UMODE_SRC" ]]; then + if [[ ! -x "$UMODE_BIN" ]] || [[ "$UMODE_SRC" -nt "$UMODE_BIN" ]]; then + echo " Building aux_kernel_usermode..." + gcc -o "$UMODE_BIN" "$UMODE_SRC" -Wall -Wextra -O2 2>&1 | \ + sed 's/^/ /' || { + skip "aux_kernel_usermode" "build failed" + UMODE_BIN="" + } + fi + + if [[ -n "$UMODE_BIN" && -x "$UMODE_BIN" ]]; then + if "$UMODE_BIN" "$PMU_NAME" 2>&1 | tee "$RESULTS_DIR/usermode.log" | \ + grep -c '\[PASS\]' > /dev/null; then + # Count pass/fail from the output + UMODE_PASS=$(grep -c '\[PASS\]' "$RESULTS_DIR/usermode.log" || true) + UMODE_FAIL=$(grep -c '\[FAIL\]' "$RESULTS_DIR/usermode.log" || true) + UMODE_SKIP=$(grep -c '\[SKIP\]' "$RESULTS_DIR/usermode.log" || true) + pass "Userspace API regression: $UMODE_PASS passed," \ + " $UMODE_FAIL failed, $UMODE_SKIP skipped" + if [[ "$UMODE_FAIL" -gt 0 ]]; then + fail "Userspace API regression: $UMODE_FAIL failures" + fi + else + fail "Userspace API regression: C program failed" + fi + else + skip "Userspace API regression" "C program not available" + fi +else + skip "Userspace API regression" "$UMODE_SRC not found" +fi + +# Check that aux_mmap_count refcount is not affected by aux_kernel_count +# (static analysis: verify the two refcounts are separate in internal.h) +if grep -q 'aux_mmap_count' "$SRC_EVENTS/internal.h" 2>/dev/null && \ + grep -q 'aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/null; then + pass "Refcount separation: aux_mmap_count and aux_kernel_count are distinct" +else + fail "Refcount separation: aux_mmap_count or aux_kernel_count missing" +fi + +# ---- Section 4: Hardware AUX live test ---- +echo "" +echo "--- 4. Hardware AUX Live Test ---" + +PMU_DIR="/sys/bus/event_source/devices/$PMU_NAME" +PERF_BIN=$(which perf 2>/dev/null || true) +if [[ -d "$PMU_DIR" ]]; then + PMU_TYPE=$(cat "$PMU_DIR/type" 2>/dev/null || true) + if [[ -n "$PMU_TYPE" ]]; then + pass "AUX PMU $PMU_NAME present (type=$PMU_TYPE)" + + if [[ -f "$PMU_DIR/caps/aux_output" ]]; then + pass "PMU $PMU_NAME: aux_output capability present" + else + skip "PMU $PMU_NAME: aux_output capability not found" + fi + + if [[ -n "$PERF_BIN" ]]; then + DMESG_BEFORE=$(dmesg 2>/dev/null | wc -l) + if "$PERF_BIN" record -e "$PMU_NAME/period=100000/" \ + -o "$RESULTS_DIR/perf_hw.data" -- sleep 0.5 2>/dev/null; then + pass "Hardware AUX: perf record with $PMU_NAME succeeded" + else + fail "Hardware AUX: perf record with $PMU_NAME failed" + fi + + DMESG_AFTER="$RESULTS_DIR/dmesg-hardware.log" + dmesg 2>/dev/null > "$DMESG_AFTER" || true + DMESG_LINES_AFTER=$(wc -l < "$DMESG_AFTER") + if [[ "$DMESG_LINES_AFTER" -ge "$DMESG_BEFORE" ]]; then + NEW_DMESG=$(tail -n "+$((DMESG_BEFORE + 1))" "$DMESG_AFTER") + else + NEW_DMESG=$(cat "$DMESG_AFTER") + fi + FAIL_LINES=$(printf '%s\n' "$NEW_DMESG" | \ + grep -Ei "perf.*(fail|warn|error).*aux|"\ +"WARNING:|BUG:|Oops:|lockdep" || true) + if [[ -n "$FAIL_LINES" ]]; then + fail "dmesg: errors during hardware AUX test" + echo "$FAIL_LINES" | sed 's/^/ /' + else + pass "dmesg: clean during hardware AUX test" + fi + fi + else + skip "Hardware AUX" "PMU type not readable" + fi +else + skip "Hardware AUX" "PMU $PMU_NAME not available" +fi + +# ---- Section 5: DAMON integration check ---- +echo "" +echo "--- 5. DAMON Integration Verification ---" + +DAMON_ADMIN="/sys/kernel/mm/damon/admin" +if [[ -d "$DAMON_ADMIN" ]]; then + pass "DAMON sysfs interface available" + + if [[ -d "$DAMON_ADMIN/kdamonds/0/contexts/0/monitoring_attrs/sample/perf_events" ]] || \ + [[ -d "/sys/kernel/debug/damon/perf_stats" ]]; then + pass "DAMON perf observe infrastructure detected" + else + skip "DAMON perf observe" "debugfs or sysfs interface not available" + fi + + DAMON_TEST_DIR="$ROOT/tools/testing/selftests/damon" + if [[ -d "$DAMON_TEST_DIR" ]]; then + pass "DAMON selftest directory present" + else + skip "DAMON selftest directory" "not found at $DAMON_TEST_DIR" + fi +else + skip "DAMON integration" "DAMON admin interface not available" +fi + +# ---- Section 6: Memory ordering and concurrency checks ---- +echo "" +echo "--- 6. Memory Ordering & Concurrency ---" + +# Verify smp_rmb() in perf_event_aux_head() +if grep -q 'smp_rmb' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "perf_event_aux_head(): smp_rmb() barrier present" +else + fail "perf_event_aux_head(): smp_rmb() barrier missing" +fi + +# Verify smp_mb() in perf_event_aux_tail_set() +if grep -A30 'perf_event_aux_tail_set' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'smp_mb'; then + pass "perf_event_aux_tail_set(): smp_mb() barrier present" +else + fail "perf_event_aux_tail_set(): smp_mb() barrier missing" +fi + +# Verify aux_refcount protection in perf_event_aux_copy() +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'aux_refcount'; then + pass "perf_event_aux_copy(): aux_refcount protection present" +else + fail "perf_event_aux_copy(): aux_refcount protection missing" +fi + +# Verify mmap_mutex serialization in setup/release +if grep -A30 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'mmap_mutex'; then + pass "perf_event_setup_aux(): mmap_mutex serialization present" +else + fail "perf_event_setup_aux(): mmap_mutex serialization missing" +fi +if grep -A30 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'mmap_mutex'; then + pass "perf_event_release_aux(): mmap_mutex serialization present" +else + fail "perf_event_release_aux(): mmap_mutex serialization missing" +fi + +# Verify aux_mutex protection in release +if grep -A20 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'aux_mutex'; then + pass "perf_event_release_aux(): aux_mutex protection present" +else + fail "perf_event_release_aux(): aux_mutex protection missing" +fi + +# ---- Section 7: Boundary validation checks ---- +echo "" +echo "--- 7. API Contract: Boundary Validation ---" + +# perf_event_setup_aux() rejects non-power-of-2 +if grep -q 'is_power_of_2' "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "perf_event_setup_aux(): is_power_of_2() check present" +else + fail "perf_event_setup_aux(): is_power_of_2() check missing" +fi + +# perf_event_setup_aux() checks is_kernel_event() +if grep -q 'is_kernel_event' "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "perf_event_setup_aux(): is_kernel_event() check present" +else + fail "perf_event_setup_aux(): is_kernel_event() check missing" +fi + +# perf_event_setup_aux() checks event->parent +if grep -A20 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'event->parent'; then + pass "perf_event_setup_aux(): event->parent check present" +else + fail "perf_event_setup_aux(): event->parent check missing" +fi + +# perf_event_aux_copy() validates window size +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'available.*aux_size'; then + pass "perf_event_aux_copy(): window bounds check present" +else + fail "perf_event_aux_copy(): window bounds check missing" +fi + +# rb_has_kernel_aux() helper +if grep -q 'rb_has_kernel_aux' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null; then + pass "ring_buffer.c: rb_has_kernel_aux() helper defined" +else + fail "ring_buffer.c: rb_has_kernel_aux() helper missing" +fi + +# perf_event_aux_tail_set() validates advance <= head - old_tail +if grep -A20 'perf_event_aux_tail_set' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'advance.*head.*old_tail'; then + pass "perf_event_aux_tail_set(): advance bounds check present" +else + fail "perf_event_aux_tail_set(): advance bounds check missing" +fi + +# perf_event_aux_copy() validates from/to in [tail, head] +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'start.*available\|len.*available.*start'; then + pass "perf_event_aux_copy(): from/to bounds check present" +else + fail "perf_event_aux_copy(): from/to bounds check missing" +fi + +# ---- Section 8: Concurrency safety checks ---- +echo "" +echo "--- 8. Concurrency Safety ---" + +# Check that aux_kernel_count uses refcount_t (atomic) +if grep -q 'refcount_t.*aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/null; then + pass "aux_kernel_count: refcount_t (atomic) type" +else + fail "aux_kernel_count: not refcount_t" +fi + +# Check that aux_kernel_count is used with refcount_dec_and_mutex_lock +if grep -q 'refcount_dec_and_mutex_lock.*aux_kernel_count' "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "aux_kernel_count: refcount_dec_and_mutex_lock() in release" +else + fail "aux_kernel_count: refcount_dec_and_mutex_lock() missing in release" +fi + +# Check that aux_refcount is incremented in copy +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'refcount_inc_not_zero.*aux_refcount'; then + pass "perf_event_aux_copy(): aux_refcount_inc_not_zero() present" +else + fail "perf_event_aux_copy(): aux_refcount_inc_not_zero() missing" +fi + +# Check that rb_free_aux is called at end of copy (release refcount) +if grep -A80 'perf_event_aux_copy' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'rb_free_aux'; then + pass "perf_event_aux_copy(): rb_free_aux() at exit (balanced refcount)" +else + fail "perf_event_aux_copy(): rb_free_aux() missing at exit" +fi + +# Check that is_kernel_event guard is on both setup and release +if grep -A20 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'is_kernel_event'; then + pass "setup side: is_kernel_event() guard present" +else + fail "setup side: is_kernel_event() guard missing" +fi +if grep -A20 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'is_kernel_event'; then + pass "release side: is_kernel_event() guard present" +else + fail "release side: is_kernel_event() guard missing" +fi + +# Check that event->parent guard is on both setup and release +if grep -A20 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'event->parent'; then + pass "setup side: event->parent guard present" +else + fail "setup side: event->parent guard missing" +fi +if grep -A20 'perf_event_release_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'event->parent'; then + pass "release side: event->parent guard present" +else + fail "release side: event->parent guard missing" +fi + +# ---- Section 9: User/kernel AUX isolation ---- +echo "" +echo "--- 9. User/Kernel AUX Isolation ---" + +if grep -A30 'perf_event_setup_aux' "$SRC_EVENTS/core.c" 2>/dev/null | \ + grep -q 'EBUSY'; then + pass "setup_aux: -EBUSY when event->rb exists (user+kernel exclusion)" +else + fail "setup_aux: missing -EBUSY guard" +fi + +if grep -q 'aux_mmap_count' "$SRC_EVENTS/internal.h" 2>/dev/null && \ + grep -q 'aux_kernel_count' "$SRC_EVENTS/internal.h" 2>/dev/null; then + pass "aux_mmap_count and aux_kernel_count are distinct fields" +else + fail "aux_mmap_count or aux_kernel_count missing" +fi + +if grep -B2 -A2 'aux_mmap_count' "$SRC_EVENTS/ring_buffer.c" 2>/dev/null | \ + grep -q 'aux_kernel_count'; then + pass "perf_aux_output_begin: checks both owner counts" +else + fail "perf_aux_output_begin: does not check both counts" +fi + +if grep -q 'aux_kernel_count' "$SRC_EVENTS/core.c" 2>/dev/null; then + pass "release_aux: checks aux_kernel_count ownership" +else + fail "release_aux: missing aux_kernel_count check" +fi + +# ---- Summary ---- +echo "" +echo "==========================================" +echo " SUMMARY: $PASSED passed, $FAILED failed, $SKIPPED skipped" +echo "==========================================" +echo "Results saved to: $RESULTS_DIR" + +if [[ "$FAILED" -gt 0 ]]; then + echo "Overall: FAIL" + exit 1 +else + echo "Overall: PASS" + exit 0 +fi -- 2.43.0 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script 2026-08-14 14:49 ` [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script Kunwu Chan @ 2026-08-14 14:56 ` sashiko-bot 0 siblings, 0 replies; 11+ messages in thread From: sashiko-bot @ 2026-08-14 14:56 UTC (permalink / raw) To: Kunwu Chan; +Cc: linux-perf-users Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [Medium] Selftest unconditionally fails in installed environments by expecting kernel source files to be present. - [Medium] Userspace regression test is improperly skipped when the selftest is run from an installed location. - [Medium] Selftest hardcodes `gcc` for runtime compilation, breaking cross-compilation environments. - [Medium] Insecure predictable temporary directory name in `/tmp` allows local arbitrary file overwrite. -- --- Patch [5]: [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script --- commit 3dec31f6b5ec09fc257a6fee2e10670ad1c14b3c Author: Kunwu Chan <kunwu.chan@gmail.com> selftests/perf_events: add AUX kernel API selftest script Add a shell script that runs the perf_aux_kernel KUnit suite through debugfs, builds and runs the userspace regression program, and performs static-analysis checks on the source tree. > diff --git a/tools/testing/selftests/perf_events/aux_kernel.sh b/tools/testing/selftests/perf_events/aux_kernel.sh > new file mode 100755 > index 0000000000000..431f59ef02e7d > --- /dev/null > +++ b/tools/testing/selftests/perf_events/aux_kernel.sh > @@ -0,0 +1,563 @@ > +#!/bin/bash [ ... ] > +RESULTS_DIR="/tmp/perf_aux_kernel_test_$$" > +mkdir -p "$RESULTS_DIR" > +exec > >(tee "$RESULTS_DIR/output.log") 2>&1 [Severity: Medium] Does this predictable temporary directory name create a local file overwrite vulnerability? Since this selftest might be executed as root, a local unprivileged user could pre-create a symlink with the target PID pointing to any system file (like /etc/shadow). The tee command would then follow the symlink and overwrite the target file. [ ... ] > +SRC_EVENTS="$ROOT/kernel/events" > +for f in core.c ring_buffer.c internal.h; do > + if [[ -f "$SRC_EVENTS/$f" ]]; then > + pass "Source file: kernel/events/$f" > + else > + fail "Source file: kernel/events/$f missing" > + fi > +done [Severity: Medium] Will this unconditionally fail in installed selftest environments? When tests are run via make install on target CI systems, the kernel source tree is typically not present. Checking for source files in kernel/events/ and invoking fail() will cause false positive CI failures. [ ... ] > +# Build and run the C regression test program > +UMODE_SRC="$SCRIPT_DIR/aux_kernel_usermode.c" > +UMODE_BIN="$SCRIPT_DIR/aux_kernel_usermode" > + > +if [[ -f "$UMODE_SRC" ]]; then > + if [[ ! -x "$UMODE_BIN" ]] || [[ "$UMODE_SRC" -nt "$UMODE_BIN" ]]; then [Severity: Medium] Will this skip the userspace regression test when run from an installed location? Because source files are not installed by TEST_GEN_PROGS during make install, the source file won't exist on the target system. This conditional causes the script to skip executing the already compiled binary, reducing test coverage in CI pipelines. > + echo " Building aux_kernel_usermode..." > + gcc -o "$UMODE_BIN" "$UMODE_SRC" -Wall -Wextra -O2 2>&1 | \ [Severity: Medium] Does hardcoding gcc break cross-compilation environments? If the binary is missing and the script attempts to compile it dynamically, using gcc ignores the CC and CROSS_COMPILE environment variables set by the kernel build system. This can lead to compilation failures or building for the wrong architecture. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260814144927.489172-1-kunwu.chan@linux.dev?part=5 ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-08-14 15:04 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-14 14:49 [RFC PATCH 0/5] perf/core: add AUX buffer kernel-consumer API Kunwu Chan 2026-08-14 14:49 ` [RFC PATCH 1/5] perf/core: add AUX buffer ownership for kernel events Kunwu Chan 2026-08-14 15:04 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 2/5] perf/core: add AUX ring accessors for kernel consumers Kunwu Chan 2026-08-14 14:59 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API Kunwu Chan 2026-08-14 15:02 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 4/5] selftests/perf_events: add userspace AUX regression test Kunwu Chan 2026-08-14 14:59 ` sashiko-bot 2026-08-14 14:49 ` [RFC PATCH 5/5] selftests/perf_events: add AUX kernel API selftest script Kunwu Chan 2026-08-14 14:56 ` sashiko-bot
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.