* [PATCH v4] arm64: Add basic MTE test
@ 2025-02-27 15:22 Vladimir Murzin
2025-03-06 14:11 ` Alexandru Elisei
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: Vladimir Murzin @ 2025-02-27 15:22 UTC (permalink / raw)
To: kvmarm; +Cc: alexandru.elisei, nikos.nikoleris, andrew.jones, eric.auger
Test tag storage access and tag mismatch for different MTE modes.
Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
---
arm/Makefile.arm64 | 8 +
arm/cstart64.S | 4 +-
arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
arm/unittests.cfg | 19 +++
lib/arm64/asm/mmu.h | 1 +
lib/arm64/asm/pgtable-hwdef.h | 3 +
lib/arm64/asm/sysreg.h | 14 ++
7 files changed, 361 insertions(+), 1 deletion(-)
create mode 100644 arm/mte.c
diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
index 3b9034e3..fbf11c98 100644
--- a/arm/Makefile.arm64
+++ b/arm/Makefile.arm64
@@ -17,6 +17,13 @@ ifneq ($(strip $(sve_flag)),)
CFLAGS += -DCC_HAS_SVE
endif
+mte_flag := $(call cc-option, -march=armv8.5-a+memtag, "")
+ifneq ($(strip $(mte_flag)),)
+# MTE is supported by the compiler, generate MTE instructions
+CFLAGS += -DCC_HAS_MTE
+endif
+
+
mno_outline_atomics := $(call cc-option, -mno-outline-atomics, "")
CFLAGS += $(mno_outline_atomics)
CFLAGS += -DCONFIG_RELOC
@@ -57,6 +64,7 @@ tests += $(TEST_DIR)/micro-bench.$(exe)
tests += $(TEST_DIR)/cache.$(exe)
tests += $(TEST_DIR)/debug.$(exe)
tests += $(TEST_DIR)/fpu.$(exe)
+tests += $(TEST_DIR)/mte.$(exe)
include $(SRCDIR)/$(TEST_DIR)/Makefile.common
diff --git a/arm/cstart64.S b/arm/cstart64.S
index b480a552..b9d7a446 100644
--- a/arm/cstart64.S
+++ b/arm/cstart64.S
@@ -242,6 +242,7 @@ halt:
* NORMAL 100 11111111
* NORMAL_WT 101 10111011
* DEVICE_nGRE 110 00001000
+ * NORMAL_TAGGED 111 11110000
*/
#define MAIR(attr, mt) ((attr) << ((mt) * 8))
@@ -275,7 +276,8 @@ asm_mmu_enable:
MAIR(0x44, MT_NORMAL_NC) | \
MAIR(0xff, MT_NORMAL) | \
MAIR(0xbb, MT_NORMAL_WT) | \
- MAIR(0x08, MT_DEVICE_nGRE)
+ MAIR(0x08, MT_DEVICE_nGRE) | \
+ MAIR(0xf0, MT_NORMAL_TAGGED)
msr mair_el1, x1
/* TTBR0 */
diff --git a/arm/mte.c b/arm/mte.c
new file mode 100644
index 00000000..f32203ce
--- /dev/null
+++ b/arm/mte.c
@@ -0,0 +1,313 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2024 Arm Limited.
+ * All rights reserved.
+ */
+
+#include <libcflat.h>
+#include <alloc_page.h>
+#include <stdlib.h>
+
+#include <asm/mmu.h>
+#include <asm/pgtable-hwdef.h>
+#include <asm/processor.h>
+#include <asm/sysreg.h>
+#include <asm/thread_info.h>
+
+
+/* Tag Check Faults cause a synchronous exception */
+#define MTE_TCF_SYNC 0b01
+/* Tag Check Faults are asynchronously accumulated */
+#define MTE_TCF_ASYNC 0b10
+/*
+ * Tag Check Faults cause a synchronous exception on reads,
+ * and are asynchronously accumulated on writes
+ */
+#define MTE_TCF_ASYMM 0b11
+
+#define MTE_GRANULE_SIZE UL(16)
+#define MTE_GRANULE_MASK (~(MTE_GRANULE_SIZE - 1))
+#define MTE_TAG_SHIFT 56
+
+#define untagged(p) \
+({ \
+ unsigned long __in = (unsigned long)(p); \
+ typeof(p) __out = (typeof(p))(__in & ~(MTE_GRANULE_MASK << MTE_TAG_SHIFT)); \
+ \
+ __out; \
+})
+
+#define tagged(p, t) \
+({ \
+ unsigned long __in = (unsigned long)(untagged(p)); \
+ unsigned long __tag = (unsigned long)(t) << MTE_TAG_SHIFT; \
+ typeof(p) __out = (typeof(p))(__in | __tag); \
+ \
+ __out; \
+})
+
+/*
+ * If we use a normal (non hand coded inline assembly) load or store
+ * to access a tagged address, the compiler will reasonably assume
+ * that the access succeeded, and the next instruction may do
+ * something based on that assumption.
+ *
+ * But a test might want the tagged access to fail on purpose, and if
+ * we advance the PC to the next instruction, the one added by the
+ * compiler, we might leave the program in an unexpected state.
+ */
+static inline void mem_read(unsigned int *addr, unsigned int *res)
+{
+ unsigned int r;
+
+ asm volatile ("ldr %0,[%1]\n"
+ "str %0,[%2]\n"
+ : "=&r" (r)
+ : "r" (addr), "r" (res) : "memory");
+}
+
+static inline void mem_write(unsigned int *addr, unsigned int val)
+{
+ /* The NOP allows the same exception handler as mem_read() to be used. */
+ asm volatile ("str %0,[%1]\n"
+ "nop\n"
+ :
+ : "r" (val), "r" (addr)
+ : "memory");
+}
+
+static volatile bool mte_exception;
+
+static void mte_fault_handler(struct pt_regs *regs, unsigned int esr)
+{
+ unsigned int dfsc = esr & GENMASK(5, 0);
+ unsigned int fnv = esr & BIT(10);
+
+ if (dfsc == 0b010001) {
+ if (fnv)
+ report_info("Unexpected non-zero FnV");
+ mte_exception = true;
+ }
+
+ /*
+ * mem_read() reads the value from the tagged pointer, then
+ * stores this value in the untagged 'res' pointer. The
+ * function that called mem_read() will want to check that the
+ * initial value of 'res' hasn't changed if a tag check fault
+ * is reported. Skip over two instructions so 'res' isn't
+ * overwritten.
+ */
+ regs->pc += 8;
+}
+
+static inline void mmu_set_tagged(pgd_t *pgtable, unsigned long vaddr)
+{
+ pteval_t *p_pte = follow_pte(pgtable, untagged(vaddr));
+
+ if (p_pte) {
+ pteval_t entry = *p_pte;
+
+ entry &= ~PTE_ATTRINDX_MASK;
+ entry |= PTE_ATTRINDX(MT_NORMAL_TAGGED);
+
+ WRITE_ONCE(*p_pte, entry);
+ flush_tlb_page(vaddr);
+ } else {
+ report_abort("Cannot find PTE");
+ }
+}
+
+static void mte_init(void)
+{
+ unsigned long sctlr = read_sysreg(sctlr_el1);
+ unsigned long tcr = read_sysreg(tcr_el1);
+
+ sctlr &= ~SCTLR_EL1_TCF_MASK;
+ sctlr |= SCTLR_EL1_ATA;
+
+ tcr &= ~TCR_TCMA0;
+ tcr |= TCR_TBI0;
+
+ write_sysreg(sctlr, sctlr_el1);
+ write_sysreg(tcr, tcr_el1);
+
+ isb();
+ flush_tlb_all();
+}
+
+static inline unsigned long mte_set_tcf(unsigned long tcf)
+{
+ unsigned long sctlr = read_sysreg(sctlr_el1);
+ unsigned long old = (sctlr & SCTLR_EL1_TCF_MASK) >> SCTLR_EL1_TCF_SHIFT;
+
+ sctlr &= ~(SCTLR_EL1_TCF_MASK | SCTLR_EL1_TCF0_MASK);
+ sctlr |= (tcf << SCTLR_EL1_TCF_SHIFT) & SCTLR_EL1_TCF_MASK;
+
+ write_sysreg(sctlr, sctlr_el1);
+ write_sysreg_s(0, TFSR_EL1);
+ isb();
+
+ return old;
+}
+
+
+static inline void mte_set_tag(void *addr, size_t size, unsigned int tag)
+{
+#ifdef CC_HAS_MTE
+ unsigned long in = (unsigned long)untagged(addr);
+ unsigned long start = ALIGN_DOWN(in, 16);
+ unsigned long end = ALIGN(in + size, 16);
+
+ for (unsigned long ptr = start; ptr < end; ptr += 16) {
+ asm volatile(".arch armv8.5-a+memtag\n"
+ "stg %0, [%0]"
+ :
+ : "r"(tagged(ptr, tag))
+ : "memory");
+ }
+#endif
+}
+
+static inline unsigned long get_clear_tfsr(void)
+{
+ unsigned long r;
+
+ dsb(nsh);
+ isb();
+
+ r = read_sysreg_s(TFSR_EL1);
+ write_sysreg_s(0, TFSR_EL1);
+
+ return r;
+}
+
+static void mte_sync_test(void)
+{
+ unsigned int *mem = tagged(alloc_page(), 1);
+ unsigned int val = 0;
+
+ mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
+ mte_set_tag(mem, PAGE_SIZE, 1);
+ memset(mem, 0xff, PAGE_SIZE);
+ mte_set_tcf(MTE_TCF_SYNC);
+
+ mte_exception = false;
+
+ install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
+
+ mem_read(tagged(mem, 2), &val);
+
+ report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
+
+ mte_exception = false;
+
+ mem_write(tagged(mem, 3), 0xbbbbbbbb);
+
+ report((*mem == 0xffffffff) && mte_exception && (get_clear_tfsr() == 0), "write");
+
+ free_page(untagged(mem));
+}
+
+static void mte_asymm_test(void)
+{
+ unsigned int *mem = tagged(alloc_page(), 2);
+ unsigned int val = 0;
+
+ mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
+ mte_set_tag(mem, PAGE_SIZE, 2);
+ memset(mem, 0xff, PAGE_SIZE);
+ mte_set_tcf(MTE_TCF_ASYMM);
+ mte_exception = false;
+
+ install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
+
+ mem_read(tagged(mem, 3), &val);
+ report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
+
+ install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, NULL);
+
+ mem_write(tagged(mem, 4), 0xaaaaaaaa);
+ report((*mem == 0xaaaaaaaa) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
+
+ free_page(untagged(mem));
+}
+
+static void mte_async_test(void)
+{
+ unsigned int *mem = tagged(alloc_page(), 3);
+ unsigned int val = 0;
+
+ mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
+ mte_set_tag(mem, PAGE_SIZE, 3);
+ memset(mem, 0xff, PAGE_SIZE);
+ mte_set_tcf(MTE_TCF_ASYNC);
+
+ mem_read(tagged(mem, 4), &val);
+ report((val == 0xffffffff) && (get_clear_tfsr() == TFSR_EL1_TF0), "read");
+
+ mem_write(tagged(mem, 5), 0xcccccccc);
+ report((*mem == 0xcccccccc) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
+
+ free_page(untagged(mem));
+}
+
+
+static unsigned int mte_version(void)
+{
+#ifdef CC_HAS_MTE
+ uint64_t r;
+
+ asm volatile("mrs %x0, id_aa64pfr1_el1" : "=r"(r));
+
+ return (r >> ID_AA64PFR1_EL1_MTE_SHIFT) & 0b1111;
+#else
+ report_info("Compiler lack MTE support");
+ return 0;
+#endif
+}
+
+int main(int argc, char *argv[])
+{
+
+ unsigned int version = mte_version();
+
+ if (version < 2) {
+ report_skip("No MTE support, skip...\n");
+ return report_summary();
+ }
+
+ if (argc < 2)
+ report_abort("no test specified");
+
+ report_prefix_push("mte");
+
+ mte_init();
+
+ if (strcmp(argv[1], "sync") == 0) {
+ report_prefix_push(argv[1]);
+ mte_sync_test();
+ report_prefix_pop();
+ } else if (strcmp(argv[1], "async") == 0) {
+ report_prefix_push(argv[1]);
+ if (version < 3) {
+ report_skip("No MTE async, skip...\n");
+ return report_summary();
+ }
+ mte_async_test();
+ report_prefix_pop();
+
+ } else if (strcmp(argv[1], "asymm") == 0) {
+ report_prefix_push(argv[1]);
+ if (version < 3) {
+ report_skip("No MTE asymm, skip...\n");
+ return report_summary();
+ }
+ mte_asymm_test();
+ report_prefix_pop();
+
+ } else {
+ report_abort("Unknown sub-test '%s'", argv[1]);
+ }
+
+ return report_summary();
+}
diff --git a/arm/unittests.cfg b/arm/unittests.cfg
index 2bdad67d..fe101145 100644
--- a/arm/unittests.cfg
+++ b/arm/unittests.cfg
@@ -271,3 +271,22 @@ smp = 2
groups = nodefault
accel = kvm
arch = arm64
+
+# MTE tests
+[mte-sync]
+file = mte.flat
+groups = mte
+extra_params = -machine mte=on -append 'sync'
+arch = arm64
+
+[mte-async]
+file = mte.flat
+groups = mte
+extra_params = -machine mte=on -append 'async'
+arch = arm64
+
+[mte-asymm]
+file = mte.flat
+groups = mte
+extra_params = -machine mte=on -append 'asymm'
+arch = arm64
diff --git a/lib/arm64/asm/mmu.h b/lib/arm64/asm/mmu.h
index 5c27edb2..9aedd09a 100644
--- a/lib/arm64/asm/mmu.h
+++ b/lib/arm64/asm/mmu.h
@@ -10,6 +10,7 @@
#define PMD_SECT_UNCACHED PMD_ATTRINDX(MT_DEVICE_nGnRE)
#define PTE_UNCACHED PTE_ATTRINDX(MT_DEVICE_nGnRE)
#define PTE_WBWA PTE_ATTRINDX(MT_NORMAL)
+#define PTE_TAGGED PTE_ATTRINDX(MT_NORMAL_TAGGED)
static inline void flush_tlb_all(void)
{
diff --git a/lib/arm64/asm/pgtable-hwdef.h b/lib/arm64/asm/pgtable-hwdef.h
index 8c41fe12..08a2e91c 100644
--- a/lib/arm64/asm/pgtable-hwdef.h
+++ b/lib/arm64/asm/pgtable-hwdef.h
@@ -145,6 +145,8 @@
#define TCR_TG1_64K (UL(3) << 30)
#define TCR_ASID16 (UL(1) << 36)
#define TCR_TBI0 (UL(1) << 37)
+#define TCR_TBI1 (UL(1) << 38)
+#define TCR_TCMA0 (UL(1) << 57)
/*
* Memory types available.
@@ -156,5 +158,6 @@
#define MT_NORMAL 4
#define MT_NORMAL_WT 5
#define MT_DEVICE_nGRE 6
+#define MT_NORMAL_TAGGED 7
#endif /* _ASMARM64_PGTABLE_HWDEF_H_ */
diff --git a/lib/arm64/asm/sysreg.h b/lib/arm64/asm/sysreg.h
index f214a4f0..b8d3d66e 100644
--- a/lib/arm64/asm/sysreg.h
+++ b/lib/arm64/asm/sysreg.h
@@ -28,6 +28,7 @@
.endm
#else
#include <libcflat.h>
+#include <bitops.h>
#define read_sysreg(r) ({ \
u64 __val; \
@@ -74,6 +75,7 @@ asm(
#endif /* __ASSEMBLY__ */
#define ID_AA64ISAR0_EL1_RNDR_SHIFT 60
+#define ID_AA64PFR1_EL1_MTE_SHIFT 8
#define ICC_PMR_EL1 sys_reg(3, 0, 4, 6, 0)
#define ICC_SGI1R_EL1 sys_reg(3, 0, 12, 11, 5)
@@ -81,7 +83,13 @@ asm(
#define ICC_EOIR1_EL1 sys_reg(3, 0, 12, 12, 1)
#define ICC_GRPEN1_EL1 sys_reg(3, 0, 12, 12, 7)
+#define TFSR_EL1 sys_reg(3, 0, 5, 6, 0)
+#define TFSR_EL1_TF0 _BITULL(0)
+#define TFSR_EL1_TF1 _BITULL(1)
+
/* System Control Register (SCTLR_EL1) bits */
+#define SCTLR_EL1_ATA _BITULL(43)
+#define SCTLR_EL1_ATA0 _BITULL(42)
#define SCTLR_EL1_LSMAOE _BITULL(29)
#define SCTLR_EL1_NTLSMD _BITULL(28)
#define SCTLR_EL1_EE _BITULL(25)
@@ -99,6 +107,12 @@ asm(
#define SCTLR_EL1_A _BITULL(1)
#define SCTLR_EL1_M _BITULL(0)
+#define SCTLR_EL1_TCF_SHIFT 40
+#define SCTLR_EL1_TCF_MASK GENMASK_ULL(41, 40)
+
+#define SCTLR_EL1_TCF0_SHIFT 38
+#define SCTLR_EL1_TCF0_MASK GENMASK_ULL(39, 38)
+
#define INIT_SCTLR_EL1_MMU_OFF \
(SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_EOS | \
SCTLR_EL1_TSCXT | SCTLR_EL1_EIS | SCTLR_EL1_SPAN | \
--
2.25.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-02-27 15:22 [PATCH v4] arm64: Add basic MTE test Vladimir Murzin
@ 2025-03-06 14:11 ` Alexandru Elisei
2025-03-06 14:25 ` Vladimir Murzin
2025-03-06 15:31 ` Andrew Jones
2025-03-06 15:45 ` Andrew Jones
` (2 subsequent siblings)
3 siblings, 2 replies; 9+ messages in thread
From: Alexandru Elisei @ 2025-03-06 14:11 UTC (permalink / raw)
To: Vladimir Murzin; +Cc: kvmarm, nikos.nikoleris, andrew.jones, eric.auger
Hi Vladimir,
Thank you for writing the test, looks good to me:
Reviewed-by: Alexandru Elisei <alexandru.elisei@arm.com>
@Drew: I'm trying to make time for the --qemu-cpu series. I have the first few
patches, the ones that implement ./configure --processor for arm64, I can send
those separately if you want.
Thanks,
Alex
On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
> Test tag storage access and tag mismatch for different MTE modes.
>
> Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> ---
> arm/Makefile.arm64 | 8 +
> arm/cstart64.S | 4 +-
> arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
> arm/unittests.cfg | 19 +++
> lib/arm64/asm/mmu.h | 1 +
> lib/arm64/asm/pgtable-hwdef.h | 3 +
> lib/arm64/asm/sysreg.h | 14 ++
> 7 files changed, 361 insertions(+), 1 deletion(-)
> create mode 100644 arm/mte.c
>
> diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
> index 3b9034e3..fbf11c98 100644
> --- a/arm/Makefile.arm64
> +++ b/arm/Makefile.arm64
> @@ -17,6 +17,13 @@ ifneq ($(strip $(sve_flag)),)
> CFLAGS += -DCC_HAS_SVE
> endif
>
> +mte_flag := $(call cc-option, -march=armv8.5-a+memtag, "")
> +ifneq ($(strip $(mte_flag)),)
> +# MTE is supported by the compiler, generate MTE instructions
> +CFLAGS += -DCC_HAS_MTE
> +endif
> +
> +
> mno_outline_atomics := $(call cc-option, -mno-outline-atomics, "")
> CFLAGS += $(mno_outline_atomics)
> CFLAGS += -DCONFIG_RELOC
> @@ -57,6 +64,7 @@ tests += $(TEST_DIR)/micro-bench.$(exe)
> tests += $(TEST_DIR)/cache.$(exe)
> tests += $(TEST_DIR)/debug.$(exe)
> tests += $(TEST_DIR)/fpu.$(exe)
> +tests += $(TEST_DIR)/mte.$(exe)
>
> include $(SRCDIR)/$(TEST_DIR)/Makefile.common
>
> diff --git a/arm/cstart64.S b/arm/cstart64.S
> index b480a552..b9d7a446 100644
> --- a/arm/cstart64.S
> +++ b/arm/cstart64.S
> @@ -242,6 +242,7 @@ halt:
> * NORMAL 100 11111111
> * NORMAL_WT 101 10111011
> * DEVICE_nGRE 110 00001000
> + * NORMAL_TAGGED 111 11110000
> */
> #define MAIR(attr, mt) ((attr) << ((mt) * 8))
>
> @@ -275,7 +276,8 @@ asm_mmu_enable:
> MAIR(0x44, MT_NORMAL_NC) | \
> MAIR(0xff, MT_NORMAL) | \
> MAIR(0xbb, MT_NORMAL_WT) | \
> - MAIR(0x08, MT_DEVICE_nGRE)
> + MAIR(0x08, MT_DEVICE_nGRE) | \
> + MAIR(0xf0, MT_NORMAL_TAGGED)
> msr mair_el1, x1
>
> /* TTBR0 */
> diff --git a/arm/mte.c b/arm/mte.c
> new file mode 100644
> index 00000000..f32203ce
> --- /dev/null
> +++ b/arm/mte.c
> @@ -0,0 +1,313 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2024 Arm Limited.
> + * All rights reserved.
> + */
> +
> +#include <libcflat.h>
> +#include <alloc_page.h>
> +#include <stdlib.h>
> +
> +#include <asm/mmu.h>
> +#include <asm/pgtable-hwdef.h>
> +#include <asm/processor.h>
> +#include <asm/sysreg.h>
> +#include <asm/thread_info.h>
> +
> +
> +/* Tag Check Faults cause a synchronous exception */
> +#define MTE_TCF_SYNC 0b01
> +/* Tag Check Faults are asynchronously accumulated */
> +#define MTE_TCF_ASYNC 0b10
> +/*
> + * Tag Check Faults cause a synchronous exception on reads,
> + * and are asynchronously accumulated on writes
> + */
> +#define MTE_TCF_ASYMM 0b11
> +
> +#define MTE_GRANULE_SIZE UL(16)
> +#define MTE_GRANULE_MASK (~(MTE_GRANULE_SIZE - 1))
> +#define MTE_TAG_SHIFT 56
> +
> +#define untagged(p) \
> +({ \
> + unsigned long __in = (unsigned long)(p); \
> + typeof(p) __out = (typeof(p))(__in & ~(MTE_GRANULE_MASK << MTE_TAG_SHIFT)); \
> + \
> + __out; \
> +})
> +
> +#define tagged(p, t) \
> +({ \
> + unsigned long __in = (unsigned long)(untagged(p)); \
> + unsigned long __tag = (unsigned long)(t) << MTE_TAG_SHIFT; \
> + typeof(p) __out = (typeof(p))(__in | __tag); \
> + \
> + __out; \
> +})
> +
> +/*
> + * If we use a normal (non hand coded inline assembly) load or store
> + * to access a tagged address, the compiler will reasonably assume
> + * that the access succeeded, and the next instruction may do
> + * something based on that assumption.
> + *
> + * But a test might want the tagged access to fail on purpose, and if
> + * we advance the PC to the next instruction, the one added by the
> + * compiler, we might leave the program in an unexpected state.
> + */
> +static inline void mem_read(unsigned int *addr, unsigned int *res)
> +{
> + unsigned int r;
> +
> + asm volatile ("ldr %0,[%1]\n"
> + "str %0,[%2]\n"
> + : "=&r" (r)
> + : "r" (addr), "r" (res) : "memory");
> +}
> +
> +static inline void mem_write(unsigned int *addr, unsigned int val)
> +{
> + /* The NOP allows the same exception handler as mem_read() to be used. */
> + asm volatile ("str %0,[%1]\n"
> + "nop\n"
> + :
> + : "r" (val), "r" (addr)
> + : "memory");
> +}
> +
> +static volatile bool mte_exception;
> +
> +static void mte_fault_handler(struct pt_regs *regs, unsigned int esr)
> +{
> + unsigned int dfsc = esr & GENMASK(5, 0);
> + unsigned int fnv = esr & BIT(10);
> +
> + if (dfsc == 0b010001) {
> + if (fnv)
> + report_info("Unexpected non-zero FnV");
> + mte_exception = true;
> + }
> +
> + /*
> + * mem_read() reads the value from the tagged pointer, then
> + * stores this value in the untagged 'res' pointer. The
> + * function that called mem_read() will want to check that the
> + * initial value of 'res' hasn't changed if a tag check fault
> + * is reported. Skip over two instructions so 'res' isn't
> + * overwritten.
> + */
> + regs->pc += 8;
> +}
> +
> +static inline void mmu_set_tagged(pgd_t *pgtable, unsigned long vaddr)
> +{
> + pteval_t *p_pte = follow_pte(pgtable, untagged(vaddr));
> +
> + if (p_pte) {
> + pteval_t entry = *p_pte;
> +
> + entry &= ~PTE_ATTRINDX_MASK;
> + entry |= PTE_ATTRINDX(MT_NORMAL_TAGGED);
> +
> + WRITE_ONCE(*p_pte, entry);
> + flush_tlb_page(vaddr);
> + } else {
> + report_abort("Cannot find PTE");
> + }
> +}
> +
> +static void mte_init(void)
> +{
> + unsigned long sctlr = read_sysreg(sctlr_el1);
> + unsigned long tcr = read_sysreg(tcr_el1);
> +
> + sctlr &= ~SCTLR_EL1_TCF_MASK;
> + sctlr |= SCTLR_EL1_ATA;
> +
> + tcr &= ~TCR_TCMA0;
> + tcr |= TCR_TBI0;
> +
> + write_sysreg(sctlr, sctlr_el1);
> + write_sysreg(tcr, tcr_el1);
> +
> + isb();
> + flush_tlb_all();
> +}
> +
> +static inline unsigned long mte_set_tcf(unsigned long tcf)
> +{
> + unsigned long sctlr = read_sysreg(sctlr_el1);
> + unsigned long old = (sctlr & SCTLR_EL1_TCF_MASK) >> SCTLR_EL1_TCF_SHIFT;
> +
> + sctlr &= ~(SCTLR_EL1_TCF_MASK | SCTLR_EL1_TCF0_MASK);
> + sctlr |= (tcf << SCTLR_EL1_TCF_SHIFT) & SCTLR_EL1_TCF_MASK;
> +
> + write_sysreg(sctlr, sctlr_el1);
> + write_sysreg_s(0, TFSR_EL1);
> + isb();
> +
> + return old;
> +}
> +
> +
> +static inline void mte_set_tag(void *addr, size_t size, unsigned int tag)
> +{
> +#ifdef CC_HAS_MTE
> + unsigned long in = (unsigned long)untagged(addr);
> + unsigned long start = ALIGN_DOWN(in, 16);
> + unsigned long end = ALIGN(in + size, 16);
> +
> + for (unsigned long ptr = start; ptr < end; ptr += 16) {
> + asm volatile(".arch armv8.5-a+memtag\n"
> + "stg %0, [%0]"
> + :
> + : "r"(tagged(ptr, tag))
> + : "memory");
> + }
> +#endif
> +}
> +
> +static inline unsigned long get_clear_tfsr(void)
> +{
> + unsigned long r;
> +
> + dsb(nsh);
> + isb();
> +
> + r = read_sysreg_s(TFSR_EL1);
> + write_sysreg_s(0, TFSR_EL1);
> +
> + return r;
> +}
> +
> +static void mte_sync_test(void)
> +{
> + unsigned int *mem = tagged(alloc_page(), 1);
> + unsigned int val = 0;
> +
> + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
> + mte_set_tag(mem, PAGE_SIZE, 1);
> + memset(mem, 0xff, PAGE_SIZE);
> + mte_set_tcf(MTE_TCF_SYNC);
> +
> + mte_exception = false;
> +
> + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
> +
> + mem_read(tagged(mem, 2), &val);
> +
> + report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
> +
> + mte_exception = false;
> +
> + mem_write(tagged(mem, 3), 0xbbbbbbbb);
> +
> + report((*mem == 0xffffffff) && mte_exception && (get_clear_tfsr() == 0), "write");
> +
> + free_page(untagged(mem));
> +}
> +
> +static void mte_asymm_test(void)
> +{
> + unsigned int *mem = tagged(alloc_page(), 2);
> + unsigned int val = 0;
> +
> + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
> + mte_set_tag(mem, PAGE_SIZE, 2);
> + memset(mem, 0xff, PAGE_SIZE);
> + mte_set_tcf(MTE_TCF_ASYMM);
> + mte_exception = false;
> +
> + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
> +
> + mem_read(tagged(mem, 3), &val);
> + report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
> +
> + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, NULL);
> +
> + mem_write(tagged(mem, 4), 0xaaaaaaaa);
> + report((*mem == 0xaaaaaaaa) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
> +
> + free_page(untagged(mem));
> +}
> +
> +static void mte_async_test(void)
> +{
> + unsigned int *mem = tagged(alloc_page(), 3);
> + unsigned int val = 0;
> +
> + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
> + mte_set_tag(mem, PAGE_SIZE, 3);
> + memset(mem, 0xff, PAGE_SIZE);
> + mte_set_tcf(MTE_TCF_ASYNC);
> +
> + mem_read(tagged(mem, 4), &val);
> + report((val == 0xffffffff) && (get_clear_tfsr() == TFSR_EL1_TF0), "read");
> +
> + mem_write(tagged(mem, 5), 0xcccccccc);
> + report((*mem == 0xcccccccc) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
> +
> + free_page(untagged(mem));
> +}
> +
> +
> +static unsigned int mte_version(void)
> +{
> +#ifdef CC_HAS_MTE
> + uint64_t r;
> +
> + asm volatile("mrs %x0, id_aa64pfr1_el1" : "=r"(r));
> +
> + return (r >> ID_AA64PFR1_EL1_MTE_SHIFT) & 0b1111;
> +#else
> + report_info("Compiler lack MTE support");
> + return 0;
> +#endif
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +
> + unsigned int version = mte_version();
> +
> + if (version < 2) {
> + report_skip("No MTE support, skip...\n");
> + return report_summary();
> + }
> +
> + if (argc < 2)
> + report_abort("no test specified");
> +
> + report_prefix_push("mte");
> +
> + mte_init();
> +
> + if (strcmp(argv[1], "sync") == 0) {
> + report_prefix_push(argv[1]);
> + mte_sync_test();
> + report_prefix_pop();
> + } else if (strcmp(argv[1], "async") == 0) {
> + report_prefix_push(argv[1]);
> + if (version < 3) {
> + report_skip("No MTE async, skip...\n");
> + return report_summary();
> + }
> + mte_async_test();
> + report_prefix_pop();
> +
> + } else if (strcmp(argv[1], "asymm") == 0) {
> + report_prefix_push(argv[1]);
> + if (version < 3) {
> + report_skip("No MTE asymm, skip...\n");
> + return report_summary();
> + }
> + mte_asymm_test();
> + report_prefix_pop();
> +
> + } else {
> + report_abort("Unknown sub-test '%s'", argv[1]);
> + }
> +
> + return report_summary();
> +}
> diff --git a/arm/unittests.cfg b/arm/unittests.cfg
> index 2bdad67d..fe101145 100644
> --- a/arm/unittests.cfg
> +++ b/arm/unittests.cfg
> @@ -271,3 +271,22 @@ smp = 2
> groups = nodefault
> accel = kvm
> arch = arm64
> +
> +# MTE tests
> +[mte-sync]
> +file = mte.flat
> +groups = mte
> +extra_params = -machine mte=on -append 'sync'
> +arch = arm64
> +
> +[mte-async]
> +file = mte.flat
> +groups = mte
> +extra_params = -machine mte=on -append 'async'
> +arch = arm64
> +
> +[mte-asymm]
> +file = mte.flat
> +groups = mte
> +extra_params = -machine mte=on -append 'asymm'
> +arch = arm64
> diff --git a/lib/arm64/asm/mmu.h b/lib/arm64/asm/mmu.h
> index 5c27edb2..9aedd09a 100644
> --- a/lib/arm64/asm/mmu.h
> +++ b/lib/arm64/asm/mmu.h
> @@ -10,6 +10,7 @@
> #define PMD_SECT_UNCACHED PMD_ATTRINDX(MT_DEVICE_nGnRE)
> #define PTE_UNCACHED PTE_ATTRINDX(MT_DEVICE_nGnRE)
> #define PTE_WBWA PTE_ATTRINDX(MT_NORMAL)
> +#define PTE_TAGGED PTE_ATTRINDX(MT_NORMAL_TAGGED)
>
> static inline void flush_tlb_all(void)
> {
> diff --git a/lib/arm64/asm/pgtable-hwdef.h b/lib/arm64/asm/pgtable-hwdef.h
> index 8c41fe12..08a2e91c 100644
> --- a/lib/arm64/asm/pgtable-hwdef.h
> +++ b/lib/arm64/asm/pgtable-hwdef.h
> @@ -145,6 +145,8 @@
> #define TCR_TG1_64K (UL(3) << 30)
> #define TCR_ASID16 (UL(1) << 36)
> #define TCR_TBI0 (UL(1) << 37)
> +#define TCR_TBI1 (UL(1) << 38)
> +#define TCR_TCMA0 (UL(1) << 57)
>
> /*
> * Memory types available.
> @@ -156,5 +158,6 @@
> #define MT_NORMAL 4
> #define MT_NORMAL_WT 5
> #define MT_DEVICE_nGRE 6
> +#define MT_NORMAL_TAGGED 7
>
> #endif /* _ASMARM64_PGTABLE_HWDEF_H_ */
> diff --git a/lib/arm64/asm/sysreg.h b/lib/arm64/asm/sysreg.h
> index f214a4f0..b8d3d66e 100644
> --- a/lib/arm64/asm/sysreg.h
> +++ b/lib/arm64/asm/sysreg.h
> @@ -28,6 +28,7 @@
> .endm
> #else
> #include <libcflat.h>
> +#include <bitops.h>
>
> #define read_sysreg(r) ({ \
> u64 __val; \
> @@ -74,6 +75,7 @@ asm(
> #endif /* __ASSEMBLY__ */
>
> #define ID_AA64ISAR0_EL1_RNDR_SHIFT 60
> +#define ID_AA64PFR1_EL1_MTE_SHIFT 8
>
> #define ICC_PMR_EL1 sys_reg(3, 0, 4, 6, 0)
> #define ICC_SGI1R_EL1 sys_reg(3, 0, 12, 11, 5)
> @@ -81,7 +83,13 @@ asm(
> #define ICC_EOIR1_EL1 sys_reg(3, 0, 12, 12, 1)
> #define ICC_GRPEN1_EL1 sys_reg(3, 0, 12, 12, 7)
>
> +#define TFSR_EL1 sys_reg(3, 0, 5, 6, 0)
> +#define TFSR_EL1_TF0 _BITULL(0)
> +#define TFSR_EL1_TF1 _BITULL(1)
> +
> /* System Control Register (SCTLR_EL1) bits */
> +#define SCTLR_EL1_ATA _BITULL(43)
> +#define SCTLR_EL1_ATA0 _BITULL(42)
> #define SCTLR_EL1_LSMAOE _BITULL(29)
> #define SCTLR_EL1_NTLSMD _BITULL(28)
> #define SCTLR_EL1_EE _BITULL(25)
> @@ -99,6 +107,12 @@ asm(
> #define SCTLR_EL1_A _BITULL(1)
> #define SCTLR_EL1_M _BITULL(0)
>
> +#define SCTLR_EL1_TCF_SHIFT 40
> +#define SCTLR_EL1_TCF_MASK GENMASK_ULL(41, 40)
> +
> +#define SCTLR_EL1_TCF0_SHIFT 38
> +#define SCTLR_EL1_TCF0_MASK GENMASK_ULL(39, 38)
> +
> #define INIT_SCTLR_EL1_MMU_OFF \
> (SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_EOS | \
> SCTLR_EL1_TSCXT | SCTLR_EL1_EIS | SCTLR_EL1_SPAN | \
> --
> 2.25.1
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-03-06 14:11 ` Alexandru Elisei
@ 2025-03-06 14:25 ` Vladimir Murzin
2025-03-06 15:31 ` Andrew Jones
1 sibling, 0 replies; 9+ messages in thread
From: Vladimir Murzin @ 2025-03-06 14:25 UTC (permalink / raw)
To: Alexandru Elisei; +Cc: kvmarm, nikos.nikoleris, andrew.jones, eric.auger
Hi Alex,
On 3/6/25 14:11, Alexandru Elisei wrote:
> Hi Vladimir,
>
> Thank you for writing the test, looks good to me:
>
> Reviewed-by: Alexandru Elisei <alexandru.elisei@arm.com>
>
Thank you for your time, much appreciated!
Vladimir
> @Drew: I'm trying to make time for the --qemu-cpu series. I have the first few
> patches, the ones that implement ./configure --processor for arm64, I can send
> those separately if you want.
>
> Thanks,
> Alex
>
> On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
>> Test tag storage access and tag mismatch for different MTE modes.
>>
>> Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
>> ---
>> arm/Makefile.arm64 | 8 +
>> arm/cstart64.S | 4 +-
>> arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
>> arm/unittests.cfg | 19 +++
>> lib/arm64/asm/mmu.h | 1 +
>> lib/arm64/asm/pgtable-hwdef.h | 3 +
>> lib/arm64/asm/sysreg.h | 14 ++
>> 7 files changed, 361 insertions(+), 1 deletion(-)
>> create mode 100644 arm/mte.c
>>
>> diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
>> index 3b9034e3..fbf11c98 100644
>> --- a/arm/Makefile.arm64
>> +++ b/arm/Makefile.arm64
>> @@ -17,6 +17,13 @@ ifneq ($(strip $(sve_flag)),)
>> CFLAGS += -DCC_HAS_SVE
>> endif
>>
>> +mte_flag := $(call cc-option, -march=armv8.5-a+memtag, "")
>> +ifneq ($(strip $(mte_flag)),)
>> +# MTE is supported by the compiler, generate MTE instructions
>> +CFLAGS += -DCC_HAS_MTE
>> +endif
>> +
>> +
>> mno_outline_atomics := $(call cc-option, -mno-outline-atomics, "")
>> CFLAGS += $(mno_outline_atomics)
>> CFLAGS += -DCONFIG_RELOC
>> @@ -57,6 +64,7 @@ tests += $(TEST_DIR)/micro-bench.$(exe)
>> tests += $(TEST_DIR)/cache.$(exe)
>> tests += $(TEST_DIR)/debug.$(exe)
>> tests += $(TEST_DIR)/fpu.$(exe)
>> +tests += $(TEST_DIR)/mte.$(exe)
>>
>> include $(SRCDIR)/$(TEST_DIR)/Makefile.common
>>
>> diff --git a/arm/cstart64.S b/arm/cstart64.S
>> index b480a552..b9d7a446 100644
>> --- a/arm/cstart64.S
>> +++ b/arm/cstart64.S
>> @@ -242,6 +242,7 @@ halt:
>> * NORMAL 100 11111111
>> * NORMAL_WT 101 10111011
>> * DEVICE_nGRE 110 00001000
>> + * NORMAL_TAGGED 111 11110000
>> */
>> #define MAIR(attr, mt) ((attr) << ((mt) * 8))
>>
>> @@ -275,7 +276,8 @@ asm_mmu_enable:
>> MAIR(0x44, MT_NORMAL_NC) | \
>> MAIR(0xff, MT_NORMAL) | \
>> MAIR(0xbb, MT_NORMAL_WT) | \
>> - MAIR(0x08, MT_DEVICE_nGRE)
>> + MAIR(0x08, MT_DEVICE_nGRE) | \
>> + MAIR(0xf0, MT_NORMAL_TAGGED)
>> msr mair_el1, x1
>>
>> /* TTBR0 */
>> diff --git a/arm/mte.c b/arm/mte.c
>> new file mode 100644
>> index 00000000..f32203ce
>> --- /dev/null
>> +++ b/arm/mte.c
>> @@ -0,0 +1,313 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright (C) 2024 Arm Limited.
>> + * All rights reserved.
>> + */
>> +
>> +#include <libcflat.h>
>> +#include <alloc_page.h>
>> +#include <stdlib.h>
>> +
>> +#include <asm/mmu.h>
>> +#include <asm/pgtable-hwdef.h>
>> +#include <asm/processor.h>
>> +#include <asm/sysreg.h>
>> +#include <asm/thread_info.h>
>> +
>> +
>> +/* Tag Check Faults cause a synchronous exception */
>> +#define MTE_TCF_SYNC 0b01
>> +/* Tag Check Faults are asynchronously accumulated */
>> +#define MTE_TCF_ASYNC 0b10
>> +/*
>> + * Tag Check Faults cause a synchronous exception on reads,
>> + * and are asynchronously accumulated on writes
>> + */
>> +#define MTE_TCF_ASYMM 0b11
>> +
>> +#define MTE_GRANULE_SIZE UL(16)
>> +#define MTE_GRANULE_MASK (~(MTE_GRANULE_SIZE - 1))
>> +#define MTE_TAG_SHIFT 56
>> +
>> +#define untagged(p) \
>> +({ \
>> + unsigned long __in = (unsigned long)(p); \
>> + typeof(p) __out = (typeof(p))(__in & ~(MTE_GRANULE_MASK << MTE_TAG_SHIFT)); \
>> + \
>> + __out; \
>> +})
>> +
>> +#define tagged(p, t) \
>> +({ \
>> + unsigned long __in = (unsigned long)(untagged(p)); \
>> + unsigned long __tag = (unsigned long)(t) << MTE_TAG_SHIFT; \
>> + typeof(p) __out = (typeof(p))(__in | __tag); \
>> + \
>> + __out; \
>> +})
>> +
>> +/*
>> + * If we use a normal (non hand coded inline assembly) load or store
>> + * to access a tagged address, the compiler will reasonably assume
>> + * that the access succeeded, and the next instruction may do
>> + * something based on that assumption.
>> + *
>> + * But a test might want the tagged access to fail on purpose, and if
>> + * we advance the PC to the next instruction, the one added by the
>> + * compiler, we might leave the program in an unexpected state.
>> + */
>> +static inline void mem_read(unsigned int *addr, unsigned int *res)
>> +{
>> + unsigned int r;
>> +
>> + asm volatile ("ldr %0,[%1]\n"
>> + "str %0,[%2]\n"
>> + : "=&r" (r)
>> + : "r" (addr), "r" (res) : "memory");
>> +}
>> +
>> +static inline void mem_write(unsigned int *addr, unsigned int val)
>> +{
>> + /* The NOP allows the same exception handler as mem_read() to be used. */
>> + asm volatile ("str %0,[%1]\n"
>> + "nop\n"
>> + :
>> + : "r" (val), "r" (addr)
>> + : "memory");
>> +}
>> +
>> +static volatile bool mte_exception;
>> +
>> +static void mte_fault_handler(struct pt_regs *regs, unsigned int esr)
>> +{
>> + unsigned int dfsc = esr & GENMASK(5, 0);
>> + unsigned int fnv = esr & BIT(10);
>> +
>> + if (dfsc == 0b010001) {
>> + if (fnv)
>> + report_info("Unexpected non-zero FnV");
>> + mte_exception = true;
>> + }
>> +
>> + /*
>> + * mem_read() reads the value from the tagged pointer, then
>> + * stores this value in the untagged 'res' pointer. The
>> + * function that called mem_read() will want to check that the
>> + * initial value of 'res' hasn't changed if a tag check fault
>> + * is reported. Skip over two instructions so 'res' isn't
>> + * overwritten.
>> + */
>> + regs->pc += 8;
>> +}
>> +
>> +static inline void mmu_set_tagged(pgd_t *pgtable, unsigned long vaddr)
>> +{
>> + pteval_t *p_pte = follow_pte(pgtable, untagged(vaddr));
>> +
>> + if (p_pte) {
>> + pteval_t entry = *p_pte;
>> +
>> + entry &= ~PTE_ATTRINDX_MASK;
>> + entry |= PTE_ATTRINDX(MT_NORMAL_TAGGED);
>> +
>> + WRITE_ONCE(*p_pte, entry);
>> + flush_tlb_page(vaddr);
>> + } else {
>> + report_abort("Cannot find PTE");
>> + }
>> +}
>> +
>> +static void mte_init(void)
>> +{
>> + unsigned long sctlr = read_sysreg(sctlr_el1);
>> + unsigned long tcr = read_sysreg(tcr_el1);
>> +
>> + sctlr &= ~SCTLR_EL1_TCF_MASK;
>> + sctlr |= SCTLR_EL1_ATA;
>> +
>> + tcr &= ~TCR_TCMA0;
>> + tcr |= TCR_TBI0;
>> +
>> + write_sysreg(sctlr, sctlr_el1);
>> + write_sysreg(tcr, tcr_el1);
>> +
>> + isb();
>> + flush_tlb_all();
>> +}
>> +
>> +static inline unsigned long mte_set_tcf(unsigned long tcf)
>> +{
>> + unsigned long sctlr = read_sysreg(sctlr_el1);
>> + unsigned long old = (sctlr & SCTLR_EL1_TCF_MASK) >> SCTLR_EL1_TCF_SHIFT;
>> +
>> + sctlr &= ~(SCTLR_EL1_TCF_MASK | SCTLR_EL1_TCF0_MASK);
>> + sctlr |= (tcf << SCTLR_EL1_TCF_SHIFT) & SCTLR_EL1_TCF_MASK;
>> +
>> + write_sysreg(sctlr, sctlr_el1);
>> + write_sysreg_s(0, TFSR_EL1);
>> + isb();
>> +
>> + return old;
>> +}
>> +
>> +
>> +static inline void mte_set_tag(void *addr, size_t size, unsigned int tag)
>> +{
>> +#ifdef CC_HAS_MTE
>> + unsigned long in = (unsigned long)untagged(addr);
>> + unsigned long start = ALIGN_DOWN(in, 16);
>> + unsigned long end = ALIGN(in + size, 16);
>> +
>> + for (unsigned long ptr = start; ptr < end; ptr += 16) {
>> + asm volatile(".arch armv8.5-a+memtag\n"
>> + "stg %0, [%0]"
>> + :
>> + : "r"(tagged(ptr, tag))
>> + : "memory");
>> + }
>> +#endif
>> +}
>> +
>> +static inline unsigned long get_clear_tfsr(void)
>> +{
>> + unsigned long r;
>> +
>> + dsb(nsh);
>> + isb();
>> +
>> + r = read_sysreg_s(TFSR_EL1);
>> + write_sysreg_s(0, TFSR_EL1);
>> +
>> + return r;
>> +}
>> +
>> +static void mte_sync_test(void)
>> +{
>> + unsigned int *mem = tagged(alloc_page(), 1);
>> + unsigned int val = 0;
>> +
>> + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
>> + mte_set_tag(mem, PAGE_SIZE, 1);
>> + memset(mem, 0xff, PAGE_SIZE);
>> + mte_set_tcf(MTE_TCF_SYNC);
>> +
>> + mte_exception = false;
>> +
>> + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
>> +
>> + mem_read(tagged(mem, 2), &val);
>> +
>> + report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
>> +
>> + mte_exception = false;
>> +
>> + mem_write(tagged(mem, 3), 0xbbbbbbbb);
>> +
>> + report((*mem == 0xffffffff) && mte_exception && (get_clear_tfsr() == 0), "write");
>> +
>> + free_page(untagged(mem));
>> +}
>> +
>> +static void mte_asymm_test(void)
>> +{
>> + unsigned int *mem = tagged(alloc_page(), 2);
>> + unsigned int val = 0;
>> +
>> + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
>> + mte_set_tag(mem, PAGE_SIZE, 2);
>> + memset(mem, 0xff, PAGE_SIZE);
>> + mte_set_tcf(MTE_TCF_ASYMM);
>> + mte_exception = false;
>> +
>> + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
>> +
>> + mem_read(tagged(mem, 3), &val);
>> + report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
>> +
>> + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, NULL);
>> +
>> + mem_write(tagged(mem, 4), 0xaaaaaaaa);
>> + report((*mem == 0xaaaaaaaa) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
>> +
>> + free_page(untagged(mem));
>> +}
>> +
>> +static void mte_async_test(void)
>> +{
>> + unsigned int *mem = tagged(alloc_page(), 3);
>> + unsigned int val = 0;
>> +
>> + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
>> + mte_set_tag(mem, PAGE_SIZE, 3);
>> + memset(mem, 0xff, PAGE_SIZE);
>> + mte_set_tcf(MTE_TCF_ASYNC);
>> +
>> + mem_read(tagged(mem, 4), &val);
>> + report((val == 0xffffffff) && (get_clear_tfsr() == TFSR_EL1_TF0), "read");
>> +
>> + mem_write(tagged(mem, 5), 0xcccccccc);
>> + report((*mem == 0xcccccccc) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
>> +
>> + free_page(untagged(mem));
>> +}
>> +
>> +
>> +static unsigned int mte_version(void)
>> +{
>> +#ifdef CC_HAS_MTE
>> + uint64_t r;
>> +
>> + asm volatile("mrs %x0, id_aa64pfr1_el1" : "=r"(r));
>> +
>> + return (r >> ID_AA64PFR1_EL1_MTE_SHIFT) & 0b1111;
>> +#else
>> + report_info("Compiler lack MTE support");
>> + return 0;
>> +#endif
>> +}
>> +
>> +int main(int argc, char *argv[])
>> +{
>> +
>> + unsigned int version = mte_version();
>> +
>> + if (version < 2) {
>> + report_skip("No MTE support, skip...\n");
>> + return report_summary();
>> + }
>> +
>> + if (argc < 2)
>> + report_abort("no test specified");
>> +
>> + report_prefix_push("mte");
>> +
>> + mte_init();
>> +
>> + if (strcmp(argv[1], "sync") == 0) {
>> + report_prefix_push(argv[1]);
>> + mte_sync_test();
>> + report_prefix_pop();
>> + } else if (strcmp(argv[1], "async") == 0) {
>> + report_prefix_push(argv[1]);
>> + if (version < 3) {
>> + report_skip("No MTE async, skip...\n");
>> + return report_summary();
>> + }
>> + mte_async_test();
>> + report_prefix_pop();
>> +
>> + } else if (strcmp(argv[1], "asymm") == 0) {
>> + report_prefix_push(argv[1]);
>> + if (version < 3) {
>> + report_skip("No MTE asymm, skip...\n");
>> + return report_summary();
>> + }
>> + mte_asymm_test();
>> + report_prefix_pop();
>> +
>> + } else {
>> + report_abort("Unknown sub-test '%s'", argv[1]);
>> + }
>> +
>> + return report_summary();
>> +}
>> diff --git a/arm/unittests.cfg b/arm/unittests.cfg
>> index 2bdad67d..fe101145 100644
>> --- a/arm/unittests.cfg
>> +++ b/arm/unittests.cfg
>> @@ -271,3 +271,22 @@ smp = 2
>> groups = nodefault
>> accel = kvm
>> arch = arm64
>> +
>> +# MTE tests
>> +[mte-sync]
>> +file = mte.flat
>> +groups = mte
>> +extra_params = -machine mte=on -append 'sync'
>> +arch = arm64
>> +
>> +[mte-async]
>> +file = mte.flat
>> +groups = mte
>> +extra_params = -machine mte=on -append 'async'
>> +arch = arm64
>> +
>> +[mte-asymm]
>> +file = mte.flat
>> +groups = mte
>> +extra_params = -machine mte=on -append 'asymm'
>> +arch = arm64
>> diff --git a/lib/arm64/asm/mmu.h b/lib/arm64/asm/mmu.h
>> index 5c27edb2..9aedd09a 100644
>> --- a/lib/arm64/asm/mmu.h
>> +++ b/lib/arm64/asm/mmu.h
>> @@ -10,6 +10,7 @@
>> #define PMD_SECT_UNCACHED PMD_ATTRINDX(MT_DEVICE_nGnRE)
>> #define PTE_UNCACHED PTE_ATTRINDX(MT_DEVICE_nGnRE)
>> #define PTE_WBWA PTE_ATTRINDX(MT_NORMAL)
>> +#define PTE_TAGGED PTE_ATTRINDX(MT_NORMAL_TAGGED)
>>
>> static inline void flush_tlb_all(void)
>> {
>> diff --git a/lib/arm64/asm/pgtable-hwdef.h b/lib/arm64/asm/pgtable-hwdef.h
>> index 8c41fe12..08a2e91c 100644
>> --- a/lib/arm64/asm/pgtable-hwdef.h
>> +++ b/lib/arm64/asm/pgtable-hwdef.h
>> @@ -145,6 +145,8 @@
>> #define TCR_TG1_64K (UL(3) << 30)
>> #define TCR_ASID16 (UL(1) << 36)
>> #define TCR_TBI0 (UL(1) << 37)
>> +#define TCR_TBI1 (UL(1) << 38)
>> +#define TCR_TCMA0 (UL(1) << 57)
>>
>> /*
>> * Memory types available.
>> @@ -156,5 +158,6 @@
>> #define MT_NORMAL 4
>> #define MT_NORMAL_WT 5
>> #define MT_DEVICE_nGRE 6
>> +#define MT_NORMAL_TAGGED 7
>>
>> #endif /* _ASMARM64_PGTABLE_HWDEF_H_ */
>> diff --git a/lib/arm64/asm/sysreg.h b/lib/arm64/asm/sysreg.h
>> index f214a4f0..b8d3d66e 100644
>> --- a/lib/arm64/asm/sysreg.h
>> +++ b/lib/arm64/asm/sysreg.h
>> @@ -28,6 +28,7 @@
>> .endm
>> #else
>> #include <libcflat.h>
>> +#include <bitops.h>
>>
>> #define read_sysreg(r) ({ \
>> u64 __val; \
>> @@ -74,6 +75,7 @@ asm(
>> #endif /* __ASSEMBLY__ */
>>
>> #define ID_AA64ISAR0_EL1_RNDR_SHIFT 60
>> +#define ID_AA64PFR1_EL1_MTE_SHIFT 8
>>
>> #define ICC_PMR_EL1 sys_reg(3, 0, 4, 6, 0)
>> #define ICC_SGI1R_EL1 sys_reg(3, 0, 12, 11, 5)
>> @@ -81,7 +83,13 @@ asm(
>> #define ICC_EOIR1_EL1 sys_reg(3, 0, 12, 12, 1)
>> #define ICC_GRPEN1_EL1 sys_reg(3, 0, 12, 12, 7)
>>
>> +#define TFSR_EL1 sys_reg(3, 0, 5, 6, 0)
>> +#define TFSR_EL1_TF0 _BITULL(0)
>> +#define TFSR_EL1_TF1 _BITULL(1)
>> +
>> /* System Control Register (SCTLR_EL1) bits */
>> +#define SCTLR_EL1_ATA _BITULL(43)
>> +#define SCTLR_EL1_ATA0 _BITULL(42)
>> #define SCTLR_EL1_LSMAOE _BITULL(29)
>> #define SCTLR_EL1_NTLSMD _BITULL(28)
>> #define SCTLR_EL1_EE _BITULL(25)
>> @@ -99,6 +107,12 @@ asm(
>> #define SCTLR_EL1_A _BITULL(1)
>> #define SCTLR_EL1_M _BITULL(0)
>>
>> +#define SCTLR_EL1_TCF_SHIFT 40
>> +#define SCTLR_EL1_TCF_MASK GENMASK_ULL(41, 40)
>> +
>> +#define SCTLR_EL1_TCF0_SHIFT 38
>> +#define SCTLR_EL1_TCF0_MASK GENMASK_ULL(39, 38)
>> +
>> #define INIT_SCTLR_EL1_MMU_OFF \
>> (SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_EOS | \
>> SCTLR_EL1_TSCXT | SCTLR_EL1_EIS | SCTLR_EL1_SPAN | \
>> --
>> 2.25.1
>>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-03-06 14:11 ` Alexandru Elisei
2025-03-06 14:25 ` Vladimir Murzin
@ 2025-03-06 15:31 ` Andrew Jones
1 sibling, 0 replies; 9+ messages in thread
From: Andrew Jones @ 2025-03-06 15:31 UTC (permalink / raw)
To: Alexandru Elisei; +Cc: Vladimir Murzin, kvmarm, nikos.nikoleris, eric.auger
On Thu, Mar 06, 2025 at 02:11:30PM +0000, Alexandru Elisei wrote:
> Hi Vladimir,
>
> Thank you for writing the test, looks good to me:
>
> Reviewed-by: Alexandru Elisei <alexandru.elisei@arm.com>
>
> @Drew: I'm trying to make time for the --qemu-cpu series. I have the first few
> patches, the ones that implement ./configure --processor for arm64, I can send
> those separately if you want.
Works for me.
Thanks,
drew
>
> Thanks,
> Alex
>
> On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
> > Test tag storage access and tag mismatch for different MTE modes.
> >
> > Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> > ---
> > arm/Makefile.arm64 | 8 +
> > arm/cstart64.S | 4 +-
> > arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
> > arm/unittests.cfg | 19 +++
> > lib/arm64/asm/mmu.h | 1 +
> > lib/arm64/asm/pgtable-hwdef.h | 3 +
> > lib/arm64/asm/sysreg.h | 14 ++
> > 7 files changed, 361 insertions(+), 1 deletion(-)
> > create mode 100644 arm/mte.c
> >
> > diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64
> > index 3b9034e3..fbf11c98 100644
> > --- a/arm/Makefile.arm64
> > +++ b/arm/Makefile.arm64
> > @@ -17,6 +17,13 @@ ifneq ($(strip $(sve_flag)),)
> > CFLAGS += -DCC_HAS_SVE
> > endif
> >
> > +mte_flag := $(call cc-option, -march=armv8.5-a+memtag, "")
> > +ifneq ($(strip $(mte_flag)),)
> > +# MTE is supported by the compiler, generate MTE instructions
> > +CFLAGS += -DCC_HAS_MTE
> > +endif
> > +
> > +
> > mno_outline_atomics := $(call cc-option, -mno-outline-atomics, "")
> > CFLAGS += $(mno_outline_atomics)
> > CFLAGS += -DCONFIG_RELOC
> > @@ -57,6 +64,7 @@ tests += $(TEST_DIR)/micro-bench.$(exe)
> > tests += $(TEST_DIR)/cache.$(exe)
> > tests += $(TEST_DIR)/debug.$(exe)
> > tests += $(TEST_DIR)/fpu.$(exe)
> > +tests += $(TEST_DIR)/mte.$(exe)
> >
> > include $(SRCDIR)/$(TEST_DIR)/Makefile.common
> >
> > diff --git a/arm/cstart64.S b/arm/cstart64.S
> > index b480a552..b9d7a446 100644
> > --- a/arm/cstart64.S
> > +++ b/arm/cstart64.S
> > @@ -242,6 +242,7 @@ halt:
> > * NORMAL 100 11111111
> > * NORMAL_WT 101 10111011
> > * DEVICE_nGRE 110 00001000
> > + * NORMAL_TAGGED 111 11110000
> > */
> > #define MAIR(attr, mt) ((attr) << ((mt) * 8))
> >
> > @@ -275,7 +276,8 @@ asm_mmu_enable:
> > MAIR(0x44, MT_NORMAL_NC) | \
> > MAIR(0xff, MT_NORMAL) | \
> > MAIR(0xbb, MT_NORMAL_WT) | \
> > - MAIR(0x08, MT_DEVICE_nGRE)
> > + MAIR(0x08, MT_DEVICE_nGRE) | \
> > + MAIR(0xf0, MT_NORMAL_TAGGED)
> > msr mair_el1, x1
> >
> > /* TTBR0 */
> > diff --git a/arm/mte.c b/arm/mte.c
> > new file mode 100644
> > index 00000000..f32203ce
> > --- /dev/null
> > +++ b/arm/mte.c
> > @@ -0,0 +1,313 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2024 Arm Limited.
> > + * All rights reserved.
> > + */
> > +
> > +#include <libcflat.h>
> > +#include <alloc_page.h>
> > +#include <stdlib.h>
> > +
> > +#include <asm/mmu.h>
> > +#include <asm/pgtable-hwdef.h>
> > +#include <asm/processor.h>
> > +#include <asm/sysreg.h>
> > +#include <asm/thread_info.h>
> > +
> > +
> > +/* Tag Check Faults cause a synchronous exception */
> > +#define MTE_TCF_SYNC 0b01
> > +/* Tag Check Faults are asynchronously accumulated */
> > +#define MTE_TCF_ASYNC 0b10
> > +/*
> > + * Tag Check Faults cause a synchronous exception on reads,
> > + * and are asynchronously accumulated on writes
> > + */
> > +#define MTE_TCF_ASYMM 0b11
> > +
> > +#define MTE_GRANULE_SIZE UL(16)
> > +#define MTE_GRANULE_MASK (~(MTE_GRANULE_SIZE - 1))
> > +#define MTE_TAG_SHIFT 56
> > +
> > +#define untagged(p) \
> > +({ \
> > + unsigned long __in = (unsigned long)(p); \
> > + typeof(p) __out = (typeof(p))(__in & ~(MTE_GRANULE_MASK << MTE_TAG_SHIFT)); \
> > + \
> > + __out; \
> > +})
> > +
> > +#define tagged(p, t) \
> > +({ \
> > + unsigned long __in = (unsigned long)(untagged(p)); \
> > + unsigned long __tag = (unsigned long)(t) << MTE_TAG_SHIFT; \
> > + typeof(p) __out = (typeof(p))(__in | __tag); \
> > + \
> > + __out; \
> > +})
> > +
> > +/*
> > + * If we use a normal (non hand coded inline assembly) load or store
> > + * to access a tagged address, the compiler will reasonably assume
> > + * that the access succeeded, and the next instruction may do
> > + * something based on that assumption.
> > + *
> > + * But a test might want the tagged access to fail on purpose, and if
> > + * we advance the PC to the next instruction, the one added by the
> > + * compiler, we might leave the program in an unexpected state.
> > + */
> > +static inline void mem_read(unsigned int *addr, unsigned int *res)
> > +{
> > + unsigned int r;
> > +
> > + asm volatile ("ldr %0,[%1]\n"
> > + "str %0,[%2]\n"
> > + : "=&r" (r)
> > + : "r" (addr), "r" (res) : "memory");
> > +}
> > +
> > +static inline void mem_write(unsigned int *addr, unsigned int val)
> > +{
> > + /* The NOP allows the same exception handler as mem_read() to be used. */
> > + asm volatile ("str %0,[%1]\n"
> > + "nop\n"
> > + :
> > + : "r" (val), "r" (addr)
> > + : "memory");
> > +}
> > +
> > +static volatile bool mte_exception;
> > +
> > +static void mte_fault_handler(struct pt_regs *regs, unsigned int esr)
> > +{
> > + unsigned int dfsc = esr & GENMASK(5, 0);
> > + unsigned int fnv = esr & BIT(10);
> > +
> > + if (dfsc == 0b010001) {
> > + if (fnv)
> > + report_info("Unexpected non-zero FnV");
> > + mte_exception = true;
> > + }
> > +
> > + /*
> > + * mem_read() reads the value from the tagged pointer, then
> > + * stores this value in the untagged 'res' pointer. The
> > + * function that called mem_read() will want to check that the
> > + * initial value of 'res' hasn't changed if a tag check fault
> > + * is reported. Skip over two instructions so 'res' isn't
> > + * overwritten.
> > + */
> > + regs->pc += 8;
> > +}
> > +
> > +static inline void mmu_set_tagged(pgd_t *pgtable, unsigned long vaddr)
> > +{
> > + pteval_t *p_pte = follow_pte(pgtable, untagged(vaddr));
> > +
> > + if (p_pte) {
> > + pteval_t entry = *p_pte;
> > +
> > + entry &= ~PTE_ATTRINDX_MASK;
> > + entry |= PTE_ATTRINDX(MT_NORMAL_TAGGED);
> > +
> > + WRITE_ONCE(*p_pte, entry);
> > + flush_tlb_page(vaddr);
> > + } else {
> > + report_abort("Cannot find PTE");
> > + }
> > +}
> > +
> > +static void mte_init(void)
> > +{
> > + unsigned long sctlr = read_sysreg(sctlr_el1);
> > + unsigned long tcr = read_sysreg(tcr_el1);
> > +
> > + sctlr &= ~SCTLR_EL1_TCF_MASK;
> > + sctlr |= SCTLR_EL1_ATA;
> > +
> > + tcr &= ~TCR_TCMA0;
> > + tcr |= TCR_TBI0;
> > +
> > + write_sysreg(sctlr, sctlr_el1);
> > + write_sysreg(tcr, tcr_el1);
> > +
> > + isb();
> > + flush_tlb_all();
> > +}
> > +
> > +static inline unsigned long mte_set_tcf(unsigned long tcf)
> > +{
> > + unsigned long sctlr = read_sysreg(sctlr_el1);
> > + unsigned long old = (sctlr & SCTLR_EL1_TCF_MASK) >> SCTLR_EL1_TCF_SHIFT;
> > +
> > + sctlr &= ~(SCTLR_EL1_TCF_MASK | SCTLR_EL1_TCF0_MASK);
> > + sctlr |= (tcf << SCTLR_EL1_TCF_SHIFT) & SCTLR_EL1_TCF_MASK;
> > +
> > + write_sysreg(sctlr, sctlr_el1);
> > + write_sysreg_s(0, TFSR_EL1);
> > + isb();
> > +
> > + return old;
> > +}
> > +
> > +
> > +static inline void mte_set_tag(void *addr, size_t size, unsigned int tag)
> > +{
> > +#ifdef CC_HAS_MTE
> > + unsigned long in = (unsigned long)untagged(addr);
> > + unsigned long start = ALIGN_DOWN(in, 16);
> > + unsigned long end = ALIGN(in + size, 16);
> > +
> > + for (unsigned long ptr = start; ptr < end; ptr += 16) {
> > + asm volatile(".arch armv8.5-a+memtag\n"
> > + "stg %0, [%0]"
> > + :
> > + : "r"(tagged(ptr, tag))
> > + : "memory");
> > + }
> > +#endif
> > +}
> > +
> > +static inline unsigned long get_clear_tfsr(void)
> > +{
> > + unsigned long r;
> > +
> > + dsb(nsh);
> > + isb();
> > +
> > + r = read_sysreg_s(TFSR_EL1);
> > + write_sysreg_s(0, TFSR_EL1);
> > +
> > + return r;
> > +}
> > +
> > +static void mte_sync_test(void)
> > +{
> > + unsigned int *mem = tagged(alloc_page(), 1);
> > + unsigned int val = 0;
> > +
> > + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
> > + mte_set_tag(mem, PAGE_SIZE, 1);
> > + memset(mem, 0xff, PAGE_SIZE);
> > + mte_set_tcf(MTE_TCF_SYNC);
> > +
> > + mte_exception = false;
> > +
> > + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
> > +
> > + mem_read(tagged(mem, 2), &val);
> > +
> > + report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
> > +
> > + mte_exception = false;
> > +
> > + mem_write(tagged(mem, 3), 0xbbbbbbbb);
> > +
> > + report((*mem == 0xffffffff) && mte_exception && (get_clear_tfsr() == 0), "write");
> > +
> > + free_page(untagged(mem));
> > +}
> > +
> > +static void mte_asymm_test(void)
> > +{
> > + unsigned int *mem = tagged(alloc_page(), 2);
> > + unsigned int val = 0;
> > +
> > + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
> > + mte_set_tag(mem, PAGE_SIZE, 2);
> > + memset(mem, 0xff, PAGE_SIZE);
> > + mte_set_tcf(MTE_TCF_ASYMM);
> > + mte_exception = false;
> > +
> > + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler);
> > +
> > + mem_read(tagged(mem, 3), &val);
> > + report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read");
> > +
> > + install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, NULL);
> > +
> > + mem_write(tagged(mem, 4), 0xaaaaaaaa);
> > + report((*mem == 0xaaaaaaaa) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
> > +
> > + free_page(untagged(mem));
> > +}
> > +
> > +static void mte_async_test(void)
> > +{
> > + unsigned int *mem = tagged(alloc_page(), 3);
> > + unsigned int val = 0;
> > +
> > + mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem);
> > + mte_set_tag(mem, PAGE_SIZE, 3);
> > + memset(mem, 0xff, PAGE_SIZE);
> > + mte_set_tcf(MTE_TCF_ASYNC);
> > +
> > + mem_read(tagged(mem, 4), &val);
> > + report((val == 0xffffffff) && (get_clear_tfsr() == TFSR_EL1_TF0), "read");
> > +
> > + mem_write(tagged(mem, 5), 0xcccccccc);
> > + report((*mem == 0xcccccccc) && (get_clear_tfsr() == TFSR_EL1_TF0), "write");
> > +
> > + free_page(untagged(mem));
> > +}
> > +
> > +
> > +static unsigned int mte_version(void)
> > +{
> > +#ifdef CC_HAS_MTE
> > + uint64_t r;
> > +
> > + asm volatile("mrs %x0, id_aa64pfr1_el1" : "=r"(r));
> > +
> > + return (r >> ID_AA64PFR1_EL1_MTE_SHIFT) & 0b1111;
> > +#else
> > + report_info("Compiler lack MTE support");
> > + return 0;
> > +#endif
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +
> > + unsigned int version = mte_version();
> > +
> > + if (version < 2) {
> > + report_skip("No MTE support, skip...\n");
> > + return report_summary();
> > + }
> > +
> > + if (argc < 2)
> > + report_abort("no test specified");
> > +
> > + report_prefix_push("mte");
> > +
> > + mte_init();
> > +
> > + if (strcmp(argv[1], "sync") == 0) {
> > + report_prefix_push(argv[1]);
> > + mte_sync_test();
> > + report_prefix_pop();
> > + } else if (strcmp(argv[1], "async") == 0) {
> > + report_prefix_push(argv[1]);
> > + if (version < 3) {
> > + report_skip("No MTE async, skip...\n");
> > + return report_summary();
> > + }
> > + mte_async_test();
> > + report_prefix_pop();
> > +
> > + } else if (strcmp(argv[1], "asymm") == 0) {
> > + report_prefix_push(argv[1]);
> > + if (version < 3) {
> > + report_skip("No MTE asymm, skip...\n");
> > + return report_summary();
> > + }
> > + mte_asymm_test();
> > + report_prefix_pop();
> > +
> > + } else {
> > + report_abort("Unknown sub-test '%s'", argv[1]);
> > + }
> > +
> > + return report_summary();
> > +}
> > diff --git a/arm/unittests.cfg b/arm/unittests.cfg
> > index 2bdad67d..fe101145 100644
> > --- a/arm/unittests.cfg
> > +++ b/arm/unittests.cfg
> > @@ -271,3 +271,22 @@ smp = 2
> > groups = nodefault
> > accel = kvm
> > arch = arm64
> > +
> > +# MTE tests
> > +[mte-sync]
> > +file = mte.flat
> > +groups = mte
> > +extra_params = -machine mte=on -append 'sync'
> > +arch = arm64
> > +
> > +[mte-async]
> > +file = mte.flat
> > +groups = mte
> > +extra_params = -machine mte=on -append 'async'
> > +arch = arm64
> > +
> > +[mte-asymm]
> > +file = mte.flat
> > +groups = mte
> > +extra_params = -machine mte=on -append 'asymm'
> > +arch = arm64
> > diff --git a/lib/arm64/asm/mmu.h b/lib/arm64/asm/mmu.h
> > index 5c27edb2..9aedd09a 100644
> > --- a/lib/arm64/asm/mmu.h
> > +++ b/lib/arm64/asm/mmu.h
> > @@ -10,6 +10,7 @@
> > #define PMD_SECT_UNCACHED PMD_ATTRINDX(MT_DEVICE_nGnRE)
> > #define PTE_UNCACHED PTE_ATTRINDX(MT_DEVICE_nGnRE)
> > #define PTE_WBWA PTE_ATTRINDX(MT_NORMAL)
> > +#define PTE_TAGGED PTE_ATTRINDX(MT_NORMAL_TAGGED)
> >
> > static inline void flush_tlb_all(void)
> > {
> > diff --git a/lib/arm64/asm/pgtable-hwdef.h b/lib/arm64/asm/pgtable-hwdef.h
> > index 8c41fe12..08a2e91c 100644
> > --- a/lib/arm64/asm/pgtable-hwdef.h
> > +++ b/lib/arm64/asm/pgtable-hwdef.h
> > @@ -145,6 +145,8 @@
> > #define TCR_TG1_64K (UL(3) << 30)
> > #define TCR_ASID16 (UL(1) << 36)
> > #define TCR_TBI0 (UL(1) << 37)
> > +#define TCR_TBI1 (UL(1) << 38)
> > +#define TCR_TCMA0 (UL(1) << 57)
> >
> > /*
> > * Memory types available.
> > @@ -156,5 +158,6 @@
> > #define MT_NORMAL 4
> > #define MT_NORMAL_WT 5
> > #define MT_DEVICE_nGRE 6
> > +#define MT_NORMAL_TAGGED 7
> >
> > #endif /* _ASMARM64_PGTABLE_HWDEF_H_ */
> > diff --git a/lib/arm64/asm/sysreg.h b/lib/arm64/asm/sysreg.h
> > index f214a4f0..b8d3d66e 100644
> > --- a/lib/arm64/asm/sysreg.h
> > +++ b/lib/arm64/asm/sysreg.h
> > @@ -28,6 +28,7 @@
> > .endm
> > #else
> > #include <libcflat.h>
> > +#include <bitops.h>
> >
> > #define read_sysreg(r) ({ \
> > u64 __val; \
> > @@ -74,6 +75,7 @@ asm(
> > #endif /* __ASSEMBLY__ */
> >
> > #define ID_AA64ISAR0_EL1_RNDR_SHIFT 60
> > +#define ID_AA64PFR1_EL1_MTE_SHIFT 8
> >
> > #define ICC_PMR_EL1 sys_reg(3, 0, 4, 6, 0)
> > #define ICC_SGI1R_EL1 sys_reg(3, 0, 12, 11, 5)
> > @@ -81,7 +83,13 @@ asm(
> > #define ICC_EOIR1_EL1 sys_reg(3, 0, 12, 12, 1)
> > #define ICC_GRPEN1_EL1 sys_reg(3, 0, 12, 12, 7)
> >
> > +#define TFSR_EL1 sys_reg(3, 0, 5, 6, 0)
> > +#define TFSR_EL1_TF0 _BITULL(0)
> > +#define TFSR_EL1_TF1 _BITULL(1)
> > +
> > /* System Control Register (SCTLR_EL1) bits */
> > +#define SCTLR_EL1_ATA _BITULL(43)
> > +#define SCTLR_EL1_ATA0 _BITULL(42)
> > #define SCTLR_EL1_LSMAOE _BITULL(29)
> > #define SCTLR_EL1_NTLSMD _BITULL(28)
> > #define SCTLR_EL1_EE _BITULL(25)
> > @@ -99,6 +107,12 @@ asm(
> > #define SCTLR_EL1_A _BITULL(1)
> > #define SCTLR_EL1_M _BITULL(0)
> >
> > +#define SCTLR_EL1_TCF_SHIFT 40
> > +#define SCTLR_EL1_TCF_MASK GENMASK_ULL(41, 40)
> > +
> > +#define SCTLR_EL1_TCF0_SHIFT 38
> > +#define SCTLR_EL1_TCF0_MASK GENMASK_ULL(39, 38)
> > +
> > #define INIT_SCTLR_EL1_MMU_OFF \
> > (SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_EOS | \
> > SCTLR_EL1_TSCXT | SCTLR_EL1_EIS | SCTLR_EL1_SPAN | \
> > --
> > 2.25.1
> >
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-02-27 15:22 [PATCH v4] arm64: Add basic MTE test Vladimir Murzin
2025-03-06 14:11 ` Alexandru Elisei
@ 2025-03-06 15:45 ` Andrew Jones
2025-03-06 17:11 ` Alexandru Elisei
2025-03-07 9:26 ` Andrew Jones
2025-04-08 15:16 ` Andrew Jones
3 siblings, 1 reply; 9+ messages in thread
From: Andrew Jones @ 2025-03-06 15:45 UTC (permalink / raw)
To: Vladimir Murzin; +Cc: kvmarm, alexandru.elisei, nikos.nikoleris, eric.auger
On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
> Test tag storage access and tag mismatch for different MTE modes.
>
> Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> ---
> arm/Makefile.arm64 | 8 +
> arm/cstart64.S | 4 +-
> arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
> arm/unittests.cfg | 19 +++
> lib/arm64/asm/mmu.h | 1 +
> lib/arm64/asm/pgtable-hwdef.h | 3 +
> lib/arm64/asm/sysreg.h | 14 ++
> 7 files changed, 361 insertions(+), 1 deletion(-)
> create mode 100644 arm/mte.c
>
Unfortunately this is failing builds with clang.
arm/mte.c:65:18: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
65 | : "=&r" (r)
| ^
arm/mte.c:63:21: note: use constraint modifier "w"
63 | asm volatile ("ldr %0,[%1]\n"
| ^~
| %w0
arm/mte.c:65:18: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
65 | : "=&r" (r)
| ^
arm/mte.c:64:14: note: use constraint modifier "w"
64 | "str %0,[%2]\n"
| ^~
| %w0
arm/mte.c:75:16: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
75 | : "r" (val), "r" (addr)
| ^
arm/mte.c:72:21: note: use constraint modifier "w"
72 | asm volatile ("str %0,[%1]\n"
| ^~
| %w0
Thanks,
drew
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-03-06 15:45 ` Andrew Jones
@ 2025-03-06 17:11 ` Alexandru Elisei
2025-03-07 8:24 ` Andrew Jones
0 siblings, 1 reply; 9+ messages in thread
From: Alexandru Elisei @ 2025-03-06 17:11 UTC (permalink / raw)
To: Andrew Jones; +Cc: Vladimir Murzin, kvmarm, nikos.nikoleris, eric.auger
Hi Drew,
On Thu, Mar 06, 2025 at 04:45:06PM +0100, Andrew Jones wrote:
> On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
> > Test tag storage access and tag mismatch for different MTE modes.
> >
> > Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> > ---
> > arm/Makefile.arm64 | 8 +
> > arm/cstart64.S | 4 +-
> > arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
> > arm/unittests.cfg | 19 +++
> > lib/arm64/asm/mmu.h | 1 +
> > lib/arm64/asm/pgtable-hwdef.h | 3 +
> > lib/arm64/asm/sysreg.h | 14 ++
> > 7 files changed, 361 insertions(+), 1 deletion(-)
> > create mode 100644 arm/mte.c
> >
>
> Unfortunately this is failing builds with clang.
>
> arm/mte.c:65:18: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
> 65 | : "=&r" (r)
> | ^
> arm/mte.c:63:21: note: use constraint modifier "w"
> 63 | asm volatile ("ldr %0,[%1]\n"
> | ^~
> | %w0
> arm/mte.c:65:18: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
> 65 | : "=&r" (r)
> | ^
> arm/mte.c:64:14: note: use constraint modifier "w"
> 64 | "str %0,[%2]\n"
> | ^~
> | %w0
> arm/mte.c:75:16: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
> 75 | : "r" (val), "r" (addr)
> | ^
> arm/mte.c:72:21: note: use constraint modifier "w"
> 72 | asm volatile ("str %0,[%1]\n"
> | ^~
> | %w0
>
> Thanks,
> drew
>
That's unfortunate, I think it's because %0 specifies a 64bit register, but
the variable is declared as an unsigned int, which is 32 bits.
Can you give this a go:
diff --git a/arm/mte.c b/arm/mte.c
index f32203ce275a..9c266fcced5f 100644
--- a/arm/mte.c
+++ b/arm/mte.c
@@ -60,8 +60,8 @@ static inline void mem_read(unsigned int *addr, unsigned int *res)
{
unsigned int r;
- asm volatile ("ldr %0,[%1]\n"
- "str %0,[%2]\n"
+ asm volatile ("ldr %w0,[%1]\n"
+ "str %w0,[%2]\n"
: "=&r" (r)
: "r" (addr), "r" (res) : "memory");
}
@@ -69,7 +69,7 @@ static inline void mem_read(unsigned int *addr, unsigned int *res)
static inline void mem_write(unsigned int *addr, unsigned int val)
{
/* The NOP allows the same exception handler as mem_read() to be used. */
- asm volatile ("str %0,[%1]\n"
+ asm volatile ("str %w0,[%1]\n"
"nop\n"
:
: "r" (val), "r" (addr)
It compiles and works with gcc; with clang it compiles, but I cannot run
the test because kvm-unit-tests doesn't detect MTE support in the compiler.
$ ./configure --arch=arm64 --cc=clang --cross-prefix=aarch64-linux-gnu- --cflags='--target=aarch64-linux -march=armv8.5-a+memtag'
$ make -j32 clean && make -j32
$ ./run_tests.sh -g mte
config.mak: line 13: -march=armv8.5-a+memtag: command not found
config.mak: line 13: -march=armv8.5-a+memtag: command not found
SKIP mte-sync (1 tests, 1 skipped)
SKIP mte-async (1 tests, 1 skipped)
SKIP mte-asymm (1 tests, 1 skipped)
(the error I suppose it's because configure doesn't wrap CFLAGS in quotes
when writing config.mak).
Do you have any hints about enabling MTE in clang?
Thanks,
Alex
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-03-06 17:11 ` Alexandru Elisei
@ 2025-03-07 8:24 ` Andrew Jones
0 siblings, 0 replies; 9+ messages in thread
From: Andrew Jones @ 2025-03-07 8:24 UTC (permalink / raw)
To: Alexandru Elisei; +Cc: Vladimir Murzin, kvmarm, nikos.nikoleris, eric.auger
On Thu, Mar 06, 2025 at 05:11:20PM +0000, Alexandru Elisei wrote:
> Hi Drew,
>
> On Thu, Mar 06, 2025 at 04:45:06PM +0100, Andrew Jones wrote:
> > On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
> > > Test tag storage access and tag mismatch for different MTE modes.
> > >
> > > Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> > > ---
> > > arm/Makefile.arm64 | 8 +
> > > arm/cstart64.S | 4 +-
> > > arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
> > > arm/unittests.cfg | 19 +++
> > > lib/arm64/asm/mmu.h | 1 +
> > > lib/arm64/asm/pgtable-hwdef.h | 3 +
> > > lib/arm64/asm/sysreg.h | 14 ++
> > > 7 files changed, 361 insertions(+), 1 deletion(-)
> > > create mode 100644 arm/mte.c
> > >
> >
> > Unfortunately this is failing builds with clang.
> >
> > arm/mte.c:65:18: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
> > 65 | : "=&r" (r)
> > | ^
> > arm/mte.c:63:21: note: use constraint modifier "w"
> > 63 | asm volatile ("ldr %0,[%1]\n"
> > | ^~
> > | %w0
> > arm/mte.c:65:18: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
> > 65 | : "=&r" (r)
> > | ^
> > arm/mte.c:64:14: note: use constraint modifier "w"
> > 64 | "str %0,[%2]\n"
> > | ^~
> > | %w0
> > arm/mte.c:75:16: error: value size does not match register size specified by the constraint and modifier [-Werror,-Wasm-operand-widths]
> > 75 | : "r" (val), "r" (addr)
> > | ^
> > arm/mte.c:72:21: note: use constraint modifier "w"
> > 72 | asm volatile ("str %0,[%1]\n"
> > | ^~
> > | %w0
> >
> > Thanks,
> > drew
> >
>
> That's unfortunate, I think it's because %0 specifies a 64bit register, but
> the variable is declared as an unsigned int, which is 32 bits.
>
> Can you give this a go:
>
> diff --git a/arm/mte.c b/arm/mte.c
> index f32203ce275a..9c266fcced5f 100644
> --- a/arm/mte.c
> +++ b/arm/mte.c
> @@ -60,8 +60,8 @@ static inline void mem_read(unsigned int *addr, unsigned int *res)
> {
> unsigned int r;
>
> - asm volatile ("ldr %0,[%1]\n"
> - "str %0,[%2]\n"
> + asm volatile ("ldr %w0,[%1]\n"
> + "str %w0,[%2]\n"
> : "=&r" (r)
> : "r" (addr), "r" (res) : "memory");
> }
> @@ -69,7 +69,7 @@ static inline void mem_read(unsigned int *addr, unsigned int *res)
> static inline void mem_write(unsigned int *addr, unsigned int val)
> {
> /* The NOP allows the same exception handler as mem_read() to be used. */
> - asm volatile ("str %0,[%1]\n"
> + asm volatile ("str %w0,[%1]\n"
> "nop\n"
> :
> : "r" (val), "r" (addr)
>
> It compiles and works with gcc; with clang it compiles,
Yup, works.
> but I cannot run
> the test because kvm-unit-tests doesn't detect MTE support in the compiler.
>
> $ ./configure --arch=arm64 --cc=clang --cross-prefix=aarch64-linux-gnu- --cflags='--target=aarch64-linux -march=armv8.5-a+memtag'
> $ make -j32 clean && make -j32
> $ ./run_tests.sh -g mte
> config.mak: line 13: -march=armv8.5-a+memtag: command not found
> config.mak: line 13: -march=armv8.5-a+memtag: command not found
> SKIP mte-sync (1 tests, 1 skipped)
> SKIP mte-async (1 tests, 1 skipped)
> SKIP mte-asymm (1 tests, 1 skipped)
>
> (the error I suppose it's because configure doesn't wrap CFLAGS in quotes
> when writing config.mak).
We shouldn't need to add -march=armv8.5-a+memtag to --cflags, since this
patch adds the cc-option check in arm/Makefile.arm64. Also, I see that
--target=aarch64-linux, as opposed to just --target=aarch64 that we have
in .gitlab-ci.yml, fails to build arm/spinlock-test.c (at least for me),
so I don't think we want to change that either.
>
> Do you have any hints about enabling MTE in clang?
We need to change cc-option to also specify CFLAGS, like below, then
everything works for me.
diff --git a/Makefile b/Makefile
index 78352fced9d4..9dc5d2234e2a 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ DESTDIR := $(PREFIX)/share/kvm-unit-tests/
# cc-option
# Usage: OP_CFLAGS+=$(call cc-option, -falign-functions=0, -malign-functions=0)
-cc-option = $(shell if $(CC) -Werror $(1) -S -o /dev/null -xc /dev/null \
+cc-option = $(shell if $(CC) $(CFLAGS) -Werror $(1) -S -o /dev/null -xc /dev/null \
> /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)
libcflat := lib/libcflat.a
Thanks,
drew
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-02-27 15:22 [PATCH v4] arm64: Add basic MTE test Vladimir Murzin
2025-03-06 14:11 ` Alexandru Elisei
2025-03-06 15:45 ` Andrew Jones
@ 2025-03-07 9:26 ` Andrew Jones
2025-04-08 15:16 ` Andrew Jones
3 siblings, 0 replies; 9+ messages in thread
From: Andrew Jones @ 2025-03-07 9:26 UTC (permalink / raw)
To: Vladimir Murzin; +Cc: kvmarm, alexandru.elisei, nikos.nikoleris, eric.auger
On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
> Test tag storage access and tag mismatch for different MTE modes.
>
> Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> ---
> arm/Makefile.arm64 | 8 +
> arm/cstart64.S | 4 +-
> arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
> arm/unittests.cfg | 19 +++
> lib/arm64/asm/mmu.h | 1 +
> lib/arm64/asm/pgtable-hwdef.h | 3 +
> lib/arm64/asm/sysreg.h | 14 ++
> 7 files changed, 361 insertions(+), 1 deletion(-)
> create mode 100644 arm/mte.c
>
I fixed up the inline assembly constraints and removed some superfluous
blank lines and applied to arm/queue
https://gitlab.com/jones-drew/kvm-unit-tests/-/commits/arm/queue
Thanks,
drew
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4] arm64: Add basic MTE test
2025-02-27 15:22 [PATCH v4] arm64: Add basic MTE test Vladimir Murzin
` (2 preceding siblings ...)
2025-03-07 9:26 ` Andrew Jones
@ 2025-04-08 15:16 ` Andrew Jones
3 siblings, 0 replies; 9+ messages in thread
From: Andrew Jones @ 2025-04-08 15:16 UTC (permalink / raw)
To: Vladimir Murzin; +Cc: kvmarm, alexandru.elisei, nikos.nikoleris, eric.auger
On Thu, Feb 27, 2025 at 03:22:40PM +0000, Vladimir Murzin wrote:
> Test tag storage access and tag mismatch for different MTE modes.
>
> Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
> ---
> arm/Makefile.arm64 | 8 +
> arm/cstart64.S | 4 +-
> arm/mte.c | 313 ++++++++++++++++++++++++++++++++++
> arm/unittests.cfg | 19 +++
> lib/arm64/asm/mmu.h | 1 +
> lib/arm64/asm/pgtable-hwdef.h | 3 +
> lib/arm64/asm/sysreg.h | 14 ++
> 7 files changed, 361 insertions(+), 1 deletion(-)
> create mode 100644 arm/mte.c
>
Merged through arm/queue.
Thanks,
drew
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-04-08 15:16 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-02-27 15:22 [PATCH v4] arm64: Add basic MTE test Vladimir Murzin
2025-03-06 14:11 ` Alexandru Elisei
2025-03-06 14:25 ` Vladimir Murzin
2025-03-06 15:31 ` Andrew Jones
2025-03-06 15:45 ` Andrew Jones
2025-03-06 17:11 ` Alexandru Elisei
2025-03-07 8:24 ` Andrew Jones
2025-03-07 9:26 ` Andrew Jones
2025-04-08 15:16 ` Andrew Jones
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.