From: Kunwu Chan <kunwu.chan@gmail.com>
To: will@kernel.org, mark.rutland@arm.com, sj@kernel.org,
akpm@linux-foundation.org, shuah@kernel.org,
kunwu.chan@linux.dev
Cc: linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-perf-users@vger.kernel.org, damon@lists.linux.dev,
linux-mm@kvack.org, linux-kselftest@vger.kernel.org,
"Lian Wang (ProcessMission)" <lianux.mm@gmail.com>,
Kunwu Chan <kunwu.chan@gmail.com>
Subject: [RFC PATCH 3/4] mm/damon/perf: add KUnit tests for the SPE record parser
Date: Sun, 16 Aug 2026 22:22:20 +0800 [thread overview]
Message-ID: <20260816142222.689624-4-kunwu.chan@linux.dev> (raw)
In-Reply-To: <20260816142222.689624-1-kunwu.chan@linux.dev>
From: "Lian Wang (ProcessMission)" <lianux.mm@gmail.com>
Add byte-exact tests for load and store records, timestamp terminators,
multiple records, PAD and ALIGNMENT packets, extended addresses, invalid
extended headers, error resynchronization, and records without a virtual
address.
Cover ALIGNMENT packets at both odd and already aligned stream offsets.
Also verify that a record split across two AUX snapshots leaves the tail
unchanged until the terminating packet becomes available.
Co-developed-by: Kunwu Chan <kunwu.chan@gmail.com>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
Signed-off-by: Lian Wang (ProcessMission) <lianux.mm@gmail.com>
---
mm/damon/perf/spe_parser_test.c | 365 ++++++++++++++++++++++++++++++++
1 file changed, 365 insertions(+)
create mode 100644 mm/damon/perf/spe_parser_test.c
diff --git a/mm/damon/perf/spe_parser_test.c b/mm/damon/perf/spe_parser_test.c
new file mode 100644
index 000000000000..598d9fd7cdc7
--- /dev/null
+++ b/mm/damon/perf/spe_parser_test.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for the DAMON perf ARM SPE record parser.
+ *
+ * Each parameterized case feeds a byte-exact SPE packet stream (same
+ * encodings and decode order as tools/perf/util/arm-spe-decoder) into
+ * spe_parse_one_record() and verifies the synthesized records, the
+ * return values, and the aux_tail accounting. The loop mirrors
+ * spe_backend_drain() including the caller-side window shrink.
+ */
+
+#include <kunit/test.h>
+#include <linux/slab.h>
+
+#include "spe_parser.h"
+
+/**
+ * struct spe_parse_case - One parser test case.
+ * @name: Parameter description (shown on failure).
+ * @stream: Byte-exact SPE packet stream.
+ * @len: @stream length.
+ * @exp_reports: Expected SPE_PARSE_REPORT count.
+ * @exp_skips: Expected SPE_PARSE_SKIP count.
+ * @exp_errors: Expected SPE_PARSE_ERROR count.
+ * @exp_tail: Expected st->aux_tail after the stream.
+ * @exp_va: Expected virtual address of the first REPORT record.
+ * @exp_tid: Expected tid of the first REPORT record.
+ * @exp_is_write: Expected access type of the first REPORT record.
+ */
+struct spe_parse_case {
+ const char *name;
+ const u8 *stream;
+ size_t len;
+ unsigned int exp_reports;
+ unsigned int exp_skips;
+ unsigned int exp_errors;
+ unsigned long exp_tail;
+ unsigned long exp_va;
+ u32 exp_tid;
+ bool exp_is_write;
+};
+
+static const u8 stream_store[] = {
+ 0x66, 0x2a, 0x00, 0x00, 0x00, /* CONTEXT: 64-bit EL1 tid=42 */
+ 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x1000 */
+ 0x49, 0x01, /* OP-TYPE: ST */
+ 0x01, /* END */
+};
+
+static const u8 stream_ts_load[] = {
+ 0xb2, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x2000 */
+ 0x49, 0x00, /* OP-TYPE: load */
+ 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* TIMESTAMP end */
+};
+
+static const u8 stream_two[] = {
+ 0x66, 0x2a, 0x00, 0x00, 0x00, /* CONTEXT: 64-bit EL1 tid=42 */
+ 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x1000 */
+ 0x49, 0x01, /* OP-TYPE: ST */
+ 0x01, /* END */
+ 0xb2, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x2000 */
+ 0x49, 0x00, /* OP-TYPE: load */
+ 0x01, /* END */
+};
+
+static const u8 stream_pad[] = {
+ 0x00, 0x00, /* PAD prefix */
+ 0x66, 0x2a, 0x00, 0x00, 0x00, /* CONTEXT: 64-bit EL1 tid=42 */
+ 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x1000 */
+ 0x49, 0x01, /* OP-TYPE: ST */
+ 0x01, /* END */
+ 0x00, 0x00, 0x00, /* PAD padding */
+};
+
+static const u8 stream_alignment[] = {
+ 0x66, 0x2a, 0x00, 0x00, 0x00, /* CONTEXT: tid 42, pos 0-4 */
+ 0x20, 0x00, /* ALIGNMENT at odd pos 5 */
+ 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x1000 */
+ 0x49, 0x00, /* OP-TYPE: load */
+ 0x01, /* END */
+};
+
+static const u8 stream_alignment_aligned[] = {
+ 0x20, 0x00, /* ALIGNMENT at even pos 0 */
+ 0x66, 0x2a, 0x00, 0x00, 0x00, /* CONTEXT: 64-bit EL1 tid=42 */
+ 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x1000 */
+ 0x49, 0x00, /* OP-TYPE: load */
+ 0x01, /* END */
+};
+
+static const u8 stream_bad[] = {
+ 0xff, /* unknown header */
+ 0x66, 0x2a, 0x00, 0x00, 0x00, /* CONTEXT: 64-bit EL1 tid=42 */
+ 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x1000 */
+ 0x49, 0x01, /* OP-TYPE: ST */
+ 0x01, /* END */
+};
+
+static const u8 stream_skip[] = {
+ 0x49, 0x00, /* OP-TYPE: load */
+ 0x01, /* END, no address */
+};
+
+static const u8 stream_other_pkts[] = {
+ 0x42, 0x05, /* EVENTS (width 1) */
+ 0x43, 0x06, /* DATA-SOURCE (width 1) */
+ 0x98, 0x00, 0x00, /* COUNTER (width 2) */
+ 0xb2, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x3000 */
+ 0x49, 0x00, /* OP-TYPE: load */
+ 0x01, /* END */
+};
+
+static const u8 stream_truncated[] = {
+ 0x66, 0x2a, 0x00, 0x00, 0x00, /* CONTEXT: 64-bit EL1 tid=42 */
+ 0xb2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x1000 */
+ 0x49, 0x01, /* OP-TYPE: ST, no END */
+};
+
+static const u8 stream_truncated_packet[] = {
+ 0xb2, 0x00, 0x10, /* short 8-byte address */
+};
+
+static const u8 stream_ext_addr[] = {
+ 0x20, 0xb2, /* EXTENDED ADDRESS, DATA_VIRT */
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* VA 0x4000 */
+ 0x49, 0x00, /* OP-TYPE: load */
+ 0x01, /* END */
+};
+
+static const u8 stream_invalid_extended[] = {
+ 0x20, 0x42, 0x00, /* invalid extended EVENTS */
+ 0x01, /* END after resync */
+};
+
+static const u8 stream_pad_only[] = {
+ 0x00, 0x00, 0x00,
+};
+
+static const u8 stream_empty[] = { 0x00 };
+
+static const struct spe_parse_case spe_parse_cases[] = {
+ {
+ .name = "store record",
+ .stream = stream_store,
+ .len = sizeof(stream_store),
+ .exp_reports = 1,
+ .exp_tail = 17,
+ .exp_va = 0x1000,
+ .exp_tid = 42,
+ .exp_is_write = true,
+ },
+ {
+ .name = "load record with timestamp terminator",
+ .stream = stream_ts_load,
+ .len = sizeof(stream_ts_load),
+ .exp_reports = 1,
+ .exp_tail = 20,
+ .exp_va = 0x2000,
+ .exp_tid = 0,
+ .exp_is_write = false,
+ },
+ {
+ .name = "two records in one window",
+ .stream = stream_two,
+ .len = sizeof(stream_two),
+ .exp_reports = 2,
+ .exp_tail = 29,
+ .exp_va = 0x1000,
+ .exp_tid = 42,
+ .exp_is_write = true,
+ },
+ {
+ .name = "pad-wrapped record",
+ .stream = stream_pad,
+ .len = sizeof(stream_pad),
+ .exp_reports = 1,
+ .exp_tail = 22,
+ .exp_va = 0x1000,
+ .exp_tid = 42,
+ .exp_is_write = true,
+ },
+ {
+ .name = "alignment packet at odd position",
+ .stream = stream_alignment,
+ .len = sizeof(stream_alignment),
+ .exp_reports = 1,
+ .exp_tail = 19,
+ .exp_va = 0x1000,
+ .exp_tid = 42,
+ .exp_is_write = false,
+ },
+ {
+ .name = "alignment packet at aligned position",
+ .stream = stream_alignment_aligned,
+ .len = sizeof(stream_alignment_aligned),
+ .exp_reports = 1,
+ .exp_tail = sizeof(stream_alignment_aligned),
+ .exp_va = 0x1000,
+ .exp_tid = 42,
+ .exp_is_write = false,
+ },
+ {
+ .name = "bad packet resync",
+ .stream = stream_bad,
+ .len = sizeof(stream_bad),
+ .exp_reports = 1,
+ .exp_errors = 1,
+ .exp_tail = 18,
+ .exp_va = 0x1000,
+ .exp_tid = 42,
+ .exp_is_write = true,
+ },
+ {
+ .name = "record without address",
+ .stream = stream_skip,
+ .len = sizeof(stream_skip),
+ .exp_skips = 1,
+ .exp_tail = 3,
+ },
+ {
+ .name = "events/source/counter packets ignored",
+ .stream = stream_other_pkts,
+ .len = sizeof(stream_other_pkts),
+ .exp_reports = 1,
+ .exp_tail = 19,
+ .exp_va = 0x3000,
+ .exp_is_write = false,
+ },
+ {
+ .name = "truncated trailing record retained",
+ .stream = stream_truncated,
+ .len = sizeof(stream_truncated),
+ .exp_tail = 0,
+ },
+ {
+ .name = "truncated packet retained",
+ .stream = stream_truncated_packet,
+ .len = sizeof(stream_truncated_packet),
+ .exp_tail = 0,
+ },
+ {
+ .name = "extended address packet",
+ .stream = stream_ext_addr,
+ .len = sizeof(stream_ext_addr),
+ .exp_reports = 1,
+ .exp_tail = 13,
+ .exp_va = 0x4000,
+ .exp_is_write = false,
+ },
+ {
+ .name = "invalid extended header resync",
+ .stream = stream_invalid_extended,
+ .len = sizeof(stream_invalid_extended),
+ .exp_skips = 1,
+ .exp_errors = 1,
+ .exp_tail = sizeof(stream_invalid_extended),
+ },
+ {
+ .name = "pad-only window",
+ .stream = stream_pad_only,
+ .len = sizeof(stream_pad_only),
+ .exp_tail = 3,
+ },
+ {
+ .name = "empty window",
+ .stream = stream_empty,
+ .len = 0,
+ .exp_tail = 0,
+ },
+};
+
+KUNIT_ARRAY_PARAM_DESC(spe_parse, spe_parse_cases, name);
+
+static void spe_parse_case_test(struct kunit *test)
+{
+ const struct spe_parse_case *tc = test->param_value;
+ struct spe_parser_state st = { 0 };
+ struct spe_record rec;
+ u8 *buf;
+ unsigned int reports = 0, skips = 0, errors = 0, guard = 0;
+ bool first_checked = false;
+
+ buf = kunit_kmalloc(test, tc->len ?: 1, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+ memcpy(buf, tc->stream, tc->len);
+ st.win = buf;
+ st.win_size = tc->len;
+
+ while (guard++ < SPE_BUFFER_MAX_RECORDS) {
+ unsigned long tail0 = st.aux_tail;
+ unsigned long consumed;
+ int ret = spe_parse_one_record(&st, &rec);
+
+ if (ret == SPE_PARSE_NEED_MORE)
+ break;
+
+ /* caller-side window shrink, mirrors spe_backend_drain() */
+ consumed = st.aux_tail - tail0;
+ st.win_size -= consumed;
+ memmove(st.win, st.win + consumed, st.win_size);
+
+ switch (ret) {
+ case SPE_PARSE_REPORT:
+ reports++;
+ if (!first_checked) {
+ KUNIT_EXPECT_EQ(test, tc->exp_va, rec.va);
+ KUNIT_EXPECT_EQ(test, tc->exp_tid, rec.tid);
+ KUNIT_EXPECT_EQ(test, tc->exp_is_write,
+ rec.is_write);
+ first_checked = true;
+ }
+ break;
+ case SPE_PARSE_SKIP:
+ skips++;
+ break;
+ case SPE_PARSE_ERROR:
+ errors++;
+ break;
+ }
+ }
+
+ KUNIT_EXPECT_EQ(test, tc->exp_reports, reports);
+ KUNIT_EXPECT_EQ(test, tc->exp_skips, skips);
+ KUNIT_EXPECT_EQ(test, tc->exp_errors, errors);
+ KUNIT_EXPECT_EQ(test, tc->exp_tail, st.aux_tail);
+ KUNIT_EXPECT_EQ(test, tc->exp_tail, st.bytes);
+ KUNIT_EXPECT_EQ(test, tc->exp_reports, st.records);
+}
+
+static void spe_split_record_test(struct kunit *test)
+{
+ struct spe_parser_state st = {
+ .win = (u8 *)stream_store,
+ .win_size = sizeof(stream_store) - 1,
+ };
+ struct spe_record rec;
+ int ret;
+
+ ret = spe_parse_one_record(&st, &rec);
+ KUNIT_ASSERT_EQ(test, SPE_PARSE_NEED_MORE, ret);
+ KUNIT_EXPECT_EQ(test, 0UL, st.aux_tail);
+ KUNIT_EXPECT_EQ(test, 0UL, st.bytes);
+
+ /* The next AUX copy starts at the unchanged tail and includes END. */
+ st.win_size = sizeof(stream_store);
+ ret = spe_parse_one_record(&st, &rec);
+ KUNIT_ASSERT_EQ(test, SPE_PARSE_REPORT, ret);
+ KUNIT_EXPECT_EQ(test, (unsigned long)sizeof(stream_store),
+ st.aux_tail);
+ KUNIT_EXPECT_EQ(test, 0x1000UL, rec.va);
+ KUNIT_EXPECT_EQ(test, 42U, rec.tid);
+}
+
+static struct kunit_case spe_parser_test_cases[] = {
+ KUNIT_CASE_PARAM(spe_parse_case_test, spe_parse_gen_params),
+ KUNIT_CASE(spe_split_record_test),
+ {},
+};
+
+static struct kunit_suite spe_parser_test_suite = {
+ .name = "damon_perf_spe_parser",
+ .test_cases = spe_parser_test_cases,
+};
+
+kunit_test_suite(spe_parser_test_suite);
--
2.43.0
next prev parent reply other threads:[~2026-08-16 14:23 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-16 14:22 [RFC PATCH 0/4] mm/damon/perf: add ARM SPE AUX backend Kunwu Chan
2026-08-16 14:22 ` [RFC PATCH 1/4] mm/damon/perf: introduce AUX backend interface and Kconfig Kunwu Chan
2026-08-16 14:22 ` [RFC PATCH 2/4] mm/damon/perf: add AUX trace-buffer PMU backend for ARM SPE Kunwu Chan
2026-08-16 14:22 ` Kunwu Chan [this message]
2026-08-16 14:22 ` [RFC PATCH 4/4] selftests/damon: add DAMON perf AUX backend test Kunwu Chan
2026-08-16 16:56 ` [RFC PATCH 0/4] mm/damon/perf: add ARM SPE AUX backend SJ Park
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260816142222.689624-4-kunwu.chan@linux.dev \
--to=kunwu.chan@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=damon@lists.linux.dev \
--cc=kunwu.chan@linux.dev \
--cc=lianux.mm@gmail.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=shuah@kernel.org \
--cc=sj@kernel.org \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.