xref: /kvm-unit-tests/powerpc/interrupts.c (revision 386ed5c2f9e98a2605817748c44c0cefa60dc88e)
1 /* SPDX-License-Identifier: LGPL-2.0-only */
2 /*
3  * Test interrupts
4  *
5  * Copyright 2024 Nicholas Piggin, IBM Corp.
6  */
7 #include <libcflat.h>
8 #include <util.h>
9 #include <migrate.h>
10 #include <alloc.h>
11 #include <asm/setup.h>
12 #include <asm/handlers.h>
13 #include <asm/hcall.h>
14 #include <asm/processor.h>
15 #include <asm/time.h>
16 #include <asm/barrier.h>
17 
18 static volatile bool got_interrupt;
19 static volatile struct pt_regs recorded_regs;
20 
21 static void mce_handler(struct pt_regs *regs, void *opaque)
22 {
23 	bool *is_fetch = opaque;
24 
25 	got_interrupt = true;
26 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
27 	if (*is_fetch)
28 		regs->nip = regs->link;
29 	else
30 		regs_advance_insn(regs);
31 }
32 
33 static void fault_handler(struct pt_regs *regs, void *opaque)
34 {
35 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
36 	if (regs->trap == 0x400 || regs->trap == 0x480)
37 		regs->nip = regs->link;
38 	else
39 		regs_advance_insn(regs);
40 }
41 
42 static void test_mce(void)
43 {
44 	unsigned long addr = -4ULL;
45 	uint8_t tmp;
46 	bool is_fetch;
47 
48 	report_prefix_push("mce");
49 
50 	handle_exception(0x200, mce_handler, &is_fetch);
51 	handle_exception(0x300, fault_handler, NULL);
52 	handle_exception(0x380, fault_handler, NULL);
53 	handle_exception(0x400, fault_handler, NULL);
54 	handle_exception(0x480, fault_handler, NULL);
55 
56 	if (machine_is_powernv()) {
57 		enable_mcheck();
58 	} else {
59 		report(mfmsr() & MSR_ME, "pseries machine has MSR[ME]=1");
60 		if (!(mfmsr() & MSR_ME)) { /* try to fix it */
61 			enable_mcheck();
62 		}
63 		if (mfmsr() & MSR_ME) {
64 			disable_mcheck();
65 			report(mfmsr() & MSR_ME, "pseries is unable to change MSR[ME]");
66 			if (!(mfmsr() & MSR_ME)) { /* try to fix it */
67 				enable_mcheck();
68 			}
69 		}
70 	}
71 
72 	is_fetch = false;
73 	asm volatile("lbz %0,0(%1)" : "=r"(tmp) : "r"(addr));
74 
75 	report(got_interrupt, "MCE on access to invalid real address");
76 	if (got_interrupt) {
77 		report(mfspr(SPR_DAR) == addr, "MCE sets DAR correctly");
78 		if (cpu_has_power_mce)
79 			report(recorded_regs.msr & (1ULL << 21), "d-side MCE sets SRR1[42]");
80 		got_interrupt = false;
81 	}
82 
83 	is_fetch = true;
84 	asm volatile("mtctr %0 ; bctrl" :: "r"(addr) : "ctr", "lr");
85 	report(got_interrupt, "MCE on fetch from invalid real address");
86 	if (got_interrupt) {
87 		report(recorded_regs.nip == addr, "MCE sets SRR0 correctly");
88 		if (cpu_has_power_mce)
89 			report(!(recorded_regs.msr & (1ULL << 21)), "i-side MCE clears SRR1[42]");
90 		got_interrupt = false;
91 	}
92 
93 	handle_exception(0x200, NULL, NULL);
94 	handle_exception(0x300, NULL, NULL);
95 	handle_exception(0x380, NULL, NULL);
96 	handle_exception(0x400, NULL, NULL);
97 	handle_exception(0x480, NULL, NULL);
98 
99 	report_prefix_pop();
100 }
101 
102 static void dseg_handler(struct pt_regs *regs, void *data)
103 {
104 	got_interrupt = true;
105 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
106 	regs_advance_insn(regs);
107 	regs->msr &= ~MSR_DR;
108 }
109 
110 static void test_dseg(void)
111 {
112 	uint64_t msr, tmp;
113 
114 	report_prefix_push("data segment");
115 
116 	/* Some HV start in radix mode and need 0x300 */
117 	handle_exception(0x300, &dseg_handler, NULL);
118 	handle_exception(0x380, &dseg_handler, NULL);
119 
120 	asm volatile(
121 "		mfmsr	%0		\n \
122 		ori	%0,%0,%2	\n \
123 		mtmsrd	%0		\n \
124 		lbz	%1,0(0)		"
125 		: "=r"(msr), "=r"(tmp) : "i"(MSR_DR): "memory");
126 
127 	report(got_interrupt, "interrupt on NULL dereference");
128 	got_interrupt = false;
129 
130 	handle_exception(0x300, NULL, NULL);
131 	handle_exception(0x380, NULL, NULL);
132 
133 	report_prefix_pop();
134 }
135 
136 static void dec_handler(struct pt_regs *regs, void *data)
137 {
138 	got_interrupt = true;
139 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
140 	regs->msr &= ~MSR_EE;
141 }
142 
143 static void test_dec(void)
144 {
145 	uint64_t msr;
146 	uint64_t tb;
147 
148 	report_prefix_push("decrementer");
149 
150 	handle_exception(0x900, &dec_handler, NULL);
151 
152 	asm volatile(
153 "		mtdec	%1		\n \
154 		mfmsr	%0		\n \
155 		ori	%0,%0,%2	\n \
156 		mtmsrd	%0,1		"
157 		: "=r"(msr) : "r"(10000), "i"(MSR_EE): "memory");
158 
159 	tb = get_tb();
160 	while (!got_interrupt) {
161 		if (get_tb() - tb > tb_hz * 5)
162 			break; /* timeout 5s */
163 	}
164 
165 	report(got_interrupt, "interrupt on decrementer underflow");
166 	got_interrupt = false;
167 
168 	handle_exception(0x900, NULL, NULL);
169 
170 	if (!machine_is_powernv())
171 		goto done; /* Skip HV tests */
172 
173 	handle_exception(0x980, &dec_handler, NULL);
174 
175 	mtspr(SPR_LPCR, mfspr(SPR_LPCR) | LPCR_HDICE);
176 	asm volatile(
177 "		mtspr	0x136,%1	\n \
178 		mtdec	%3		\n \
179 		mfmsr	%0		\n \
180 		ori	%0,%0,%2	\n \
181 		mtmsrd	%0,1		"
182 		: "=r"(msr) : "r"(10000), "i"(MSR_EE), "r"(0x7fffffff): "memory");
183 
184 	tb = get_tb();
185 	while (!got_interrupt) {
186 		if (get_tb() - tb > tb_hz * 5)
187 			break; /* timeout 5s */
188 	}
189 
190 	mtspr(SPR_LPCR, mfspr(SPR_LPCR) & ~LPCR_HDICE);
191 
192 	report(got_interrupt, "interrupt on hdecrementer underflow");
193 	got_interrupt = false;
194 
195 	handle_exception(0x980, NULL, NULL);
196 
197 done:
198 	report_prefix_pop();
199 }
200 
201 
202 static volatile uint64_t recorded_heir;
203 
204 static void heai_handler(struct pt_regs *regs, void *data)
205 {
206 	got_interrupt = true;
207 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
208 	regs_advance_insn(regs);
209 	if (cpu_has_heai)
210 		recorded_heir = mfspr(SPR_HEIR);
211 }
212 
213 static void program_handler(struct pt_regs *regs, void *data)
214 {
215 	got_interrupt = true;
216 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
217 	regs_advance_insn(regs);
218 }
219 
220 /*
221  * This tests invalid instruction handling. powernv (HV) should take an
222  * HEAI interrupt with the HEIR SPR set to the instruction image. pseries
223  * (guest) should take a program interrupt. CPUs which support prefix
224  * should report prefix instruction in (H)SRR1[34].
225  */
226 static void test_illegal(void)
227 {
228 	report_prefix_push("illegal instruction");
229 
230 	if (machine_is_powernv()) {
231 		handle_exception(0xe40, &heai_handler, NULL);
232 	} else {
233 		handle_exception(0x700, &program_handler, NULL);
234 	}
235 
236 	asm volatile(".long 0x12345678" ::: "memory");
237 	report(got_interrupt, "interrupt on invalid instruction");
238 	got_interrupt = false;
239 	if (cpu_has_heai)
240 		report(recorded_heir == 0x12345678, "HEIR: 0x%08lx", recorded_heir);
241 	report(!regs_is_prefix(&recorded_regs), "(H)SRR1 prefix bit clear");
242 
243 	if (cpu_has_prefix) {
244 		asm volatile(".balign 8 ; .long 0x04000123; .long 0x00badc0d");
245 		report(got_interrupt, "interrupt on invalid prefix instruction");
246 		got_interrupt = false;
247 		if (cpu_has_heai)
248 			report(recorded_heir == 0x0400012300badc0d, "HEIR: 0x%08lx", recorded_heir);
249 		report(regs_is_prefix(&recorded_regs), "(H)SRR1 prefix bit set");
250 	}
251 
252 	handle_exception(0xe40, NULL, NULL);
253 	handle_exception(0x700, NULL, NULL);
254 
255 	report_prefix_pop();
256 }
257 
258 static void sc_handler(struct pt_regs *regs, void *data)
259 {
260 	got_interrupt = true;
261 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
262 }
263 
264 static void test_sc(void)
265 {
266 	report_prefix_push("syscall");
267 
268 	handle_exception(0xc00, &sc_handler, NULL);
269 
270 	asm volatile("sc 0" ::: "memory");
271 
272 	report(got_interrupt, "interrupt on sc 0 instruction");
273 	got_interrupt = false;
274 	if (cpu_has_sc_lev)
275 		report(((recorded_regs.msr >> 20) & 0x3) == 0, "SRR1 set LEV=0");
276 	if (machine_is_powernv()) {
277 		asm volatile("sc 1" ::: "memory");
278 
279 		report(got_interrupt, "interrupt on sc 1 instruction");
280 		got_interrupt = false;
281 		if (cpu_has_sc_lev)
282 			report(((recorded_regs.msr >> 20) & 0x3) == 1, "SRR1 set LEV=1");
283 	}
284 
285 	handle_exception(0xc00, NULL, NULL);
286 
287 	report_prefix_pop();
288 }
289 
290 
291 static void trace_handler(struct pt_regs *regs, void *data)
292 {
293 	got_interrupt = true;
294 	memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
295 	regs->msr &= ~(MSR_SE | MSR_BE);
296 }
297 
298 static void program_trace_handler(struct pt_regs *regs, void *data)
299 {
300 	regs->msr &= ~(MSR_SE | MSR_BE);
301 	regs->nip += 4;
302 }
303 
304 extern char trace_insn[];
305 extern char trace_insn2[];
306 extern char trace_insn3[];
307 extern char trace_rfid[];
308 
309 static void test_trace(void)
310 {
311 	unsigned long msr;
312 
313 	report_prefix_push("trace");
314 
315 	handle_exception(0xd00, &trace_handler, NULL);
316 
317 	msr = mfmsr() | MSR_SE;
318 	asm volatile(
319 	"	mtmsr	%0		\n"
320 	".global trace_insn		\n"
321 	"trace_insn:			\n"
322 	"	nop			\n"
323 	: : "r"(msr) : "memory");
324 
325 	report(got_interrupt, "interrupt on single step");
326 	got_interrupt = false;
327 	report(recorded_regs.nip == (unsigned long)trace_insn + 4,
328 			"single step interrupt at the correct address");
329 	if (cpu_has_siar)
330 		report(mfspr(SPR_SIAR) == (unsigned long)trace_insn,
331 			"single step recorded SIAR at the correct address");
332 
333 	msr = mfmsr() | MSR_SE;
334 	asm volatile(
335 	"	mtmsr	%0		\n"
336 	".global trace_insn2		\n"
337 	"trace_insn2:			\n"
338 	"	b	1f		\n"
339 	"	nop			\n"
340 	"1:				\n"
341 	: : "r"(msr) : "memory");
342 
343 	report(got_interrupt, "interrupt on single step branch");
344 	got_interrupt = false;
345 	report(recorded_regs.nip == (unsigned long)trace_insn2 + 8,
346 			"single step interrupt at the correct address");
347 	if (cpu_has_siar)
348 		report(mfspr(SPR_SIAR) == (unsigned long)trace_insn2,
349 			"single step recorded SIAR at the correct address");
350 
351 	msr = mfmsr() | MSR_BE;
352 	asm volatile(
353 	"	mtmsr	%0		\n"
354 	".global trace_insn3		\n"
355 	"trace_insn3:			\n"
356 	"	nop			\n"
357 	"	b	1f		\n"
358 	"	nop			\n"
359 	"1:				\n"
360 	: : "r"(msr) : "memory");
361 
362 	report(got_interrupt, "interrupt on branch trace");
363 	got_interrupt = false;
364 	report(recorded_regs.nip == (unsigned long)trace_insn3 + 12,
365 			"branch trace interrupt at the correct address");
366 	if (cpu_has_siar)
367 		report(mfspr(SPR_SIAR) == (unsigned long)trace_insn3 + 4,
368 			"branch trace recorded SIAR at the correct address");
369 
370 	handle_exception(0x700, &program_trace_handler, NULL);
371 	msr = mfmsr() | MSR_SE;
372 	asm volatile(
373 	"	mtmsr	%0		\n"
374 	"	trap			\n"
375 	: : "r"(msr) : "memory");
376 
377 	report(!got_interrupt, "no interrupt on single step trap");
378 	got_interrupt = false;
379 	handle_exception(0x700, NULL, NULL);
380 
381 	msr = mfmsr() | MSR_SE;
382 	mtspr(SPR_SRR0, (unsigned long)trace_rfid);
383 	mtspr(SPR_SRR1, mfmsr());
384 	asm volatile(
385 	"	mtmsr	%0		\n"
386 	"	rfid			\n"
387 	".global trace_rfid		\n"
388 	"trace_rfid:			\n"
389 	: : "r"(msr) : "memory");
390 
391 	report(!got_interrupt, "no interrupt on single step rfid");
392 	got_interrupt = false;
393 	handle_exception(0xd00, NULL, NULL);
394 
395 	report_prefix_pop();
396 }
397 
398 
399 int main(int argc, char **argv)
400 {
401 	report_prefix_push("interrupts");
402 
403 	if (cpu_has_power_mce)
404 		test_mce();
405 	test_dseg();
406 	test_illegal();
407 	test_dec();
408 	test_sc();
409 	test_trace();
410 
411 	report_prefix_pop();
412 
413 	return report_summary();
414 }
415