xref: /kvm-unit-tests/s390x/pv-icptcode.c (revision 1f08a91a41402b0e032ecce8ed1b5952cbfca0ea)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3  * PV virtualization interception tests for intercepts that are not
4  * caused by an instruction.
5  *
6  * Copyright (c) 2023 IBM Corp
7  *
8  * Authors:
9  *  Janosch Frank <frankja@linux.ibm.com>
10  */
11 #include <libcflat.h>
12 #include <sie.h>
13 #include <smp.h>
14 #include <sclp.h>
15 #include <snippet.h>
16 #include <sie-icpt.h>
17 #include <asm/facility.h>
18 #include <asm/barrier.h>
19 #include <asm/sigp.h>
20 #include <asm/uv.h>
21 #include <asm/time.h>
22 
23 static struct vm vm, vm2;
24 
25 /*
26  * The hypervisor should not be able to decrease the cpu timer by an
27  * amount that is higher than the amount of time spent outside of
28  * SIE.
29  *
30  * Warning: A lot of things influence time so decreasing the timer by
31  * a more significant amount than the difference to have a safety
32  * margin is advised.
33  */
test_validity_timing(void)34 static void test_validity_timing(void)
35 {
36 	extern const char SNIPPET_NAME_START(asm, pv_icpt_vir_timing)[];
37 	extern const char SNIPPET_NAME_END(asm, pv_icpt_vir_timing)[];
38 	extern const char SNIPPET_HDR_START(asm, pv_icpt_vir_timing)[];
39 	extern const char SNIPPET_HDR_END(asm, pv_icpt_vir_timing)[];
40 	int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_vir_timing);
41 	int size_gbin = SNIPPET_LEN(asm, pv_icpt_vir_timing);
42 	uint64_t time_exit, time_entry, tmp;
43 
44 	report_prefix_push("manipulated cpu time");
45 	snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_vir_timing),
46 			SNIPPET_HDR_START(asm, pv_icpt_vir_timing),
47 			size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
48 
49 	sie(&vm);
50 	report(sie_is_diag_icpt(&vm, 0x44), "spt done");
51 	stck(&time_exit);
52 	tmp = vm.sblk->cputm;
53 	mb();
54 
55 	/* Cpu timer counts down so adding a ms should lead to a validity */
56 	vm.sblk->cputm += S390_CLOCK_SHIFT_US * 1000;
57 	sie_expect_validity(&vm);
58 	sie(&vm);
59 	report(uv_validity_check(&vm), "validity entry cput > exit cput");
60 	vm.sblk->cputm = tmp;
61 
62 	/*
63 	 * We are not allowed to decrement the timer more than the
64 	 * time spent outside of SIE
65 	 */
66 	stck(&time_entry);
67 	vm.sblk->cputm -= (time_entry - time_exit) + S390_CLOCK_SHIFT_US * 1000;
68 	sie_expect_validity(&vm);
69 	sie(&vm);
70 	report(uv_validity_check(&vm), "validity entry cput < time spent outside SIE");
71 	vm.sblk->cputm = tmp;
72 
73 	uv_destroy_guest(&vm);
74 	report_prefix_pop();
75 }
76 
run_loop(void)77 static void run_loop(void)
78 {
79 	sie(&vm);
80 	sigp_retry(stap(), SIGP_STOP, 0, NULL);
81 }
82 
test_validity_already_running(void)83 static void test_validity_already_running(void)
84 {
85 	extern const char SNIPPET_NAME_START(asm, loop)[];
86 	extern const char SNIPPET_NAME_END(asm, loop)[];
87 	extern const char SNIPPET_HDR_START(asm, loop)[];
88 	extern const char SNIPPET_HDR_END(asm, loop)[];
89 	int size_hdr = SNIPPET_HDR_LEN(asm, loop);
90 	int size_gbin = SNIPPET_LEN(asm, loop);
91 	struct psw psw = {
92 		.mask = PSW_MASK_64,
93 		.addr = (uint64_t)run_loop,
94 	};
95 
96 	report_prefix_push("already running");
97 	if (smp_query_num_cpus() < 3) {
98 		report_skip("need at least 3 cpus for this test");
99 		goto out;
100 	}
101 
102 	snippet_pv_init(&vm, SNIPPET_NAME_START(asm, loop),
103 			SNIPPET_HDR_START(asm, loop),
104 			size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
105 
106 	smp_cpu_setup(1, psw);
107 	sie_expect_validity(&vm);
108 	smp_cpu_setup(2, psw);
109 	while (vm.sblk->icptcode != ICPT_VALIDITY) {
110 		mb();
111 	}
112 
113 	/*
114 	 * One cpu will enter SIE and one will receive the validity.
115 	 * We rely on the expectation that the cpu in SIE won't exit
116 	 * until we had a chance to observe the validity as the exit
117 	 * would overwrite the validity.
118 	 *
119 	 * In general that expectation is valid but HW/FW can in
120 	 * theory still exit to handle their interrupts.
121 	 */
122 	report(uv_validity_check(&vm), "validity");
123 	smp_cpu_stop(1);
124 	smp_cpu_stop(2);
125 	uv_destroy_guest(&vm);
126 
127 out:
128 	report_prefix_pop();
129 }
130 
131 /* Tests if a vcpu handle from another configuration results in a validity intercept. */
test_validity_handle_not_in_config(void)132 static void test_validity_handle_not_in_config(void)
133 {
134 	extern const char SNIPPET_NAME_START(asm, icpt_loop)[];
135 	extern const char SNIPPET_NAME_END(asm, icpt_loop)[];
136 	extern const char SNIPPET_HDR_START(asm, icpt_loop)[];
137 	extern const char SNIPPET_HDR_END(asm, icpt_loop)[];
138 	int size_hdr = SNIPPET_HDR_LEN(asm, icpt_loop);
139 	int size_gbin = SNIPPET_LEN(asm, icpt_loop);
140 
141 	report_prefix_push("handle not in config");
142 	/* Setup our primary vm */
143 	snippet_pv_init(&vm, SNIPPET_NAME_START(asm, icpt_loop),
144 			SNIPPET_HDR_START(asm, icpt_loop),
145 			size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
146 
147 	/* Setup secondary vm */
148 	snippet_setup_guest(&vm2, true);
149 	snippet_pv_init(&vm2, SNIPPET_NAME_START(asm, icpt_loop),
150 			SNIPPET_HDR_START(asm, icpt_loop),
151 			size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
152 
153 	vm.sblk->pv_handle_cpu = vm2.sblk->pv_handle_cpu;
154 	sie_expect_validity(&vm);
155 	sie(&vm);
156 	report(uv_validity_check(&vm), "switched cpu handle");
157 	vm.sblk->pv_handle_cpu = vm.uv.vcpu_handle;
158 
159 	vm.sblk->pv_handle_config = vm2.uv.vm_handle;
160 	sie_expect_validity(&vm);
161 	sie(&vm);
162 	report(uv_validity_check(&vm), "switched configuration handle");
163 	vm.sblk->pv_handle_config = vm.uv.vm_handle;
164 
165 	/* Destroy the second vm, since we don't need it for further tests */
166 	uv_destroy_guest(&vm2);
167 	sie_guest_destroy(&vm2);
168 
169 	uv_destroy_guest(&vm);
170 	report_prefix_pop();
171 }
172 
173 /* Tests if a wrong vm or vcpu handle results in a validity intercept. */
test_validity_seid(void)174 static void test_validity_seid(void)
175 {
176 	extern const char SNIPPET_NAME_START(asm, icpt_loop)[];
177 	extern const char SNIPPET_NAME_END(asm, icpt_loop)[];
178 	extern const char SNIPPET_HDR_START(asm, icpt_loop)[];
179 	extern const char SNIPPET_HDR_END(asm, icpt_loop)[];
180 	int size_hdr = SNIPPET_HDR_LEN(asm, icpt_loop);
181 	int size_gbin = SNIPPET_LEN(asm, icpt_loop);
182 	int fails = 0;
183 	int i;
184 
185 	report_prefix_push("handles");
186 	snippet_pv_init(&vm, SNIPPET_NAME_START(asm, icpt_loop),
187 			SNIPPET_HDR_START(asm, icpt_loop),
188 			size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
189 
190 	for (i = 0; i < 64; i++) {
191 		vm.sblk->pv_handle_config ^= 1UL << i;
192 		sie_expect_validity(&vm);
193 		sie(&vm);
194 		if (!uv_validity_check(&vm)) {
195 			report_fail("SIE accepted wrong VM SEID, changed bit %d",
196 				    63 - i);
197 			fails++;
198 		}
199 		vm.sblk->pv_handle_config ^= 1UL << i;
200 	}
201 	report(!fails, "No wrong vm handle accepted");
202 
203 	fails = 0;
204 	for (i = 0; i < 64; i++) {
205 		vm.sblk->pv_handle_cpu ^= 1UL << i;
206 		sie_expect_validity(&vm);
207 		sie(&vm);
208 		if (!uv_validity_check(&vm)) {
209 			report_fail("SIE accepted wrong CPU SEID, changed bit %d",
210 				    63 - i);
211 			fails++;
212 		}
213 		vm.sblk->pv_handle_cpu ^= 1UL << i;
214 	}
215 	report(!fails, "No wrong cpu handle accepted");
216 
217 	uv_destroy_guest(&vm);
218 	report_prefix_pop();
219 }
220 
221 /*
222  * Tests if we get a validity intercept if the CR1 asce at SIE entry
223  * is not the same as the one given at the UV creation of the VM.
224  */
test_validity_asce(void)225 static void test_validity_asce(void)
226 {
227 	extern const char SNIPPET_NAME_START(asm, pv_icpt_112)[];
228 	extern const char SNIPPET_NAME_END(asm, pv_icpt_112)[];
229 	extern const char SNIPPET_HDR_START(asm, pv_icpt_112)[];
230 	extern const char SNIPPET_HDR_END(asm, pv_icpt_112)[];
231 	int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_112);
232 	int size_gbin = SNIPPET_LEN(asm, pv_icpt_112);
233 	uint64_t asce_old, asce_new;
234 	void *pgd_new, *pgd_old;
235 
236 	report_prefix_push("asce");
237 	snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_112),
238 			SNIPPET_HDR_START(asm, pv_icpt_112),
239 			size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
240 
241 	asce_old = vm.save_area.guest.asce;
242 	pgd_new = memalign_pages_flags(PAGE_SIZE, PAGE_SIZE * 4, 0);
243 	pgd_old = (void *)(asce_old & PAGE_MASK);
244 
245 	/* Copy the contents of the top most table */
246 	memcpy(pgd_new, pgd_old, PAGE_SIZE * 4);
247 
248 	/* Create the replacement ASCE */
249 	asce_new = __pa(pgd_new) | ASCE_DT_REGION1 | REGION_TABLE_LENGTH | ASCE_P;
250 	vm.save_area.guest.asce = asce_new;
251 
252 	sie_expect_validity(&vm);
253 	sie(&vm);
254 	report(uv_validity_check(&vm), "wrong CR1 validity");
255 
256 	/* Restore the old ASCE */
257 	vm.save_area.guest.asce = asce_old;
258 
259 	/* Try if we can still do an entry with the correct asce */
260 	sie(&vm);
261 	report(sie_is_diag_icpt(&vm, 0x44), "re-entry with valid CR1");
262 	uv_destroy_guest(&vm);
263 	free_pages(pgd_new);
264 	report_prefix_pop();
265 }
266 
run_icpt_122_tests(unsigned long lc_off)267 static void run_icpt_122_tests(unsigned long lc_off)
268 {
269 	uv_export(vm.sblk->mso + lc_off);
270 	sie(&vm);
271 	report(vm.sblk->icptcode == ICPT_PV_PREF, "Intercept 112 for page 0");
272 	uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off);
273 
274 	uv_export(vm.sblk->mso + lc_off + PAGE_SIZE);
275 	sie(&vm);
276 	report(vm.sblk->icptcode == ICPT_PV_PREF, "Intercept 112 for page 1");
277 	uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off + PAGE_SIZE);
278 }
279 
run_icpt_122_tests_prefix(unsigned long prefix)280 static void run_icpt_122_tests_prefix(unsigned long prefix)
281 {
282 	uint32_t *ptr = 0;
283 
284 	report_prefix_pushf("0x%lx", prefix);
285 	report_prefix_push("unshared");
286 	run_icpt_122_tests(prefix);
287 	report_prefix_pop();
288 
289 	/*
290 	 * Guest will share the lowcore and we need to check if that
291 	 * makes a difference (which it should not).
292 	 */
293 	report_prefix_push("shared");
294 
295 	sie(&vm);
296 	/* Guest indicates that it has been setup via the diag 0x44 */
297 	assert(sie_is_diag_icpt(&vm, 0x44));
298 	/* If the pages have not been shared these writes will cause exceptions */
299 	ptr = (uint32_t *)prefix;
300 	WRITE_ONCE(ptr, 0);
301 	ptr = (uint32_t *)(prefix + offsetof(struct lowcore, ars_sa[0]));
302 	WRITE_ONCE(ptr, 0);
303 
304 	run_icpt_122_tests(prefix);
305 
306 	/* shared*/
307 	report_prefix_pop();
308 	/* prefix hex value */
309 	report_prefix_pop();
310 }
311 
test_icpt_112(void)312 static void test_icpt_112(void)
313 {
314 	extern const char SNIPPET_NAME_START(asm, pv_icpt_112)[];
315 	extern const char SNIPPET_NAME_END(asm, pv_icpt_112)[];
316 	extern const char SNIPPET_HDR_START(asm, pv_icpt_112)[];
317 	extern const char SNIPPET_HDR_END(asm, pv_icpt_112)[];
318 	int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_112);
319 	int size_gbin = SNIPPET_LEN(asm, pv_icpt_112);
320 
321 	unsigned long lc_off = 0;
322 
323 	report_prefix_push("prefix");
324 
325 	snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_112),
326 			SNIPPET_HDR_START(asm, pv_icpt_112),
327 			size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
328 
329 	/* Setup of the guest's state for 0x0 prefix */
330 	sie(&vm);
331 	assert(sie_is_diag_icpt(&vm, 0x44));
332 
333 	/* Test on standard 0x0 prefix */
334 	run_icpt_122_tests_prefix(0);
335 
336 	/* Setup of the guest's state for 0x8000 prefix */
337 	lc_off = 0x8000;
338 	uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off);
339 	uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off + PAGE_SIZE);
340 	/* Guest will set prefix to 0x8000 */
341 	sie(&vm);
342 	/* SPX generates a PV instruction notification */
343 	assert(vm.sblk->icptcode == ICPT_PV_NOTIFY && vm.sblk->ipa == 0xb210);
344 	assert(*(u32 *)vm.sblk->sidad == 0x8000);
345 
346 	/* Test on 0x8000 prefix */
347 	run_icpt_122_tests_prefix(0x8000);
348 
349 	/* Try a re-entry after everything has been imported again */
350 	sie(&vm);
351 	report(sie_is_diag_icpt(&vm, 0x9c) &&
352 	       vm.save_area.guest.grs[0] == 42,
353 	       "re-entry successful");
354 	report_prefix_pop();
355 	uv_destroy_guest(&vm);
356 }
357 
main(void)358 int main(void)
359 {
360 	report_prefix_push("pv-icpts");
361 	if (!uv_host_requirement_checks())
362 		goto done;
363 
364 	snippet_setup_guest(&vm, true);
365 	test_icpt_112();
366 	test_validity_asce();
367 	test_validity_seid();
368 	test_validity_handle_not_in_config();
369 	test_validity_already_running();
370 	test_validity_timing();
371 	sie_guest_destroy(&vm);
372 
373 done:
374 	report_prefix_pop();
375 	return report_summary();
376 }
377