1*1b59c632SVladimir Murzin /* SPDX-License-Identifier: GPL-2.0 */ 2*1b59c632SVladimir Murzin /* 3*1b59c632SVladimir Murzin * Copyright (C) 2024 Arm Limited. 4*1b59c632SVladimir Murzin * All rights reserved. 5*1b59c632SVladimir Murzin */ 6*1b59c632SVladimir Murzin 7*1b59c632SVladimir Murzin #include <libcflat.h> 8*1b59c632SVladimir Murzin #include <alloc_page.h> 9*1b59c632SVladimir Murzin #include <stdlib.h> 10*1b59c632SVladimir Murzin 11*1b59c632SVladimir Murzin #include <asm/mmu.h> 12*1b59c632SVladimir Murzin #include <asm/pgtable-hwdef.h> 13*1b59c632SVladimir Murzin #include <asm/processor.h> 14*1b59c632SVladimir Murzin #include <asm/sysreg.h> 15*1b59c632SVladimir Murzin #include <asm/thread_info.h> 16*1b59c632SVladimir Murzin 17*1b59c632SVladimir Murzin 18*1b59c632SVladimir Murzin /* Tag Check Faults cause a synchronous exception */ 19*1b59c632SVladimir Murzin #define MTE_TCF_SYNC 0b01 20*1b59c632SVladimir Murzin /* Tag Check Faults are asynchronously accumulated */ 21*1b59c632SVladimir Murzin #define MTE_TCF_ASYNC 0b10 22*1b59c632SVladimir Murzin /* 23*1b59c632SVladimir Murzin * Tag Check Faults cause a synchronous exception on reads, 24*1b59c632SVladimir Murzin * and are asynchronously accumulated on writes 25*1b59c632SVladimir Murzin */ 26*1b59c632SVladimir Murzin #define MTE_TCF_ASYMM 0b11 27*1b59c632SVladimir Murzin 28*1b59c632SVladimir Murzin #define MTE_GRANULE_SIZE UL(16) 29*1b59c632SVladimir Murzin #define MTE_GRANULE_MASK (~(MTE_GRANULE_SIZE - 1)) 30*1b59c632SVladimir Murzin #define MTE_TAG_SHIFT 56 31*1b59c632SVladimir Murzin 32*1b59c632SVladimir Murzin #define untagged(p) \ 33*1b59c632SVladimir Murzin ({ \ 34*1b59c632SVladimir Murzin unsigned long __in = (unsigned long)(p); \ 35*1b59c632SVladimir Murzin typeof(p) __out = (typeof(p))(__in & ~(MTE_GRANULE_MASK << MTE_TAG_SHIFT)); \ 36*1b59c632SVladimir Murzin \ 37*1b59c632SVladimir Murzin __out; \ 38*1b59c632SVladimir Murzin }) 39*1b59c632SVladimir Murzin 40*1b59c632SVladimir Murzin #define tagged(p, t) \ 41*1b59c632SVladimir Murzin ({ \ 42*1b59c632SVladimir Murzin unsigned long __in = (unsigned long)(untagged(p)); \ 43*1b59c632SVladimir Murzin unsigned long __tag = (unsigned long)(t) << MTE_TAG_SHIFT; \ 44*1b59c632SVladimir Murzin typeof(p) __out = (typeof(p))(__in | __tag); \ 45*1b59c632SVladimir Murzin \ 46*1b59c632SVladimir Murzin __out; \ 47*1b59c632SVladimir Murzin }) 48*1b59c632SVladimir Murzin 49*1b59c632SVladimir Murzin /* 50*1b59c632SVladimir Murzin * If we use a normal (non hand coded inline assembly) load or store 51*1b59c632SVladimir Murzin * to access a tagged address, the compiler will reasonably assume 52*1b59c632SVladimir Murzin * that the access succeeded, and the next instruction may do 53*1b59c632SVladimir Murzin * something based on that assumption. 54*1b59c632SVladimir Murzin * 55*1b59c632SVladimir Murzin * But a test might want the tagged access to fail on purpose, and if 56*1b59c632SVladimir Murzin * we advance the PC to the next instruction, the one added by the 57*1b59c632SVladimir Murzin * compiler, we might leave the program in an unexpected state. 58*1b59c632SVladimir Murzin */ 59*1b59c632SVladimir Murzin static inline void mem_read(unsigned int *addr, unsigned int *res) 60*1b59c632SVladimir Murzin { 61*1b59c632SVladimir Murzin unsigned int r; 62*1b59c632SVladimir Murzin 63*1b59c632SVladimir Murzin asm volatile ("ldr %w0,[%1]\n" 64*1b59c632SVladimir Murzin "str %w0,[%2]\n" 65*1b59c632SVladimir Murzin : "=&r" (r) 66*1b59c632SVladimir Murzin : "r" (addr), "r" (res) : "memory"); 67*1b59c632SVladimir Murzin } 68*1b59c632SVladimir Murzin 69*1b59c632SVladimir Murzin static inline void mem_write(unsigned int *addr, unsigned int val) 70*1b59c632SVladimir Murzin { 71*1b59c632SVladimir Murzin /* The NOP allows the same exception handler as mem_read() to be used. */ 72*1b59c632SVladimir Murzin asm volatile ("str %w0,[%1]\n" 73*1b59c632SVladimir Murzin "nop\n" 74*1b59c632SVladimir Murzin : 75*1b59c632SVladimir Murzin : "r" (val), "r" (addr) 76*1b59c632SVladimir Murzin : "memory"); 77*1b59c632SVladimir Murzin } 78*1b59c632SVladimir Murzin 79*1b59c632SVladimir Murzin static volatile bool mte_exception; 80*1b59c632SVladimir Murzin 81*1b59c632SVladimir Murzin static void mte_fault_handler(struct pt_regs *regs, unsigned int esr) 82*1b59c632SVladimir Murzin { 83*1b59c632SVladimir Murzin unsigned int dfsc = esr & GENMASK(5, 0); 84*1b59c632SVladimir Murzin unsigned int fnv = esr & BIT(10); 85*1b59c632SVladimir Murzin 86*1b59c632SVladimir Murzin if (dfsc == 0b010001) { 87*1b59c632SVladimir Murzin if (fnv) 88*1b59c632SVladimir Murzin report_info("Unexpected non-zero FnV"); 89*1b59c632SVladimir Murzin mte_exception = true; 90*1b59c632SVladimir Murzin } 91*1b59c632SVladimir Murzin 92*1b59c632SVladimir Murzin /* 93*1b59c632SVladimir Murzin * mem_read() reads the value from the tagged pointer, then 94*1b59c632SVladimir Murzin * stores this value in the untagged 'res' pointer. The 95*1b59c632SVladimir Murzin * function that called mem_read() will want to check that the 96*1b59c632SVladimir Murzin * initial value of 'res' hasn't changed if a tag check fault 97*1b59c632SVladimir Murzin * is reported. Skip over two instructions so 'res' isn't 98*1b59c632SVladimir Murzin * overwritten. 99*1b59c632SVladimir Murzin */ 100*1b59c632SVladimir Murzin regs->pc += 8; 101*1b59c632SVladimir Murzin } 102*1b59c632SVladimir Murzin 103*1b59c632SVladimir Murzin static inline void mmu_set_tagged(pgd_t *pgtable, unsigned long vaddr) 104*1b59c632SVladimir Murzin { 105*1b59c632SVladimir Murzin pteval_t *p_pte = follow_pte(pgtable, untagged(vaddr)); 106*1b59c632SVladimir Murzin 107*1b59c632SVladimir Murzin if (p_pte) { 108*1b59c632SVladimir Murzin pteval_t entry = *p_pte; 109*1b59c632SVladimir Murzin 110*1b59c632SVladimir Murzin entry &= ~PTE_ATTRINDX_MASK; 111*1b59c632SVladimir Murzin entry |= PTE_ATTRINDX(MT_NORMAL_TAGGED); 112*1b59c632SVladimir Murzin 113*1b59c632SVladimir Murzin WRITE_ONCE(*p_pte, entry); 114*1b59c632SVladimir Murzin flush_tlb_page(vaddr); 115*1b59c632SVladimir Murzin } else { 116*1b59c632SVladimir Murzin report_abort("Cannot find PTE"); 117*1b59c632SVladimir Murzin } 118*1b59c632SVladimir Murzin } 119*1b59c632SVladimir Murzin 120*1b59c632SVladimir Murzin static void mte_init(void) 121*1b59c632SVladimir Murzin { 122*1b59c632SVladimir Murzin unsigned long sctlr = read_sysreg(sctlr_el1); 123*1b59c632SVladimir Murzin unsigned long tcr = read_sysreg(tcr_el1); 124*1b59c632SVladimir Murzin 125*1b59c632SVladimir Murzin sctlr &= ~SCTLR_EL1_TCF_MASK; 126*1b59c632SVladimir Murzin sctlr |= SCTLR_EL1_ATA; 127*1b59c632SVladimir Murzin 128*1b59c632SVladimir Murzin tcr &= ~TCR_TCMA0; 129*1b59c632SVladimir Murzin tcr |= TCR_TBI0; 130*1b59c632SVladimir Murzin 131*1b59c632SVladimir Murzin write_sysreg(sctlr, sctlr_el1); 132*1b59c632SVladimir Murzin write_sysreg(tcr, tcr_el1); 133*1b59c632SVladimir Murzin 134*1b59c632SVladimir Murzin isb(); 135*1b59c632SVladimir Murzin flush_tlb_all(); 136*1b59c632SVladimir Murzin } 137*1b59c632SVladimir Murzin 138*1b59c632SVladimir Murzin static inline unsigned long mte_set_tcf(unsigned long tcf) 139*1b59c632SVladimir Murzin { 140*1b59c632SVladimir Murzin unsigned long sctlr = read_sysreg(sctlr_el1); 141*1b59c632SVladimir Murzin unsigned long old = (sctlr & SCTLR_EL1_TCF_MASK) >> SCTLR_EL1_TCF_SHIFT; 142*1b59c632SVladimir Murzin 143*1b59c632SVladimir Murzin sctlr &= ~(SCTLR_EL1_TCF_MASK | SCTLR_EL1_TCF0_MASK); 144*1b59c632SVladimir Murzin sctlr |= (tcf << SCTLR_EL1_TCF_SHIFT) & SCTLR_EL1_TCF_MASK; 145*1b59c632SVladimir Murzin 146*1b59c632SVladimir Murzin write_sysreg(sctlr, sctlr_el1); 147*1b59c632SVladimir Murzin write_sysreg_s(0, TFSR_EL1); 148*1b59c632SVladimir Murzin isb(); 149*1b59c632SVladimir Murzin 150*1b59c632SVladimir Murzin return old; 151*1b59c632SVladimir Murzin } 152*1b59c632SVladimir Murzin 153*1b59c632SVladimir Murzin static inline void mte_set_tag(void *addr, size_t size, unsigned int tag) 154*1b59c632SVladimir Murzin { 155*1b59c632SVladimir Murzin #ifdef CC_HAS_MTE 156*1b59c632SVladimir Murzin unsigned long in = (unsigned long)untagged(addr); 157*1b59c632SVladimir Murzin unsigned long start = ALIGN_DOWN(in, 16); 158*1b59c632SVladimir Murzin unsigned long end = ALIGN(in + size, 16); 159*1b59c632SVladimir Murzin 160*1b59c632SVladimir Murzin for (unsigned long ptr = start; ptr < end; ptr += 16) { 161*1b59c632SVladimir Murzin asm volatile(".arch armv8.5-a+memtag\n" 162*1b59c632SVladimir Murzin "stg %0, [%0]" 163*1b59c632SVladimir Murzin : 164*1b59c632SVladimir Murzin : "r"(tagged(ptr, tag)) 165*1b59c632SVladimir Murzin : "memory"); 166*1b59c632SVladimir Murzin } 167*1b59c632SVladimir Murzin #endif 168*1b59c632SVladimir Murzin } 169*1b59c632SVladimir Murzin 170*1b59c632SVladimir Murzin static inline unsigned long get_clear_tfsr(void) 171*1b59c632SVladimir Murzin { 172*1b59c632SVladimir Murzin unsigned long r; 173*1b59c632SVladimir Murzin 174*1b59c632SVladimir Murzin dsb(nsh); 175*1b59c632SVladimir Murzin isb(); 176*1b59c632SVladimir Murzin 177*1b59c632SVladimir Murzin r = read_sysreg_s(TFSR_EL1); 178*1b59c632SVladimir Murzin write_sysreg_s(0, TFSR_EL1); 179*1b59c632SVladimir Murzin 180*1b59c632SVladimir Murzin return r; 181*1b59c632SVladimir Murzin } 182*1b59c632SVladimir Murzin 183*1b59c632SVladimir Murzin static void mte_sync_test(void) 184*1b59c632SVladimir Murzin { 185*1b59c632SVladimir Murzin unsigned int *mem = tagged(alloc_page(), 1); 186*1b59c632SVladimir Murzin unsigned int val = 0; 187*1b59c632SVladimir Murzin 188*1b59c632SVladimir Murzin mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem); 189*1b59c632SVladimir Murzin mte_set_tag(mem, PAGE_SIZE, 1); 190*1b59c632SVladimir Murzin memset(mem, 0xff, PAGE_SIZE); 191*1b59c632SVladimir Murzin mte_set_tcf(MTE_TCF_SYNC); 192*1b59c632SVladimir Murzin 193*1b59c632SVladimir Murzin mte_exception = false; 194*1b59c632SVladimir Murzin 195*1b59c632SVladimir Murzin install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler); 196*1b59c632SVladimir Murzin 197*1b59c632SVladimir Murzin mem_read(tagged(mem, 2), &val); 198*1b59c632SVladimir Murzin 199*1b59c632SVladimir Murzin report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read"); 200*1b59c632SVladimir Murzin 201*1b59c632SVladimir Murzin mte_exception = false; 202*1b59c632SVladimir Murzin 203*1b59c632SVladimir Murzin mem_write(tagged(mem, 3), 0xbbbbbbbb); 204*1b59c632SVladimir Murzin 205*1b59c632SVladimir Murzin report((*mem == 0xffffffff) && mte_exception && (get_clear_tfsr() == 0), "write"); 206*1b59c632SVladimir Murzin 207*1b59c632SVladimir Murzin free_page(untagged(mem)); 208*1b59c632SVladimir Murzin } 209*1b59c632SVladimir Murzin 210*1b59c632SVladimir Murzin static void mte_asymm_test(void) 211*1b59c632SVladimir Murzin { 212*1b59c632SVladimir Murzin unsigned int *mem = tagged(alloc_page(), 2); 213*1b59c632SVladimir Murzin unsigned int val = 0; 214*1b59c632SVladimir Murzin 215*1b59c632SVladimir Murzin mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem); 216*1b59c632SVladimir Murzin mte_set_tag(mem, PAGE_SIZE, 2); 217*1b59c632SVladimir Murzin memset(mem, 0xff, PAGE_SIZE); 218*1b59c632SVladimir Murzin mte_set_tcf(MTE_TCF_ASYMM); 219*1b59c632SVladimir Murzin mte_exception = false; 220*1b59c632SVladimir Murzin 221*1b59c632SVladimir Murzin install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, mte_fault_handler); 222*1b59c632SVladimir Murzin 223*1b59c632SVladimir Murzin mem_read(tagged(mem, 3), &val); 224*1b59c632SVladimir Murzin report((val == 0) && mte_exception && (get_clear_tfsr() == 0), "read"); 225*1b59c632SVladimir Murzin 226*1b59c632SVladimir Murzin install_exception_handler(EL1H_SYNC, ESR_EL1_EC_DABT_EL1, NULL); 227*1b59c632SVladimir Murzin 228*1b59c632SVladimir Murzin mem_write(tagged(mem, 4), 0xaaaaaaaa); 229*1b59c632SVladimir Murzin report((*mem == 0xaaaaaaaa) && (get_clear_tfsr() == TFSR_EL1_TF0), "write"); 230*1b59c632SVladimir Murzin 231*1b59c632SVladimir Murzin free_page(untagged(mem)); 232*1b59c632SVladimir Murzin } 233*1b59c632SVladimir Murzin 234*1b59c632SVladimir Murzin static void mte_async_test(void) 235*1b59c632SVladimir Murzin { 236*1b59c632SVladimir Murzin unsigned int *mem = tagged(alloc_page(), 3); 237*1b59c632SVladimir Murzin unsigned int val = 0; 238*1b59c632SVladimir Murzin 239*1b59c632SVladimir Murzin mmu_set_tagged(current_thread_info()->pgtable, (unsigned long)mem); 240*1b59c632SVladimir Murzin mte_set_tag(mem, PAGE_SIZE, 3); 241*1b59c632SVladimir Murzin memset(mem, 0xff, PAGE_SIZE); 242*1b59c632SVladimir Murzin mte_set_tcf(MTE_TCF_ASYNC); 243*1b59c632SVladimir Murzin 244*1b59c632SVladimir Murzin mem_read(tagged(mem, 4), &val); 245*1b59c632SVladimir Murzin report((val == 0xffffffff) && (get_clear_tfsr() == TFSR_EL1_TF0), "read"); 246*1b59c632SVladimir Murzin 247*1b59c632SVladimir Murzin mem_write(tagged(mem, 5), 0xcccccccc); 248*1b59c632SVladimir Murzin report((*mem == 0xcccccccc) && (get_clear_tfsr() == TFSR_EL1_TF0), "write"); 249*1b59c632SVladimir Murzin 250*1b59c632SVladimir Murzin free_page(untagged(mem)); 251*1b59c632SVladimir Murzin } 252*1b59c632SVladimir Murzin 253*1b59c632SVladimir Murzin static unsigned int mte_version(void) 254*1b59c632SVladimir Murzin { 255*1b59c632SVladimir Murzin #ifdef CC_HAS_MTE 256*1b59c632SVladimir Murzin uint64_t r; 257*1b59c632SVladimir Murzin 258*1b59c632SVladimir Murzin asm volatile("mrs %x0, id_aa64pfr1_el1" : "=r"(r)); 259*1b59c632SVladimir Murzin 260*1b59c632SVladimir Murzin return (r >> ID_AA64PFR1_EL1_MTE_SHIFT) & 0b1111; 261*1b59c632SVladimir Murzin #else 262*1b59c632SVladimir Murzin report_info("Compiler lack MTE support"); 263*1b59c632SVladimir Murzin return 0; 264*1b59c632SVladimir Murzin #endif 265*1b59c632SVladimir Murzin } 266*1b59c632SVladimir Murzin 267*1b59c632SVladimir Murzin int main(int argc, char *argv[]) 268*1b59c632SVladimir Murzin { 269*1b59c632SVladimir Murzin 270*1b59c632SVladimir Murzin unsigned int version = mte_version(); 271*1b59c632SVladimir Murzin 272*1b59c632SVladimir Murzin if (version < 2) { 273*1b59c632SVladimir Murzin report_skip("No MTE support, skip...\n"); 274*1b59c632SVladimir Murzin return report_summary(); 275*1b59c632SVladimir Murzin } 276*1b59c632SVladimir Murzin 277*1b59c632SVladimir Murzin if (argc < 2) 278*1b59c632SVladimir Murzin report_abort("no test specified"); 279*1b59c632SVladimir Murzin 280*1b59c632SVladimir Murzin report_prefix_push("mte"); 281*1b59c632SVladimir Murzin 282*1b59c632SVladimir Murzin mte_init(); 283*1b59c632SVladimir Murzin 284*1b59c632SVladimir Murzin if (strcmp(argv[1], "sync") == 0) { 285*1b59c632SVladimir Murzin report_prefix_push(argv[1]); 286*1b59c632SVladimir Murzin mte_sync_test(); 287*1b59c632SVladimir Murzin report_prefix_pop(); 288*1b59c632SVladimir Murzin } else if (strcmp(argv[1], "async") == 0) { 289*1b59c632SVladimir Murzin report_prefix_push(argv[1]); 290*1b59c632SVladimir Murzin if (version < 3) { 291*1b59c632SVladimir Murzin report_skip("No MTE async, skip...\n"); 292*1b59c632SVladimir Murzin return report_summary(); 293*1b59c632SVladimir Murzin } 294*1b59c632SVladimir Murzin mte_async_test(); 295*1b59c632SVladimir Murzin report_prefix_pop(); 296*1b59c632SVladimir Murzin } else if (strcmp(argv[1], "asymm") == 0) { 297*1b59c632SVladimir Murzin report_prefix_push(argv[1]); 298*1b59c632SVladimir Murzin if (version < 3) { 299*1b59c632SVladimir Murzin report_skip("No MTE asymm, skip...\n"); 300*1b59c632SVladimir Murzin return report_summary(); 301*1b59c632SVladimir Murzin } 302*1b59c632SVladimir Murzin mte_asymm_test(); 303*1b59c632SVladimir Murzin report_prefix_pop(); 304*1b59c632SVladimir Murzin } else { 305*1b59c632SVladimir Murzin report_abort("Unknown sub-test '%s'", argv[1]); 306*1b59c632SVladimir Murzin } 307*1b59c632SVladimir Murzin 308*1b59c632SVladimir Murzin return report_summary(); 309*1b59c632SVladimir Murzin } 310