Path: blob/master/tools/testing/selftests/kvm/arm64/sea_to_user.c
38237 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Test KVM returns to userspace with KVM_EXIT_ARM_SEA if host APEI fails3* to handle SEA and userspace has opt-ed in KVM_CAP_ARM_SEA_TO_USER.4*5* After reaching userspace with expected arm_sea info, also test userspace6* injecting a synchronous external data abort into the guest.7*8* This test utilizes EINJ to generate a REAL synchronous external data9* abort by consuming a recoverable uncorrectable memory error. Therefore10* the device under test must support EINJ in both firmware and host kernel,11* including the notrigger feature. Otherwise the test will be skipped.12* The under-test platform's APEI should be unable to claim SEA. Otherwise13* the test will also be skipped.14*/1516#include <signal.h>17#include <stdio.h>18#include <stdlib.h>19#include <unistd.h>2021#include "test_util.h"22#include "kvm_util.h"23#include "processor.h"24#include "guest_modes.h"2526#define PAGE_PRESENT (1ULL << 63)27#define PAGE_PHYSICAL 0x007fffffffffffffULL28#define PAGE_ADDR_MASK (~(0xfffULL))2930/* Group ISV and ISS[23:14]. */31#define ESR_ELx_INST_SYNDROME ((ESR_ELx_ISV) | (ESR_ELx_SAS) | \32(ESR_ELx_SSE) | (ESR_ELx_SRT_MASK) | \33(ESR_ELx_SF) | (ESR_ELx_AR))3435#define EINJ_ETYPE "/sys/kernel/debug/apei/einj/error_type"36#define EINJ_ADDR "/sys/kernel/debug/apei/einj/param1"37#define EINJ_MASK "/sys/kernel/debug/apei/einj/param2"38#define EINJ_FLAGS "/sys/kernel/debug/apei/einj/flags"39#define EINJ_NOTRIGGER "/sys/kernel/debug/apei/einj/notrigger"40#define EINJ_DOIT "/sys/kernel/debug/apei/einj/error_inject"41/* Memory Uncorrectable non-fatal. */42#define ERROR_TYPE_MEMORY_UER 0x1043/* Memory address and mask valid (param1 and param2). */44#define MASK_MEMORY_UER 0b104546/* Guest virtual address region = [2G, 3G). */47#define START_GVA 0x80000000UL48#define VM_MEM_SIZE 0x40000000UL49/* Note: EINJ_OFFSET must < VM_MEM_SIZE. */50#define EINJ_OFFSET 0x01234badUL51#define EINJ_GVA ((START_GVA) + (EINJ_OFFSET))5253static vm_paddr_t einj_gpa;54static void *einj_hva;55static uint64_t einj_hpa;56static bool far_invalid;5758static uint64_t translate_to_host_paddr(unsigned long vaddr)59{60uint64_t pinfo;61int64_t offset = vaddr / getpagesize() * sizeof(pinfo);62int fd;63uint64_t page_addr;64uint64_t paddr;6566fd = open("/proc/self/pagemap", O_RDONLY);67if (fd < 0)68ksft_exit_fail_perror("Failed to open /proc/self/pagemap");69if (pread(fd, &pinfo, sizeof(pinfo), offset) != sizeof(pinfo)) {70close(fd);71ksft_exit_fail_perror("Failed to read /proc/self/pagemap");72}7374close(fd);7576if ((pinfo & PAGE_PRESENT) == 0)77ksft_exit_fail_perror("Page not present");7879page_addr = (pinfo & PAGE_PHYSICAL) << MIN_PAGE_SHIFT;80paddr = page_addr + (vaddr & (getpagesize() - 1));81return paddr;82}8384static void write_einj_entry(const char *einj_path, uint64_t val)85{86char cmd[256] = {0};87FILE *cmdfile = NULL;8889sprintf(cmd, "echo %#lx > %s", val, einj_path);90cmdfile = popen(cmd, "r");9192if (pclose(cmdfile) == 0)93ksft_print_msg("echo %#lx > %s - done\n", val, einj_path);94else95ksft_exit_fail_perror("Failed to write EINJ entry");96}9798static void inject_uer(uint64_t paddr)99{100if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)101ksft_test_result_skip("EINJ table no available in firmware");102103if (access(EINJ_ETYPE, R_OK | W_OK) == -1)104ksft_test_result_skip("EINJ module probably not loaded?");105106write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);107write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);108write_einj_entry(EINJ_ADDR, paddr);109write_einj_entry(EINJ_MASK, ~0x0UL);110write_einj_entry(EINJ_NOTRIGGER, 1);111write_einj_entry(EINJ_DOIT, 1);112}113114/*115* When host APEI successfully claims the SEA caused by guest_code, kernel116* will send SIGBUS signal with BUS_MCEERR_AR to test thread.117*118* We set up this SIGBUS handler to skip the test for that case.119*/120static void sigbus_signal_handler(int sig, siginfo_t *si, void *v)121{122ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);123ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",124si->si_signo, si->si_errno, si->si_code, si->si_addr);125if (si->si_code == BUS_MCEERR_AR)126ksft_test_result_skip("SEA is claimed by host APEI\n");127else128ksft_test_result_fail("Exit with signal unhandled\n");129130exit(0);131}132133static void setup_sigbus_handler(void)134{135struct sigaction act;136137memset(&act, 0, sizeof(act));138sigemptyset(&act.sa_mask);139act.sa_sigaction = sigbus_signal_handler;140act.sa_flags = SA_SIGINFO;141TEST_ASSERT(sigaction(SIGBUS, &act, NULL) == 0,142"Failed to setup SIGBUS handler");143}144145static void guest_code(void)146{147uint64_t guest_data;148149/* Consumes error will cause a SEA. */150guest_data = *(uint64_t *)EINJ_GVA;151152GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",153EINJ_GVA, guest_data);154}155156static void expect_sea_handler(struct ex_regs *regs)157{158u64 esr = read_sysreg(esr_el1);159u64 far = read_sysreg(far_el1);160bool expect_far_invalid = far_invalid;161162GUEST_PRINTF("Handling Guest SEA\n");163GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);164165GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);166GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);167168if (expect_far_invalid) {169GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);170GUEST_PRINTF("Guest observed garbage value in FAR\n");171} else {172GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, 0);173GUEST_ASSERT_EQ(far, EINJ_GVA);174}175176GUEST_DONE();177}178179static void vcpu_inject_sea(struct kvm_vcpu *vcpu)180{181struct kvm_vcpu_events events = {};182183events.exception.ext_dabt_pending = true;184vcpu_events_set(vcpu, &events);185}186187static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)188{189struct ucall uc;190bool guest_done = false;191struct kvm_run *run = vcpu->run;192u64 esr;193194/* Resume the vCPU after error injection to consume the error. */195vcpu_run(vcpu);196197ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",198exit_reason_str(run->exit_reason));199ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",200run->arm_sea.esr, run->arm_sea.flags);201ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",202run->arm_sea.gva, run->arm_sea.gpa);203204TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);205206esr = run->arm_sea.esr;207TEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_LOW);208TEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);209TEST_ASSERT_EQ(ESR_ELx_ISS2(esr), 0);210TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);211TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);212213if (!(esr & ESR_ELx_FnV)) {214ksft_print_msg("Expect gva to match given FnV bit is 0\n");215TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);216}217218if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {219ksft_print_msg("Expect gpa to match given KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");220TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);221}222223far_invalid = esr & ESR_ELx_FnV;224225/* Inject a SEA into guest and expect handled in SEA handler. */226vcpu_inject_sea(vcpu);227228/* Expect the guest to reach GUEST_DONE gracefully. */229do {230vcpu_run(vcpu);231switch (get_ucall(vcpu, &uc)) {232case UCALL_PRINTF:233ksft_print_msg("From guest: %s", uc.buffer);234break;235case UCALL_DONE:236ksft_print_msg("Guest done gracefully!\n");237guest_done = 1;238break;239case UCALL_ABORT:240ksft_print_msg("Guest aborted!\n");241guest_done = 1;242REPORT_GUEST_ASSERT(uc);243break;244default:245TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);246}247} while (!guest_done);248}249250static struct kvm_vm *vm_create_with_sea_handler(struct kvm_vcpu **vcpu)251{252size_t backing_page_size;253size_t guest_page_size;254size_t alignment;255uint64_t num_guest_pages;256vm_paddr_t start_gpa;257enum vm_mem_backing_src_type src_type = VM_MEM_SRC_ANONYMOUS_HUGETLB_1GB;258struct kvm_vm *vm;259260backing_page_size = get_backing_src_pagesz(src_type);261guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;262alignment = max(backing_page_size, guest_page_size);263num_guest_pages = VM_MEM_SIZE / guest_page_size;264265vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);266vm_init_descriptor_tables(vm);267vcpu_init_descriptor_tables(*vcpu);268269vm_install_sync_handler(vm,270/*vector=*/VECTOR_SYNC_CURRENT,271/*ec=*/ESR_ELx_EC_DABT_CUR,272/*handler=*/expect_sea_handler);273274start_gpa = (vm->max_gfn - num_guest_pages) * guest_page_size;275start_gpa = align_down(start_gpa, alignment);276277vm_userspace_mem_region_add(278/*vm=*/vm,279/*src_type=*/src_type,280/*guest_paddr=*/start_gpa,281/*slot=*/1,282/*npages=*/num_guest_pages,283/*flags=*/0);284285virt_map(vm, START_GVA, start_gpa, num_guest_pages);286287ksft_print_msg("Mapped %#lx pages: gva=%#lx to gpa=%#lx\n",288num_guest_pages, START_GVA, start_gpa);289return vm;290}291292static void vm_inject_memory_uer(struct kvm_vm *vm)293{294uint64_t guest_data;295296einj_gpa = addr_gva2gpa(vm, EINJ_GVA);297einj_hva = addr_gva2hva(vm, EINJ_GVA);298299/* Populate certain data before injecting UER. */300*(uint64_t *)einj_hva = 0xBAADCAFE;301guest_data = *(uint64_t *)einj_hva;302ksft_print_msg("Before EINJect: data=%#lx\n",303guest_data);304305einj_hpa = translate_to_host_paddr((unsigned long)einj_hva);306307ksft_print_msg("EINJ_GVA=%#lx, einj_gpa=%#lx, einj_hva=%p, einj_hpa=%#lx\n",308EINJ_GVA, einj_gpa, einj_hva, einj_hpa);309310inject_uer(einj_hpa);311ksft_print_msg("Memory UER EINJected\n");312}313314int main(int argc, char *argv[])315{316struct kvm_vm *vm;317struct kvm_vcpu *vcpu;318319TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SEA_TO_USER));320321setup_sigbus_handler();322323vm = vm_create_with_sea_handler(&vcpu);324vm_enable_cap(vm, KVM_CAP_ARM_SEA_TO_USER, 0);325vm_inject_memory_uer(vm);326run_vm(vm, vcpu);327kvm_vm_free(vm);328329return 0;330}331332333