blob: a1bed8a70c2c732540426b598e1b7ce82790ca50 [file] [edit]
/* 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 %w0,[%1]\n"
"str %w0,[%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 %w0,[%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();
}