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