| /* 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 <frankja@linux.ibm.com> | |
| */ | |
| #include <libcflat.h> | |
| #include <sie.h> | |
| #include <smp.h> | |
| #include <sclp.h> | |
| #include <snippet.h> | |
| #include <sie-icpt.h> | |
| #include <asm/facility.h> | |
| #include <asm/barrier.h> | |
| #include <asm/sigp.h> | |
| #include <asm/uv.h> | |
| #include <asm/time.h> | |
| 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(); | |
| } |