[RFC PATCH 3/5] perf/core: add KUnit tests for AUX kernel-consumer API

Kunwu Chan <[email protected]>
Newsgroups org.kernel.vger.linux-perf-users,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest
Message-ID <[email protected]>
From: Lian Wang <[email protected]>

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 <[email protected]>
Signed-off-by: Kunwu Chan <[email protected]>
Signed-off-by: Lian Wang <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.