1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * MMU Tests
4 *
5 * Copyright 2024 Nicholas Piggin, IBM Corp.
6 */
7 #include <libcflat.h>
8 #include <asm/atomic.h>
9 #include <asm/barrier.h>
10 #include <asm/processor.h>
11 #include <asm/mmu.h>
12 #include <asm/smp.h>
13 #include <asm/setup.h>
14 #include <asm/ppc_asm.h>
15 #include <vmalloc.h>
16 #include <devicetree.h>
17
18 static volatile bool tlbie_test_running = true;
19 static volatile bool tlbie_test_failed = false;
20 static int tlbie_fn_started;
21
22 static void *memory;
23
trap_handler(struct pt_regs * regs,void * opaque)24 static void trap_handler(struct pt_regs *regs, void *opaque)
25 {
26 tlbie_test_failed = true;
27 regs_advance_insn(regs);
28 }
29
tlbie_fn(int cpu_id)30 static void tlbie_fn(int cpu_id)
31 {
32 volatile char *m = memory;
33
34 setup_mmu(0, NULL);
35
36 atomic_fetch_inc(&tlbie_fn_started);
37 while (tlbie_test_running) {
38 unsigned long tmp;
39
40 /*
41 * This is intended to execuse a QEMU TCG bug by forming a
42 * large TB which can prevent async work from running while the
43 * TB executes, so it could miss a broadcast TLB invalidation
44 * and pick up a stale translation.
45 */
46 asm volatile (".rept 256 ; lbz %0,0(%1) ; tdnei %0,0 ; .endr" : "=&r"(tmp) : "r"(m));
47 }
48 }
49
50 #define ITERS 100000
51
test_tlbie(int argc,char ** argv)52 static void test_tlbie(int argc, char **argv)
53 {
54 void *m[2];
55 phys_addr_t p[2];
56 pteval_t pteval[2];
57 pteval_t *ptep;
58 int i;
59
60 if (argc > 2)
61 report_abort("Unsupported argument: '%s'", argv[2]);
62
63 if (nr_cpus_present < 2) {
64 report_skip("Requires SMP (2 or more CPUs)");
65 return;
66 }
67
68 handle_exception(0x700, &trap_handler, NULL);
69
70 m[0] = alloc_page();
71 p[0] = virt_to_phys(m[0]);
72 memset(m[0], 0, PAGE_SIZE);
73 m[1] = alloc_page();
74 p[1] = virt_to_phys(m[1]);
75 memset(m[1], 0, PAGE_SIZE);
76
77 memory = alloc_vpages(1);
78 ptep = install_page(NULL, p[0], memory);
79 pteval[0] = *ptep;
80 assert(ptep == install_page(NULL, p[1], memory));
81 pteval[1] = *ptep;
82 assert(ptep == install_page(NULL, p[0], memory));
83 assert(pteval[0] == *ptep);
84 flush_tlb_page((unsigned long)memory);
85
86 if (!start_all_cpus(tlbie_fn))
87 report_abort("Failed to start secondary cpus");
88
89 while (tlbie_fn_started < nr_cpus_present - 1) {
90 cpu_relax();
91 }
92
93 for (i = 0; i < ITERS; i++) {
94 *ptep = pteval[1];
95 flush_tlb_page((unsigned long)memory);
96 *(long *)m[0] = -1;
97 barrier();
98 *(long *)m[0] = 0;
99 barrier();
100 *ptep = pteval[0];
101 flush_tlb_page((unsigned long)memory);
102 *(long *)m[1] = -1;
103 barrier();
104 *(long *)m[1] = 0;
105 barrier();
106 if (tlbie_test_failed)
107 break;
108 }
109
110 tlbie_test_running = false;
111
112 stop_all_cpus();
113
114 handle_exception(0x700, NULL, NULL);
115
116 /* TCG has a known race invalidating other CPUs */
117 report_kfail(host_is_tcg, !tlbie_test_failed, "tlbie");
118 }
119
120 #define THIS_ITERS 100000
121
test_tlbie_this_cpu(int argc,char ** argv)122 static void test_tlbie_this_cpu(int argc, char **argv)
123 {
124 void *m[2];
125 phys_addr_t p[2];
126 pteval_t pteval[2];
127 pteval_t *ptep;
128 int i;
129 bool success;
130
131 if (argc > 2)
132 report_abort("Unsupported argument: '%s'", argv[2]);
133
134 m[0] = alloc_page();
135 p[0] = virt_to_phys(m[0]);
136 memset(m[0], 0, PAGE_SIZE);
137 m[1] = alloc_page();
138 p[1] = virt_to_phys(m[1]);
139 memset(m[1], 0, PAGE_SIZE);
140
141 memory = alloc_vpages(1);
142 ptep = install_page(NULL, p[0], memory);
143 pteval[0] = *ptep;
144 assert(ptep == install_page(NULL, p[1], memory));
145 pteval[1] = *ptep;
146 assert(ptep == install_page(NULL, p[0], memory));
147 assert(pteval[0] == *ptep);
148 flush_tlb_page((unsigned long)memory);
149
150 *(long *)m[0] = 0;
151 *(long *)m[1] = -1;
152
153 success = true;
154 for (i = 0; i < THIS_ITERS; i++) {
155 if (*(long *)memory != 0) {
156 success = false;
157 break;
158 }
159 *ptep = pteval[1];
160 flush_tlb_page_local((unsigned long)memory);
161 if (*(long *)memory != -1) {
162 success = false;
163 break;
164 }
165 *ptep = pteval[0];
166 flush_tlb_page_local((unsigned long)memory);
167 }
168 report(success, "tlbiel");
169
170 success = true;
171 flush_tlb_page((unsigned long)memory);
172 for (i = 0; i < THIS_ITERS; i++) {
173 if (*(long *)memory != 0) {
174 success = false;
175 break;
176 }
177 *ptep = pteval[1];
178 flush_tlb_page((unsigned long)memory);
179 if (*(long *)memory != -1) {
180 success = false;
181 break;
182 }
183 *ptep = pteval[0];
184 flush_tlb_page((unsigned long)memory);
185 }
186 report(success, "tlbie");
187 }
188
189
190 struct {
191 const char *name;
192 void (*func)(int argc, char **argv);
193 } hctests[] = {
194 { "tlbi-this-cpu", test_tlbie_this_cpu },
195 { "tlbi-other-cpu", test_tlbie },
196 { NULL, NULL }
197 };
198
main(int argc,char ** argv)199 int main(int argc, char **argv)
200 {
201 bool all;
202 int i;
203
204 if (!vm_available()) {
205 report_skip("MMU is only supported for radix");
206 return 0;
207 }
208
209 setup_vm();
210
211 all = argc == 1 || !strcmp(argv[1], "all");
212
213 report_prefix_push("mmu");
214
215 for (i = 0; hctests[i].name != NULL; i++) {
216 if (all || strcmp(argv[1], hctests[i].name) == 0) {
217 report_prefix_push(hctests[i].name);
218 hctests[i].func(argc, argv);
219 report_prefix_pop();
220 }
221 }
222
223 report_prefix_pop();
224 return report_summary();
225 }
226