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 <pv_icptdata.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 */ 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(pv_icptdata_check_diag(&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 77 static void run_loop(void) 78 { 79 sie(&vm); 80 sigp_retry(stap(), SIGP_STOP, 0, NULL); 81 } 82 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. */ 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. */ 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 */ 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(pv_icptdata_check_diag(&vm, 0x44), "re-entry with valid CR1"); 262 uv_destroy_guest(&vm); 263 free_pages(pgd_new); 264 report_prefix_pop(); 265 } 266 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 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(pv_icptdata_check_diag(&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 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(pv_icptdata_check_diag(&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(pv_icptdata_check_diag(&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 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