mirror of
https://github.com/torvalds/linux.git
synced 2026-05-27 16:44:58 +02:00
Merge branch 'kvm-arm64/sea-user' into kvmarm/next
* kvm-arm64/sea-user: : Userspace handling of SEAs, courtesy of Jiaqi Yan : : Add support for processing external aborts in userspace in situations : where the host has failed to do so, allowing the VMM to potentially : reinject an external abort into the VM. Documentation: kvm: new UAPI for handling SEA KVM: selftests: Test for KVM_EXIT_ARM_SEA KVM: arm64: VM exit to userspace to handle SEA Signed-off-by: Oliver Upton <oupton@kernel.org>
This commit is contained in:
commit
11b8e6edc1
|
|
@ -7286,6 +7286,41 @@ exit, even without calls to ``KVM_ENABLE_CAP`` or similar. In this case,
|
|||
it will enter with output fields already valid; in the common case, the
|
||||
``unknown.ret`` field of the union will be ``TDVMCALL_STATUS_SUBFUNC_UNSUPPORTED``.
|
||||
Userspace need not do anything if it does not wish to support a TDVMCALL.
|
||||
|
||||
::
|
||||
|
||||
/* KVM_EXIT_ARM_SEA */
|
||||
struct {
|
||||
#define KVM_EXIT_ARM_SEA_FLAG_GPA_VALID (1ULL << 0)
|
||||
__u64 flags;
|
||||
__u64 esr;
|
||||
__u64 gva;
|
||||
__u64 gpa;
|
||||
} arm_sea;
|
||||
|
||||
Used on arm64 systems. When the VM capability ``KVM_CAP_ARM_SEA_TO_USER`` is
|
||||
enabled, a KVM exits to userspace if a guest access causes a synchronous
|
||||
external abort (SEA) and the host APEI fails to handle the SEA.
|
||||
|
||||
``esr`` is set to a sanitized value of ESR_EL2 from the exception taken to KVM,
|
||||
consisting of the following fields:
|
||||
|
||||
- ``ESR_EL2.EC``
|
||||
- ``ESR_EL2.IL``
|
||||
- ``ESR_EL2.FnV``
|
||||
- ``ESR_EL2.EA``
|
||||
- ``ESR_EL2.CM``
|
||||
- ``ESR_EL2.WNR``
|
||||
- ``ESR_EL2.FSC``
|
||||
- ``ESR_EL2.SET`` (when FEAT_RAS is implemented for the VM)
|
||||
|
||||
``gva`` is set to the value of FAR_EL2 from the exception taken to KVM when
|
||||
``ESR_EL2.FnV == 0``. Otherwise, the value of ``gva`` is unknown.
|
||||
|
||||
``gpa`` is set to the faulting IPA from the exception taken to KVM when
|
||||
the ``KVM_EXIT_ARM_SEA_FLAG_GPA_VALID`` flag is set. Otherwise, the value of
|
||||
``gpa`` is unknown.
|
||||
|
||||
::
|
||||
|
||||
/* Fix the size of the union. */
|
||||
|
|
@ -8703,6 +8738,18 @@ This capability indicate to the userspace whether a PFNMAP memory region
|
|||
can be safely mapped as cacheable. This relies on the presence of
|
||||
force write back (FWB) feature support on the hardware.
|
||||
|
||||
7.45 KVM_CAP_ARM_SEA_TO_USER
|
||||
----------------------------
|
||||
|
||||
:Architecture: arm64
|
||||
:Target: VM
|
||||
:Parameters: none
|
||||
:Returns: 0 on success, -EINVAL if unsupported.
|
||||
|
||||
When this capability is enabled, KVM may exit to userspace for SEAs taken to
|
||||
EL2 resulting from a guest access. See ``KVM_EXIT_ARM_SEA`` for more
|
||||
information.
|
||||
|
||||
8. Other capabilities.
|
||||
======================
|
||||
|
||||
|
|
|
|||
|
|
@ -350,6 +350,8 @@ struct kvm_arch {
|
|||
#define KVM_ARCH_FLAG_GUEST_HAS_SVE 9
|
||||
/* MIDR_EL1, REVIDR_EL1, and AIDR_EL1 are writable from userspace */
|
||||
#define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS 10
|
||||
/* Unhandled SEAs are taken to userspace */
|
||||
#define KVM_ARCH_FLAG_EXIT_SEA 11
|
||||
unsigned long flags;
|
||||
|
||||
/* VM-wide vCPU feature set */
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
|
|||
}
|
||||
mutex_unlock(&kvm->lock);
|
||||
break;
|
||||
case KVM_CAP_ARM_SEA_TO_USER:
|
||||
r = 0;
|
||||
set_bit(KVM_ARCH_FLAG_EXIT_SEA, &kvm->arch.flags);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -327,6 +331,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
|
|||
case KVM_CAP_IRQFD_RESAMPLE:
|
||||
case KVM_CAP_COUNTER_OFFSET:
|
||||
case KVM_CAP_ARM_WRITABLE_IMP_ID_REGS:
|
||||
case KVM_CAP_ARM_SEA_TO_USER:
|
||||
r = 1;
|
||||
break;
|
||||
case KVM_CAP_SET_GUEST_DEBUG2:
|
||||
|
|
|
|||
|
|
@ -1931,8 +1931,48 @@ static void handle_access_fault(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)
|
|||
read_unlock(&vcpu->kvm->mmu_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the SEA should be handled locally within KVM if the abort
|
||||
* is caused by a kernel memory allocation (e.g. stage-2 table memory).
|
||||
*/
|
||||
static bool host_owns_sea(struct kvm_vcpu *vcpu, u64 esr)
|
||||
{
|
||||
/*
|
||||
* Without FEAT_RAS HCR_EL2.TEA is RES0, meaning any external abort
|
||||
* taken from a guest EL to EL2 is due to a host-imposed access (e.g.
|
||||
* stage-2 PTW).
|
||||
*/
|
||||
if (!cpus_have_final_cap(ARM64_HAS_RAS_EXTN))
|
||||
return true;
|
||||
|
||||
/* KVM owns the VNCR when the vCPU isn't in a nested context. */
|
||||
if (is_hyp_ctxt(vcpu) && !kvm_vcpu_trap_is_iabt(vcpu) && (esr & ESR_ELx_VNCR))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Determining if an external abort during a table walk happened at
|
||||
* stage-2 is only possible with S1PTW is set. Otherwise, since KVM
|
||||
* sets HCR_EL2.TEA, SEAs due to a stage-1 walk (i.e. accessing the
|
||||
* PA of the stage-1 descriptor) can reach here and are reported
|
||||
* with a TTW ESR value.
|
||||
*/
|
||||
return (esr_fsc_is_sea_ttw(esr) && (esr & ESR_ELx_S1PTW));
|
||||
}
|
||||
|
||||
int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
|
||||
{
|
||||
struct kvm *kvm = vcpu->kvm;
|
||||
struct kvm_run *run = vcpu->run;
|
||||
u64 esr = kvm_vcpu_get_esr(vcpu);
|
||||
u64 esr_mask = ESR_ELx_EC_MASK |
|
||||
ESR_ELx_IL |
|
||||
ESR_ELx_FnV |
|
||||
ESR_ELx_EA |
|
||||
ESR_ELx_CM |
|
||||
ESR_ELx_WNR |
|
||||
ESR_ELx_FSC;
|
||||
u64 ipa;
|
||||
|
||||
/*
|
||||
* Give APEI the opportunity to claim the abort before handling it
|
||||
* within KVM. apei_claim_sea() expects to be called with IRQs enabled.
|
||||
|
|
@ -1941,7 +1981,33 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
|
|||
if (apei_claim_sea(NULL) == 0)
|
||||
return 1;
|
||||
|
||||
return kvm_inject_serror(vcpu);
|
||||
if (host_owns_sea(vcpu, esr) ||
|
||||
!test_bit(KVM_ARCH_FLAG_EXIT_SEA, &vcpu->kvm->arch.flags))
|
||||
return kvm_inject_serror(vcpu);
|
||||
|
||||
/* ESR_ELx.SET is RES0 when FEAT_RAS isn't implemented. */
|
||||
if (kvm_has_ras(kvm))
|
||||
esr_mask |= ESR_ELx_SET_MASK;
|
||||
|
||||
/*
|
||||
* Exit to userspace, and provide faulting guest virtual and physical
|
||||
* addresses in case userspace wants to emulate SEA to guest by
|
||||
* writing to FAR_ELx and HPFAR_ELx registers.
|
||||
*/
|
||||
memset(&run->arm_sea, 0, sizeof(run->arm_sea));
|
||||
run->exit_reason = KVM_EXIT_ARM_SEA;
|
||||
run->arm_sea.esr = esr & esr_mask;
|
||||
|
||||
if (!(esr & ESR_ELx_FnV))
|
||||
run->arm_sea.gva = kvm_vcpu_get_hfar(vcpu);
|
||||
|
||||
ipa = kvm_vcpu_get_fault_ipa(vcpu);
|
||||
if (ipa != INVALID_GPA) {
|
||||
run->arm_sea.flags |= KVM_EXIT_ARM_SEA_FLAG_GPA_VALID;
|
||||
run->arm_sea.gpa = ipa;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ struct kvm_xen_exit {
|
|||
#define KVM_EXIT_LOONGARCH_IOCSR 38
|
||||
#define KVM_EXIT_MEMORY_FAULT 39
|
||||
#define KVM_EXIT_TDX 40
|
||||
#define KVM_EXIT_ARM_SEA 41
|
||||
|
||||
/* For KVM_EXIT_INTERNAL_ERROR */
|
||||
/* Emulate instruction failed. */
|
||||
|
|
@ -473,6 +474,14 @@ struct kvm_run {
|
|||
} setup_event_notify;
|
||||
};
|
||||
} tdx;
|
||||
/* KVM_EXIT_ARM_SEA */
|
||||
struct {
|
||||
#define KVM_EXIT_ARM_SEA_FLAG_GPA_VALID (1ULL << 0)
|
||||
__u64 flags;
|
||||
__u64 esr;
|
||||
__u64 gva;
|
||||
__u64 gpa;
|
||||
} arm_sea;
|
||||
/* Fix the size of the union. */
|
||||
char padding[256];
|
||||
};
|
||||
|
|
@ -963,6 +972,7 @@ struct kvm_enable_cap {
|
|||
#define KVM_CAP_RISCV_MP_STATE_RESET 242
|
||||
#define KVM_CAP_ARM_CACHEABLE_PFNMAP_SUPPORTED 243
|
||||
#define KVM_CAP_GUEST_MEMFD_FLAGS 244
|
||||
#define KVM_CAP_ARM_SEA_TO_USER 245
|
||||
|
||||
struct kvm_irq_routing_irqchip {
|
||||
__u32 irqchip;
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@
|
|||
#define ESR_ELx_SF (UL(1) << ESR_ELx_SF_SHIFT)
|
||||
#define ESR_ELx_AR_SHIFT (14)
|
||||
#define ESR_ELx_AR (UL(1) << ESR_ELx_AR_SHIFT)
|
||||
#define ESR_ELx_VNCR_SHIFT (13)
|
||||
#define ESR_ELx_VNCR (UL(1) << ESR_ELx_VNCR_SHIFT)
|
||||
#define ESR_ELx_CM_SHIFT (8)
|
||||
#define ESR_ELx_CM (UL(1) << ESR_ELx_CM_SHIFT)
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ TEST_GEN_PROGS_arm64 += arm64/hypercalls
|
|||
TEST_GEN_PROGS_arm64 += arm64/external_aborts
|
||||
TEST_GEN_PROGS_arm64 += arm64/page_fault_test
|
||||
TEST_GEN_PROGS_arm64 += arm64/psci_test
|
||||
TEST_GEN_PROGS_arm64 += arm64/sea_to_user
|
||||
TEST_GEN_PROGS_arm64 += arm64/set_id_regs
|
||||
TEST_GEN_PROGS_arm64 += arm64/smccc_filter
|
||||
TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
|
||||
|
|
|
|||
331
tools/testing/selftests/kvm/arm64/sea_to_user.c
Normal file
331
tools/testing/selftests/kvm/arm64/sea_to_user.c
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
// 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;
|
||||
}
|
||||
|
|
@ -2025,6 +2025,7 @@ static struct exit_reason {
|
|||
KVM_EXIT_STRING(NOTIFY),
|
||||
KVM_EXIT_STRING(LOONGARCH_IOCSR),
|
||||
KVM_EXIT_STRING(MEMORY_FAULT),
|
||||
KVM_EXIT_STRING(ARM_SEA),
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user