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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox