| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Test KVM returns to userspace with KVM_EXIT_ARM_SEA if host APEI fails
- * to handle SEA and userspace has opt-ed in KVM_CAP_ARM_SEA_TO_USER.
- *
- * After reaching userspace with expected arm_sea info, also test userspace
- * injecting a synchronous external data abort into the guest.
- *
- * This test utilizes EINJ to generate a REAL synchronous external data
- * abort by consuming a recoverable uncorrectable memory error. Therefore
- * the device under test must support EINJ in both firmware and host kernel,
- * including the notrigger feature. Otherwise the test will be skipped.
- * The under-test platform's APEI should be unable to claim SEA. Otherwise
- * the test will also be skipped.
- */
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include "test_util.h"
- #include "kvm_util.h"
- #include "processor.h"
- #include "guest_modes.h"
- #define PAGE_PRESENT (1ULL << 63)
- #define PAGE_PHYSICAL 0x007fffffffffffffULL
- #define PAGE_ADDR_MASK (~(0xfffULL))
- /* Group ISV and ISS[23:14]. */
- #define ESR_ELx_INST_SYNDROME ((ESR_ELx_ISV) | (ESR_ELx_SAS) | \
- (ESR_ELx_SSE) | (ESR_ELx_SRT_MASK) | \
- (ESR_ELx_SF) | (ESR_ELx_AR))
- #define EINJ_ETYPE "/sys/kernel/debug/apei/einj/error_type"
- #define EINJ_ADDR "/sys/kernel/debug/apei/einj/param1"
- #define EINJ_MASK "/sys/kernel/debug/apei/einj/param2"
- #define EINJ_FLAGS "/sys/kernel/debug/apei/einj/flags"
- #define EINJ_NOTRIGGER "/sys/kernel/debug/apei/einj/notrigger"
- #define EINJ_DOIT "/sys/kernel/debug/apei/einj/error_inject"
- /* Memory Uncorrectable non-fatal. */
- #define ERROR_TYPE_MEMORY_UER 0x10
- /* Memory address and mask valid (param1 and param2). */
- #define MASK_MEMORY_UER 0b10
- /* Guest virtual address region = [2G, 3G). */
- #define START_GVA 0x80000000UL
- #define VM_MEM_SIZE 0x40000000UL
- /* Note: EINJ_OFFSET must < VM_MEM_SIZE. */
- #define EINJ_OFFSET 0x01234badUL
- #define EINJ_GVA ((START_GVA) + (EINJ_OFFSET))
- static vm_paddr_t einj_gpa;
- static void *einj_hva;
- static uint64_t einj_hpa;
- static bool far_invalid;
- static uint64_t translate_to_host_paddr(unsigned long vaddr)
- {
- uint64_t pinfo;
- int64_t offset = vaddr / getpagesize() * sizeof(pinfo);
- int fd;
- uint64_t page_addr;
- uint64_t paddr;
- fd = open("/proc/self/pagemap", O_RDONLY);
- if (fd < 0)
- ksft_exit_fail_perror("Failed to open /proc/self/pagemap");
- if (pread(fd, &pinfo, sizeof(pinfo), offset) != sizeof(pinfo)) {
- close(fd);
- ksft_exit_fail_perror("Failed to read /proc/self/pagemap");
- }
- close(fd);
- if ((pinfo & PAGE_PRESENT) == 0)
- ksft_exit_fail_perror("Page not present");
- page_addr = (pinfo & PAGE_PHYSICAL) << MIN_PAGE_SHIFT;
- paddr = page_addr + (vaddr & (getpagesize() - 1));
- return paddr;
- }
- static void write_einj_entry(const char *einj_path, uint64_t val)
- {
- char cmd[256] = {0};
- FILE *cmdfile = NULL;
- sprintf(cmd, "echo %#lx > %s", val, einj_path);
- cmdfile = popen(cmd, "r");
- if (pclose(cmdfile) == 0)
- ksft_print_msg("echo %#lx > %s - done\n", val, einj_path);
- else
- ksft_exit_fail_perror("Failed to write EINJ entry");
- }
- static void inject_uer(uint64_t paddr)
- {
- if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
- ksft_test_result_skip("EINJ table no available in firmware");
- if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
- ksft_test_result_skip("EINJ module probably not loaded?");
- write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
- write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
- write_einj_entry(EINJ_ADDR, paddr);
- write_einj_entry(EINJ_MASK, ~0x0UL);
- write_einj_entry(EINJ_NOTRIGGER, 1);
- write_einj_entry(EINJ_DOIT, 1);
- }
- /*
- * When host APEI successfully claims the SEA caused by guest_code, kernel
- * will send SIGBUS signal with BUS_MCEERR_AR to test thread.
- *
- * We set up this SIGBUS handler to skip the test for that case.
- */
- static void sigbus_signal_handler(int sig, siginfo_t *si, void *v)
- {
- ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);
- ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",
- si->si_signo, si->si_errno, si->si_code, si->si_addr);
- if (si->si_code == BUS_MCEERR_AR)
- ksft_test_result_skip("SEA is claimed by host APEI\n");
- else
- ksft_test_result_fail("Exit with signal unhandled\n");
- exit(0);
- }
- static void setup_sigbus_handler(void)
- {
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- sigemptyset(&act.sa_mask);
- act.sa_sigaction = sigbus_signal_handler;
- act.sa_flags = SA_SIGINFO;
- TEST_ASSERT(sigaction(SIGBUS, &act, NULL) == 0,
- "Failed to setup SIGBUS handler");
- }
- static void guest_code(void)
- {
- uint64_t guest_data;
- /* Consumes error will cause a SEA. */
- guest_data = *(uint64_t *)EINJ_GVA;
- GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",
- EINJ_GVA, guest_data);
- }
- static void expect_sea_handler(struct ex_regs *regs)
- {
- u64 esr = read_sysreg(esr_el1);
- u64 far = read_sysreg(far_el1);
- bool expect_far_invalid = far_invalid;
- GUEST_PRINTF("Handling Guest SEA\n");
- GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);
- GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
- GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
- if (expect_far_invalid) {
- GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);
- GUEST_PRINTF("Guest observed garbage value in FAR\n");
- } else {
- GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, 0);
- GUEST_ASSERT_EQ(far, EINJ_GVA);
- }
- GUEST_DONE();
- }
- 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 void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
- {
- struct ucall uc;
- bool guest_done = false;
- struct kvm_run *run = vcpu->run;
- u64 esr;
- /* Resume the vCPU after error injection to consume the error. */
- vcpu_run(vcpu);
- ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
- exit_reason_str(run->exit_reason));
- ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
- run->arm_sea.esr, run->arm_sea.flags);
- ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
- run->arm_sea.gva, run->arm_sea.gpa);
- TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
- esr = run->arm_sea.esr;
- TEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_LOW);
- TEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
- TEST_ASSERT_EQ(ESR_ELx_ISS2(esr), 0);
- TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);
- TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);
- if (!(esr & ESR_ELx_FnV)) {
- ksft_print_msg("Expect gva to match given FnV bit is 0\n");
- TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);
- }
- if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
- ksft_print_msg("Expect gpa to match given KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");
- TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);
- }
- far_invalid = esr & ESR_ELx_FnV;
- /* Inject a SEA into guest and expect handled in SEA handler. */
- vcpu_inject_sea(vcpu);
- /* Expect the guest to reach GUEST_DONE gracefully. */
- do {
- vcpu_run(vcpu);
- switch (get_ucall(vcpu, &uc)) {
- case UCALL_PRINTF:
- ksft_print_msg("From guest: %s", uc.buffer);
- break;
- case UCALL_DONE:
- ksft_print_msg("Guest done gracefully!\n");
- guest_done = 1;
- break;
- case UCALL_ABORT:
- ksft_print_msg("Guest aborted!\n");
- guest_done = 1;
- REPORT_GUEST_ASSERT(uc);
- break;
- default:
- TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
- }
- } while (!guest_done);
- }
- static struct kvm_vm *vm_create_with_sea_handler(struct kvm_vcpu **vcpu)
- {
- size_t backing_page_size;
- size_t guest_page_size;
- size_t alignment;
- uint64_t num_guest_pages;
- vm_paddr_t start_gpa;
- enum vm_mem_backing_src_type src_type = VM_MEM_SRC_ANONYMOUS_HUGETLB_1GB;
- struct kvm_vm *vm;
- backing_page_size = get_backing_src_pagesz(src_type);
- guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;
- alignment = max(backing_page_size, guest_page_size);
- num_guest_pages = VM_MEM_SIZE / guest_page_size;
- vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);
- vm_init_descriptor_tables(vm);
- vcpu_init_descriptor_tables(*vcpu);
- vm_install_sync_handler(vm,
- /*vector=*/VECTOR_SYNC_CURRENT,
- /*ec=*/ESR_ELx_EC_DABT_CUR,
- /*handler=*/expect_sea_handler);
- start_gpa = (vm->max_gfn - num_guest_pages) * guest_page_size;
- start_gpa = align_down(start_gpa, alignment);
- vm_userspace_mem_region_add(
- /*vm=*/vm,
- /*src_type=*/src_type,
- /*guest_paddr=*/start_gpa,
- /*slot=*/1,
- /*npages=*/num_guest_pages,
- /*flags=*/0);
- virt_map(vm, START_GVA, start_gpa, num_guest_pages);
- ksft_print_msg("Mapped %#lx pages: gva=%#lx to gpa=%#lx\n",
- num_guest_pages, START_GVA, start_gpa);
- return vm;
- }
- static void vm_inject_memory_uer(struct kvm_vm *vm)
- {
- uint64_t guest_data;
- einj_gpa = addr_gva2gpa(vm, EINJ_GVA);
- einj_hva = addr_gva2hva(vm, EINJ_GVA);
- /* Populate certain data before injecting UER. */
- *(uint64_t *)einj_hva = 0xBAADCAFE;
- guest_data = *(uint64_t *)einj_hva;
- ksft_print_msg("Before EINJect: data=%#lx\n",
- guest_data);
- einj_hpa = translate_to_host_paddr((unsigned long)einj_hva);
- ksft_print_msg("EINJ_GVA=%#lx, einj_gpa=%#lx, einj_hva=%p, einj_hpa=%#lx\n",
- EINJ_GVA, einj_gpa, einj_hva, einj_hpa);
- inject_uer(einj_hpa);
- ksft_print_msg("Memory UER EINJected\n");
- }
- int main(int argc, char *argv[])
- {
- struct kvm_vm *vm;
- struct kvm_vcpu *vcpu;
- TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SEA_TO_USER));
- setup_sigbus_handler();
- vm = vm_create_with_sea_handler(&vcpu);
- vm_enable_cap(vm, KVM_CAP_ARM_SEA_TO_USER, 0);
- vm_inject_memory_uer(vm);
- run_vm(vm, vcpu);
- kvm_vm_free(vm);
- return 0;
- }
|