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