@@ -2102,6 +2102,16 @@ config ARM64_MTE_COMP
128-byte tag buffers corresponding to 4K pages can be compressed to save heap memory.
+config ARM64_MTE_COMP_KUNIT_TEST
+ tristate "Test tag compression for ARM64 Memory Tagging Extension" if !KUNIT_ALL_TESTS
+ default KUNIT_ALL_TESTS
+ depends on KUNIT && ARM64_MTE_COMP
+ help
+ Test MTE compression algorithm enabled by CONFIG_ARM64_MTE_COMP.
+
+ Ensure that tag sequences containing alternating values are compressed
+ to buffers of expected size and correctly decompressed afterwards.
+
config ARM64_SVE
bool "ARM Scalable Vector Extension support"
default y
@@ -11,6 +11,7 @@ obj-$(CONFIG_TRANS_TABLE) += trans_pgd-asm.o
obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o
obj-$(CONFIG_ARM64_MTE) += mteswap.o
obj-$(CONFIG_ARM64_MTE_COMP) += mtecomp.o
+obj-$(CONFIG_ARM64_MTE_COMP_KUNIT_TEST) += test_mtecomp.o
KASAN_SANITIZE_physaddr.o += n
obj-$(CONFIG_KASAN) += kasan_init.o
new file mode 100644
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test cases for MTE tags compression algorithm.
+ */
+
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <kunit/test.h>
+#include <linux/types.h>
+
+#include <asm/mtecomp.h>
+
+/* Functions exported from mtecomp.c for this test. */
+void mte_tags_to_ranges(u8 *tags, u8 *out_tags, unsigned short *out_sizes,
+ size_t *out_len);
+void mte_ranges_to_tags(u8 *r_tags, unsigned short *r_sizes, size_t r_len,
+ u8 *tags);
+
+/*
+ * Test that mte_tags_to_ranges() produces a single range for a zero-filled tag
+ * buffer.
+ */
+static void test_tags_to_ranges_zero(struct kunit *test)
+{
+ u8 tags[128], dtags[128];
+ unsigned short r_sizes[256];
+ size_t r_len = 256;
+ u8 r_tags[256];
+
+ memset(tags, 0, 128);
+ mte_tags_to_ranges(tags, r_tags, r_sizes, &r_len);
+ KUNIT_EXPECT_EQ(test, r_len, 1);
+ KUNIT_EXPECT_EQ(test, r_tags[0], 0);
+ KUNIT_EXPECT_EQ(test, r_sizes[0], 256);
+ mte_ranges_to_tags(r_tags, r_sizes, r_len, dtags);
+ KUNIT_EXPECT_EQ(test, memcmp(tags, dtags, 128), 0);
+}
+
+/*
+ * Test that a small number of different tags is correctly transformed into
+ * ranges.
+ */
+static void test_tags_to_ranges_simple(struct kunit *test)
+{
+ u8 tags[128], dtags[128];
+ const u8 ex_tags[] = { 0xa, 0x0, 0xa, 0xb, 0x0 };
+ const unsigned short ex_sizes[] = { 1, 2, 2, 1, 250 };
+ unsigned short r_sizes[256];
+ size_t r_len = 256;
+ u8 r_tags[256];
+
+ memset(tags, 0, 128);
+ tags[0] = 0xa0;
+ tags[1] = 0x0a;
+ tags[2] = 0xab;
+ mte_tags_to_ranges(tags, r_tags, r_sizes, &r_len);
+ KUNIT_EXPECT_EQ(test, r_len, 5);
+ KUNIT_EXPECT_EQ(test, memcmp(r_tags, ex_tags, sizeof(ex_tags)), 0);
+ KUNIT_EXPECT_EQ(test, memcmp(r_sizes, ex_sizes, sizeof(ex_sizes)), 0);
+ mte_ranges_to_tags(r_tags, r_sizes, r_len, dtags);
+ KUNIT_EXPECT_EQ(test, memcmp(tags, dtags, 128), 0);
+}
+
+/* Test that repeated 0xa0 byte produces 256 ranges of length 1. */
+static void test_tags_to_ranges_repeated(struct kunit *test)
+{
+ u8 tags[128], dtags[128];
+ unsigned short r_sizes[256];
+ size_t r_len = 256;
+ u8 r_tags[256];
+
+ memset(tags, 0xa0, 128);
+ mte_tags_to_ranges(tags, r_tags, r_sizes, &r_len);
+ KUNIT_EXPECT_EQ(test, r_len, 256);
+ mte_ranges_to_tags(r_tags, r_sizes, r_len, dtags);
+ KUNIT_EXPECT_EQ(test, memcmp(tags, dtags, 128), 0);
+}
+
+/* Generate a buffer that will contain @nranges of tag ranges. */
+static void gen_tag_range_helper(u8 *tags, int nranges)
+{
+ unsigned int i;
+
+ memset(tags, 0, 128);
+ if (nranges > 1) {
+ nranges--;
+ for (i = 0; i < nranges / 2; i++)
+ tags[i] = 0xab;
+ if (nranges % 2)
+ tags[nranges / 2] = 0xa0;
+ }
+}
+
+/*
+ * Test that mte_tags_to_ranges()/mte_ranges_to_tags() work for various
+ * r_len values.
+ */
+static void test_tag_to_ranges_n(struct kunit *test)
+{
+ unsigned short r_sizes[256];
+ u8 tags[128], dtags[128];
+ unsigned int i, j, sum;
+ size_t r_len = 256;
+ u8 r_tags[256];
+
+ for (i = 1; i <= 256; i++) {
+ gen_tag_range_helper(tags, i);
+ mte_tags_to_ranges(tags, r_tags, r_sizes, &r_len);
+ sum = 0;
+ for (j = 0; j < r_len; j++)
+ sum += r_sizes[j];
+ KUNIT_EXPECT_EQ(test, sum, 256);
+ mte_ranges_to_tags(r_tags, r_sizes, r_len, dtags);
+ KUNIT_EXPECT_EQ(test, memcmp(tags, dtags, 128), 0);
+ }
+}
+
+/* Test that a zero-filled array is compressed into inline storage. */
+static void test_compress_zero(struct kunit *test)
+{
+ u8 tags[128], dtags[128];
+ unsigned long handle;
+
+ memset(tags, 0, 128);
+ handle = mte_compress(tags);
+ KUNIT_EXPECT_EQ(test, handle & BIT_ULL(63), 0);
+ /* Tags are stored inline. */
+ KUNIT_EXPECT_EQ(test, mte_storage_size(handle), 8);
+ KUNIT_EXPECT_TRUE(test, mte_decompress(handle, dtags));
+ KUNIT_EXPECT_EQ(test, memcmp(tags, dtags, 128), 0);
+}
+
+/*
+ * Test that a very small number of tag ranges ends up compressed into 8 bytes.
+ */
+static void test_compress_simple(struct kunit *test)
+{
+ u8 tags[128], dtags[128];
+ unsigned long handle;
+
+ memset(tags, 0, 128);
+ tags[0] = 0xa0;
+ tags[1] = 0x0a;
+ tags[2] = 0xab;
+
+ handle = mte_compress(tags);
+ KUNIT_EXPECT_EQ(test, handle & BIT_ULL(63), 0);
+ /* Tags are stored inline. */
+ KUNIT_EXPECT_EQ(test, mte_storage_size(handle), 8);
+ KUNIT_EXPECT_TRUE(test, mte_decompress(handle, dtags));
+ KUNIT_EXPECT_EQ(test, memcmp(tags, dtags, 128), 0);
+}
+
+/*
+ * Test that a buffer containing @nranges ranges compresses into @exp_size
+ * bytes and decompresses into the original tag sequence.
+ */
+static void compress_range_helper(struct kunit *test, int nranges,
+ size_t exp_size)
+{
+ u8 tags[128], dtags[128];
+ unsigned long handle;
+
+ gen_tag_range_helper(tags, nranges);
+ handle = mte_compress(tags);
+ KUNIT_EXPECT_EQ(test, handle & BIT_ULL(63), 0);
+ KUNIT_EXPECT_EQ(test, mte_storage_size(handle), exp_size);
+ KUNIT_EXPECT_TRUE(test, mte_decompress(handle, dtags));
+ KUNIT_EXPECT_EQ(test, memcmp(tags, dtags, 128), 0);
+}
+
+/*
+ * Test that every number of tag ranges is correctly compressed and
+ * decompressed.
+ */
+static void test_compress_ranges(struct kunit *test)
+{
+ size_t exp_size;
+ unsigned int i;
+
+ for (i = 1; i <= 256; i++) {
+ if (i < 7)
+ exp_size = 8;
+ else if (i < 12)
+ exp_size = 16;
+ else if (i < 24)
+ exp_size = 32;
+ else if (i < 47)
+ exp_size = 64;
+ else
+ exp_size = 128;
+ compress_range_helper(test, i, exp_size);
+ }
+}
+
+static struct kunit_case mtecomp_test_cases[] = {
+ KUNIT_CASE(test_tags_to_ranges_zero),
+ KUNIT_CASE(test_tags_to_ranges_simple),
+ KUNIT_CASE(test_tags_to_ranges_repeated),
+ KUNIT_CASE(test_tag_to_ranges_n),
+ KUNIT_CASE(test_compress_zero),
+ KUNIT_CASE(test_compress_simple),
+ KUNIT_CASE(test_compress_ranges),
+ {}
+};
+
+static struct kunit_suite mtecomp_test_suite = {
+ .name = "mtecomp",
+ .test_cases = mtecomp_test_cases,
+};
+kunit_test_suites(&mtecomp_test_suite);
+
+MODULE_IMPORT_NS(MTECOMP);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Potapenko <glider@google.com>");