// SPDX-License-Identifier: GPL-2.0-only /* * external_abort - Tests for userspace external abort injection * * Copyright (c) 2024 Google LLC */ #include "processor.h" #include "test_util.h" #define MMIO_ADDR 0x8000000ULL #define EXPECTED_SERROR_ISS (ESR_ELx_ISV | 0x1d1ed) static u64 expected_abort_pc; static void expect_sea_handler(struct ex_regs *regs) { u64 esr = read_sysreg(esr_el1); GUEST_ASSERT_EQ(regs->pc, expected_abort_pc); GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR); GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT); GUEST_DONE(); } static void unexpected_dabt_handler(struct ex_regs *regs) { GUEST_FAIL("Unexpected data abort at PC: %lx\n", regs->pc); } static struct kvm_vm *vm_create_with_dabt_handler(struct kvm_vcpu **vcpu, void *guest_code, handler_fn dabt_handler) { struct kvm_vm *vm = vm_create_with_one_vcpu(vcpu, guest_code); vm_init_descriptor_tables(vm); vcpu_init_descriptor_tables(*vcpu); vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ESR_ELx_EC_DABT_CUR, dabt_handler); virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1); return vm; } static void vcpu_inject_sea(struct kvm_vcpu *vcpu) { struct kvm_vcpu_events events = {}; events.exception.ext_dabt_pending = true; vcpu_events_set(vcpu, &events); } static bool vcpu_has_ras(struct kvm_vcpu *vcpu) { u64 pfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1)); return SYS_FIELD_GET(ID_AA64PFR0_EL1, RAS, pfr0); } static bool guest_has_ras(void) { return SYS_FIELD_GET(ID_AA64PFR0_EL1, RAS, read_sysreg(id_aa64pfr0_el1)); } static void vcpu_inject_serror(struct kvm_vcpu *vcpu) { struct kvm_vcpu_events events = {}; events.exception.serror_pending = true; if (vcpu_has_ras(vcpu)) { events.exception.serror_has_esr = true; events.exception.serror_esr = EXPECTED_SERROR_ISS; } vcpu_events_set(vcpu, &events); } static void __vcpu_run_expect(struct kvm_vcpu *vcpu, unsigned int cmd) { struct ucall uc; vcpu_run(vcpu); switch (get_ucall(vcpu, &uc)) { case UCALL_ABORT: REPORT_GUEST_ASSERT(uc); break; default: if (uc.cmd == cmd) return; TEST_FAIL("Unexpected ucall: %lu", uc.cmd); } } static void vcpu_run_expect_done(struct kvm_vcpu *vcpu) { __vcpu_run_expect(vcpu, UCALL_DONE); } static void vcpu_run_expect_sync(struct kvm_vcpu *vcpu) { __vcpu_run_expect(vcpu, UCALL_SYNC); } extern char test_mmio_abort_insn; static noinline void test_mmio_abort_guest(void) { WRITE_ONCE(expected_abort_pc, (u64)&test_mmio_abort_insn); asm volatile("test_mmio_abort_insn:\n\t" "ldr x0, [%0]\n\t" : : "r" (MMIO_ADDR) : "x0", "memory"); GUEST_FAIL("MMIO instruction should not retire"); } /* * Test that KVM doesn't complete MMIO emulation when userspace has made an * external abort pending for the instruction. */ static void test_mmio_abort(void) { struct kvm_vcpu *vcpu; struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_abort_guest, expect_sea_handler); struct kvm_run *run = vcpu->run; vcpu_run(vcpu); TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR); TEST_ASSERT_EQ(run->mmio.len, sizeof(unsigned long)); TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read"); vcpu_inject_sea(vcpu); vcpu_run_expect_done(vcpu); kvm_vm_free(vm); } extern char test_mmio_nisv_insn; static void test_mmio_nisv_guest(void) { WRITE_ONCE(expected_abort_pc, (u64)&test_mmio_nisv_insn); asm volatile("test_mmio_nisv_insn:\n\t" "ldr x0, [%0], #8\n\t" : : "r" (MMIO_ADDR) : "x0", "memory"); GUEST_FAIL("MMIO instruction should not retire"); } /* * Test that the KVM_RUN ioctl fails for ESR_EL2.ISV=0 MMIO aborts if userspace * hasn't enabled KVM_CAP_ARM_NISV_TO_USER. */ static void test_mmio_nisv(void) { struct kvm_vcpu *vcpu; struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_nisv_guest, unexpected_dabt_handler); TEST_ASSERT(_vcpu_run(vcpu), "Expected nonzero return code from KVM_RUN"); TEST_ASSERT_EQ(errno, ENOSYS); kvm_vm_free(vm); } /* * Test that ESR_EL2.ISV=0 MMIO aborts reach userspace and that an injected SEA * reaches the guest. */ static void test_mmio_nisv_abort(void) { struct kvm_vcpu *vcpu; struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_nisv_guest, expect_sea_handler); struct kvm_run *run = vcpu->run; vm_enable_cap(vm, KVM_CAP_ARM_NISV_TO_USER, 1); vcpu_run(vcpu); TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_NISV); TEST_ASSERT_EQ(run->arm_nisv.fault_ipa, MMIO_ADDR); vcpu_inject_sea(vcpu); vcpu_run_expect_done(vcpu); kvm_vm_free(vm); } static void unexpected_serror_handler(struct ex_regs *regs) { GUEST_FAIL("Took unexpected SError exception"); } static void test_serror_masked_guest(void) { GUEST_ASSERT(read_sysreg(isr_el1) & ISR_EL1_A); isb(); GUEST_DONE(); } static void test_serror_masked(void) { struct kvm_vcpu *vcpu; struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_masked_guest, unexpected_dabt_handler); vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, unexpected_serror_handler); vcpu_inject_serror(vcpu); vcpu_run_expect_done(vcpu); kvm_vm_free(vm); } static void expect_serror_handler(struct ex_regs *regs) { u64 esr = read_sysreg(esr_el1); GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_SERROR); if (guest_has_ras()) GUEST_ASSERT_EQ(ESR_ELx_ISS(esr), EXPECTED_SERROR_ISS); GUEST_DONE(); } static void test_serror_guest(void) { GUEST_ASSERT(read_sysreg(isr_el1) & ISR_EL1_A); local_serror_enable(); isb(); local_serror_disable(); GUEST_FAIL("Should've taken pending SError exception"); } static void test_serror(void) { struct kvm_vcpu *vcpu; struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_guest, unexpected_dabt_handler); vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, expect_serror_handler); vcpu_inject_serror(vcpu); vcpu_run_expect_done(vcpu); kvm_vm_free(vm); } static void test_serror_emulated_guest(void) { GUEST_ASSERT(!(read_sysreg(isr_el1) & ISR_EL1_A)); local_serror_enable(); GUEST_SYNC(0); local_serror_disable(); GUEST_FAIL("Should've taken unmasked SError exception"); } static void test_serror_emulated(void) { struct kvm_vcpu *vcpu; struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_emulated_guest, unexpected_dabt_handler); vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, expect_serror_handler); vcpu_run_expect_sync(vcpu); vcpu_inject_serror(vcpu); vcpu_run_expect_done(vcpu); kvm_vm_free(vm); } static void test_mmio_ease_guest(void) { sysreg_clear_set_s(SYS_SCTLR2_EL1, 0, SCTLR2_EL1_EASE); isb(); test_mmio_abort_guest(); } /* * Test that KVM doesn't complete MMIO emulation when userspace has made an * external abort pending for the instruction. */ static void test_mmio_ease(void) { struct kvm_vcpu *vcpu; struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_ease_guest, unexpected_dabt_handler); struct kvm_run *run = vcpu->run; u64 pfr1; pfr1 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1)); if (!SYS_FIELD_GET(ID_AA64PFR1_EL1, DF2, pfr1)) { pr_debug("Skipping %s\n", __func__); return; } /* * SCTLR2_ELx.EASE changes the exception vector to the SError vector but * doesn't further modify the exception context (e.g. ESR_ELx, FAR_ELx). */ vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, expect_sea_handler); vcpu_run(vcpu); TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR); TEST_ASSERT_EQ(run->mmio.len, sizeof(unsigned long)); TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read"); vcpu_inject_sea(vcpu); vcpu_run_expect_done(vcpu); kvm_vm_free(vm); } int main(void) { test_mmio_abort(); test_mmio_nisv(); test_mmio_nisv_abort(); test_serror(); test_serror_masked(); test_serror_emulated(); test_mmio_ease(); }