xref: /kvm-unit-tests/powerpc/mmu.c (revision d4c8e725478d05179b23be44fc61357a92da4912)
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