From: "Lian Wang (ProcessMission)" 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 Signed-off-by: Kunwu Chan Signed-off-by: Lian Wang (ProcessMission) --- 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 +#include + +#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