| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- // 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 expect_sea_s1ptw_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), ESR_ELx_FSC_SEA_TTW(3));
- GUEST_DONE();
- }
- static noinline void test_s1ptw_abort_guest(void)
- {
- extern char test_s1ptw_abort_insn;
- WRITE_ONCE(expected_abort_pc, (u64)&test_s1ptw_abort_insn);
- asm volatile("test_s1ptw_abort_insn:\n\t"
- "ldr x0, [%0]\n\t"
- : : "r" (MMIO_ADDR) : "x0", "memory");
- GUEST_FAIL("Load on S1PTW abort should not retire");
- }
- static void test_s1ptw_abort(void)
- {
- struct kvm_vcpu *vcpu;
- u64 *ptep, bad_pa;
- struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_s1ptw_abort_guest,
- expect_sea_s1ptw_handler);
- ptep = virt_get_pte_hva_at_level(vm, MMIO_ADDR, 2);
- bad_pa = BIT(vm->pa_bits) - vm->page_size;
- *ptep &= ~GENMASK(47, 12);
- *ptep |= bad_pa;
- 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);
- }
- static void test_serror_amo_guest(void)
- {
- /*
- * The ISB is entirely unnecessary (and highlights how FEAT_NV2 is borked)
- * since the write is redirected to memory. But don't write (intentionally)
- * broken code!
- */
- sysreg_clear_set(hcr_el2, HCR_EL2_AMO | HCR_EL2_TGE, 0);
- isb();
- GUEST_SYNC(0);
- GUEST_ASSERT(read_sysreg(isr_el1) & ISR_EL1_A);
- /*
- * KVM treats the effective value of AMO as 1 when
- * HCR_EL2.{E2H,TGE} = {1, 0}, meaning the SError will be taken when
- * unmasked.
- */
- local_serror_enable();
- isb();
- local_serror_disable();
- GUEST_FAIL("Should've taken pending SError exception");
- }
- static void test_serror_amo(void)
- {
- struct kvm_vcpu *vcpu;
- struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_amo_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);
- }
- int main(void)
- {
- test_mmio_abort();
- test_mmio_nisv();
- test_mmio_nisv_abort();
- test_serror();
- test_serror_masked();
- test_serror_emulated();
- test_mmio_ease();
- test_s1ptw_abort();
- if (!test_supports_el2())
- return 0;
- test_serror_amo();
- }
|