From: "Lian Wang (ProcessMission)" <[email protected]>

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 <[email protected]>
Signed-off-by: Kunwu Chan <[email protected]>
Signed-off-by: Lian Wang (ProcessMission) <[email protected]>
---
 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


Reply via email to