/* SPDX-License-Identifier: GPL-2.0-only */ /* * PV virtualization interception tests for intercepts that are not * caused by an instruction. * * Copyright (c) 2023 IBM Corp * * Authors: * Janosch Frank */ #include #include #include #include #include #include #include #include #include #include #include static struct vm vm, vm2; /* * The hypervisor should not be able to decrease the cpu timer by an * amount that is higher than the amount of time spent outside of * SIE. * * Warning: A lot of things influence time so decreasing the timer by * a more significant amount than the difference to have a safety * margin is advised. */ static void test_validity_timing(void) { extern const char SNIPPET_NAME_START(asm, pv_icpt_vir_timing)[]; extern const char SNIPPET_NAME_END(asm, pv_icpt_vir_timing)[]; extern const char SNIPPET_HDR_START(asm, pv_icpt_vir_timing)[]; extern const char SNIPPET_HDR_END(asm, pv_icpt_vir_timing)[]; int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_vir_timing); int size_gbin = SNIPPET_LEN(asm, pv_icpt_vir_timing); uint64_t time_exit, time_entry, tmp; report_prefix_push("manipulated cpu time"); snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_vir_timing), SNIPPET_HDR_START(asm, pv_icpt_vir_timing), size_gbin, size_hdr, SNIPPET_UNPACK_OFF); sie(&vm); report(sie_is_diag_icpt(&vm, 0x44), "spt done"); stck(&time_exit); tmp = vm.sblk->cputm; mb(); /* Cpu timer counts down so adding a ms should lead to a validity */ vm.sblk->cputm += S390_CLOCK_SHIFT_US * 1000; sie_expect_validity(&vm); sie(&vm); report(uv_validity_check(&vm), "validity entry cput > exit cput"); vm.sblk->cputm = tmp; /* * We are not allowed to decrement the timer more than the * time spent outside of SIE */ stck(&time_entry); vm.sblk->cputm -= (time_entry - time_exit) + S390_CLOCK_SHIFT_US * 1000; sie_expect_validity(&vm); sie(&vm); report(uv_validity_check(&vm), "validity entry cput < time spent outside SIE"); vm.sblk->cputm = tmp; uv_destroy_guest(&vm); report_prefix_pop(); } static void run_loop(void) { sie(&vm); sigp_retry(stap(), SIGP_STOP, 0, NULL); } static void test_validity_already_running(void) { extern const char SNIPPET_NAME_START(asm, loop)[]; extern const char SNIPPET_NAME_END(asm, loop)[]; extern const char SNIPPET_HDR_START(asm, loop)[]; extern const char SNIPPET_HDR_END(asm, loop)[]; int size_hdr = SNIPPET_HDR_LEN(asm, loop); int size_gbin = SNIPPET_LEN(asm, loop); struct psw psw = { .mask = PSW_MASK_64, .addr = (uint64_t)run_loop, }; report_prefix_push("already running"); if (smp_query_num_cpus() < 3) { report_skip("need at least 3 cpus for this test"); goto out; } snippet_pv_init(&vm, SNIPPET_NAME_START(asm, loop), SNIPPET_HDR_START(asm, loop), size_gbin, size_hdr, SNIPPET_UNPACK_OFF); smp_cpu_setup(1, psw); sie_expect_validity(&vm); smp_cpu_setup(2, psw); while (vm.sblk->icptcode != ICPT_VALIDITY) { mb(); } /* * One cpu will enter SIE and one will receive the validity. * We rely on the expectation that the cpu in SIE won't exit * until we had a chance to observe the validity as the exit * would overwrite the validity. * * In general that expectation is valid but HW/FW can in * theory still exit to handle their interrupts. */ report(uv_validity_check(&vm), "validity"); smp_cpu_stop(1); smp_cpu_stop(2); uv_destroy_guest(&vm); out: report_prefix_pop(); } /* Tests if a vcpu handle from another configuration results in a validity intercept. */ static void test_validity_handle_not_in_config(void) { extern const char SNIPPET_NAME_START(asm, icpt_loop)[]; extern const char SNIPPET_NAME_END(asm, icpt_loop)[]; extern const char SNIPPET_HDR_START(asm, icpt_loop)[]; extern const char SNIPPET_HDR_END(asm, icpt_loop)[]; int size_hdr = SNIPPET_HDR_LEN(asm, icpt_loop); int size_gbin = SNIPPET_LEN(asm, icpt_loop); report_prefix_push("handle not in config"); /* Setup our primary vm */ snippet_pv_init(&vm, SNIPPET_NAME_START(asm, icpt_loop), SNIPPET_HDR_START(asm, icpt_loop), size_gbin, size_hdr, SNIPPET_UNPACK_OFF); /* Setup secondary vm */ snippet_setup_guest(&vm2, true); snippet_pv_init(&vm2, SNIPPET_NAME_START(asm, icpt_loop), SNIPPET_HDR_START(asm, icpt_loop), size_gbin, size_hdr, SNIPPET_UNPACK_OFF); vm.sblk->pv_handle_cpu = vm2.sblk->pv_handle_cpu; sie_expect_validity(&vm); sie(&vm); report(uv_validity_check(&vm), "switched cpu handle"); vm.sblk->pv_handle_cpu = vm.uv.vcpu_handle; vm.sblk->pv_handle_config = vm2.uv.vm_handle; sie_expect_validity(&vm); sie(&vm); report(uv_validity_check(&vm), "switched configuration handle"); vm.sblk->pv_handle_config = vm.uv.vm_handle; /* Destroy the second vm, since we don't need it for further tests */ uv_destroy_guest(&vm2); sie_guest_destroy(&vm2); uv_destroy_guest(&vm); report_prefix_pop(); } /* Tests if a wrong vm or vcpu handle results in a validity intercept. */ static void test_validity_seid(void) { extern const char SNIPPET_NAME_START(asm, icpt_loop)[]; extern const char SNIPPET_NAME_END(asm, icpt_loop)[]; extern const char SNIPPET_HDR_START(asm, icpt_loop)[]; extern const char SNIPPET_HDR_END(asm, icpt_loop)[]; int size_hdr = SNIPPET_HDR_LEN(asm, icpt_loop); int size_gbin = SNIPPET_LEN(asm, icpt_loop); int fails = 0; int i; report_prefix_push("handles"); snippet_pv_init(&vm, SNIPPET_NAME_START(asm, icpt_loop), SNIPPET_HDR_START(asm, icpt_loop), size_gbin, size_hdr, SNIPPET_UNPACK_OFF); for (i = 0; i < 64; i++) { vm.sblk->pv_handle_config ^= 1UL << i; sie_expect_validity(&vm); sie(&vm); if (!uv_validity_check(&vm)) { report_fail("SIE accepted wrong VM SEID, changed bit %d", 63 - i); fails++; } vm.sblk->pv_handle_config ^= 1UL << i; } report(!fails, "No wrong vm handle accepted"); fails = 0; for (i = 0; i < 64; i++) { vm.sblk->pv_handle_cpu ^= 1UL << i; sie_expect_validity(&vm); sie(&vm); if (!uv_validity_check(&vm)) { report_fail("SIE accepted wrong CPU SEID, changed bit %d", 63 - i); fails++; } vm.sblk->pv_handle_cpu ^= 1UL << i; } report(!fails, "No wrong cpu handle accepted"); uv_destroy_guest(&vm); report_prefix_pop(); } /* * Tests if we get a validity intercept if the CR1 asce at SIE entry * is not the same as the one given at the UV creation of the VM. */ static void test_validity_asce(void) { extern const char SNIPPET_NAME_START(asm, pv_icpt_112)[]; extern const char SNIPPET_NAME_END(asm, pv_icpt_112)[]; extern const char SNIPPET_HDR_START(asm, pv_icpt_112)[]; extern const char SNIPPET_HDR_END(asm, pv_icpt_112)[]; int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_112); int size_gbin = SNIPPET_LEN(asm, pv_icpt_112); uint64_t asce_old, asce_new; void *pgd_new, *pgd_old; report_prefix_push("asce"); snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_112), SNIPPET_HDR_START(asm, pv_icpt_112), size_gbin, size_hdr, SNIPPET_UNPACK_OFF); asce_old = vm.save_area.guest.asce; pgd_new = memalign_pages_flags(PAGE_SIZE, PAGE_SIZE * 4, 0); pgd_old = (void *)(asce_old & PAGE_MASK); /* Copy the contents of the top most table */ memcpy(pgd_new, pgd_old, PAGE_SIZE * 4); /* Create the replacement ASCE */ asce_new = __pa(pgd_new) | ASCE_DT_REGION1 | REGION_TABLE_LENGTH | ASCE_P; vm.save_area.guest.asce = asce_new; sie_expect_validity(&vm); sie(&vm); report(uv_validity_check(&vm), "wrong CR1 validity"); /* Restore the old ASCE */ vm.save_area.guest.asce = asce_old; /* Try if we can still do an entry with the correct asce */ sie(&vm); report(sie_is_diag_icpt(&vm, 0x44), "re-entry with valid CR1"); uv_destroy_guest(&vm); free_pages(pgd_new); report_prefix_pop(); } static void run_icpt_122_tests(unsigned long lc_off) { uv_export(vm.sblk->mso + lc_off); sie(&vm); report(vm.sblk->icptcode == ICPT_PV_PREF, "Intercept 112 for page 0"); uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off); uv_export(vm.sblk->mso + lc_off + PAGE_SIZE); sie(&vm); report(vm.sblk->icptcode == ICPT_PV_PREF, "Intercept 112 for page 1"); uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off + PAGE_SIZE); } static void run_icpt_122_tests_prefix(unsigned long prefix) { uint32_t *ptr = 0; report_prefix_pushf("0x%lx", prefix); report_prefix_push("unshared"); run_icpt_122_tests(prefix); report_prefix_pop(); /* * Guest will share the lowcore and we need to check if that * makes a difference (which it should not). */ report_prefix_push("shared"); sie(&vm); /* Guest indicates that it has been setup via the diag 0x44 */ assert(sie_is_diag_icpt(&vm, 0x44)); /* If the pages have not been shared these writes will cause exceptions */ ptr = (uint32_t *)prefix; WRITE_ONCE(ptr, 0); ptr = (uint32_t *)(prefix + offsetof(struct lowcore, ars_sa[0])); WRITE_ONCE(ptr, 0); run_icpt_122_tests(prefix); /* shared*/ report_prefix_pop(); /* prefix hex value */ report_prefix_pop(); } static void test_icpt_112(void) { extern const char SNIPPET_NAME_START(asm, pv_icpt_112)[]; extern const char SNIPPET_NAME_END(asm, pv_icpt_112)[]; extern const char SNIPPET_HDR_START(asm, pv_icpt_112)[]; extern const char SNIPPET_HDR_END(asm, pv_icpt_112)[]; int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_112); int size_gbin = SNIPPET_LEN(asm, pv_icpt_112); unsigned long lc_off = 0; report_prefix_push("prefix"); snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_112), SNIPPET_HDR_START(asm, pv_icpt_112), size_gbin, size_hdr, SNIPPET_UNPACK_OFF); /* Setup of the guest's state for 0x0 prefix */ sie(&vm); assert(sie_is_diag_icpt(&vm, 0x44)); /* Test on standard 0x0 prefix */ run_icpt_122_tests_prefix(0); /* Setup of the guest's state for 0x8000 prefix */ lc_off = 0x8000; uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off); uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off + PAGE_SIZE); /* Guest will set prefix to 0x8000 */ sie(&vm); /* SPX generates a PV instruction notification */ assert(vm.sblk->icptcode == ICPT_PV_NOTIFY && vm.sblk->ipa == 0xb210); assert(*(u32 *)vm.sblk->sidad == 0x8000); /* Test on 0x8000 prefix */ run_icpt_122_tests_prefix(0x8000); /* Try a re-entry after everything has been imported again */ sie(&vm); report(sie_is_diag_icpt(&vm, 0x9c) && vm.save_area.guest.grs[0] == 42, "re-entry successful"); report_prefix_pop(); uv_destroy_guest(&vm); } int main(void) { report_prefix_push("pv-icpts"); if (!uv_host_requirement_checks()) goto done; snippet_setup_guest(&vm, true); test_icpt_112(); test_validity_asce(); test_validity_seid(); test_validity_handle_not_in_config(); test_validity_already_running(); test_validity_timing(); sie_guest_destroy(&vm); done: report_prefix_pop(); return report_summary(); }