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 */
mem_read(unsigned int * addr,unsigned int * res)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
mem_write(unsigned int * addr,unsigned int val)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
mte_fault_handler(struct pt_regs * regs,unsigned int esr)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
mmu_set_tagged(pgd_t * pgtable,unsigned long vaddr)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
mte_init(void)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
mte_set_tcf(unsigned long tcf)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
mte_set_tag(void * addr,size_t size,unsigned int tag)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
get_clear_tfsr(void)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
mte_sync_test(void)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
mte_asymm_test(void)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
mte_async_test(void)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
mte_version(void)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
main(int argc,char * argv[])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