[PATCH v3 11/12] KVM: selftests: Test VMware hypercall exits with nested Hyper-V flushes

From: Zack Rusin

Date: Wed Nov 11 2026 - 01:08:46 EST


Enable VMware hypercall exits in hyperv_evmcs and hyperv_svm_test when
KVM supports them, and issue the first L2 TLB flush hypercall with
VMware's magic value in RAX.

When L1 enables it, L0 handles Hyper-V's L2 TLB flush hypercall itself,
through the same dispatch that forwards VMware hypercalls. The 64-bit
Hyper-V calling convention doesn't use RAX as an input, so a flush with
VMware's magic value left in RAX must still be handled as a Hyper-V
hypercall instead of exiting to userspace.

Add hyperv_hypercall_rax() to issue a Hyper-V hypercall with a given
value in RAX, and enable the VMware flag again when hyperv_evmcs
restores its state into a new VM.

Signed-off-by: Zack Rusin <zack.rusin@xxxxxxxxxxxx>
---
.../selftests/kvm/include/x86/hyperv.h | 21 +++++++++++++++
.../testing/selftests/kvm/x86/hyperv_evmcs.c | 27 +++++++++++++++----
.../selftests/kvm/x86/hyperv_svm_test.c | 21 ++++++++++-----
3 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/kvm/include/x86/hyperv.h b/tools/testing/selftests/kvm/include/x86/hyperv.h
index 78003f5a22f3..06d14be3c4ee 100644
--- a/tools/testing/selftests/kvm/include/x86/hyperv.h
+++ b/tools/testing/selftests/kvm/include/x86/hyperv.h
@@ -286,6 +286,27 @@ static inline void hyperv_hypercall(u64 control, gva_t input_address,
GUEST_ASSERT((hv_status & 0xffff) == 0);
}

+/*
+ * Issue a Hyper-V hypercall with @rax in RAX, which the 64-bit calling
+ * convention doesn't use as an input, and assert that it succeeded.
+ */
+static inline void hyperv_hypercall_rax(u64 control, gva_t input_address,
+ gva_t output_address, u64 rax)
+{
+ u64 error_code;
+ u8 vector;
+
+ asm volatile("mov %[output_address], %%r8\n\t"
+ KVM_ASM_SAFE("vmcall")
+ : "+a" (rax), "+c" (control), "+d" (input_address),
+ KVM_ASM_SAFE_OUTPUTS(vector, error_code)
+ : [output_address] "r"(output_address)
+ : "cc", "memory", "r8", KVM_ASM_SAFE_CLOBBERS);
+
+ GUEST_ASSERT(!vector);
+ GUEST_ASSERT((rax & 0xffff) == 0);
+}
+
/* Write 'Fast' hypercall input 'data' to the first 'n_sse_regs' SSE regs */
static inline void hyperv_write_xmm_input(void *data, int n_sse_regs)
{
diff --git a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c
index c81f2dd4f2a1..cdd4e523c62a 100644
--- a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c
+++ b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c
@@ -18,6 +18,8 @@
#include "hyperv.h"
#include "vmx.h"

+#define VMWARE_HYPERVISOR_MAGIC 0x564d5868
+
static int ud_count;

static void guest_ud_handler(struct ex_regs *regs)
@@ -63,9 +65,13 @@ void l2_guest_code(void)
vmcall();
rdmsr_from_l2(MSR_GS_BASE); /* intercepted */

- /* L2 TLB flush tests */
- hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE | HV_HYPERCALL_FAST_BIT, 0x0,
- HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES | HV_FLUSH_ALL_PROCESSORS);
+ /*
+ * L2 TLB flush tests. Leave VMware's magic value in RAX to check that
+ * VMware hypercall exits don't claim the flush that L0 handles.
+ */
+ hyperv_hypercall_rax(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE | HV_HYPERCALL_FAST_BIT, 0x0,
+ HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES | HV_FLUSH_ALL_PROCESSORS,
+ VMWARE_HYPERVISOR_MAGIC);
rdmsr_from_l2(MSR_FS_BASE);
/*
* Note: hypercall status (RAX) is not preserved correctly by L1 after
@@ -209,6 +215,13 @@ void inject_nmi(struct kvm_vcpu *vcpu)
vcpu_events_set(vcpu, &events);
}

+static void vm_enable_vmware_hypercall(struct kvm_vm *vm)
+{
+ if (kvm_check_cap(KVM_CAP_X86_VMWARE) & KVM_VMWARE_ENABLE_HYPERCALL)
+ vm_enable_cap(vm, KVM_CAP_X86_VMWARE,
+ KVM_VMWARE_ENABLE_HYPERCALL);
+}
+
static struct kvm_vcpu *save_restore_vm(struct kvm_vm *vm,
struct kvm_vcpu *vcpu)
{
@@ -222,7 +235,9 @@ static struct kvm_vcpu *save_restore_vm(struct kvm_vm *vm,
kvm_vm_release(vm);

/* Restore state in a new VM. */
- vcpu = vm_recreate_with_one_vcpu(vm);
+ kvm_vm_restart(vm);
+ vm_enable_vmware_hypercall(vm);
+ vcpu = vm_vcpu_recreate(vm, 0);
vcpu_set_hv_cpuid(vcpu);
vcpu_enable_evmcs(vcpu);
vcpu_load_state(vcpu, state);
@@ -251,7 +266,9 @@ int main(int argc, char *argv[])
TEST_REQUIRE(kvm_has_cap(KVM_CAP_HYPERV_ENLIGHTENED_VMCS));
TEST_REQUIRE(kvm_hv_cpu_has(HV_X64_NESTED_DIRECT_FLUSH));

- vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ vm = vm_create(1);
+ vm_enable_vmware_hypercall(vm);
+ vcpu = vm_vcpu_add(vm, 0, guest_code);

hcall_page = vm_alloc_pages(vm, 1);
memset(addr_gva2hva(vm, hcall_page), 0x0, getpagesize());
diff --git a/tools/testing/selftests/kvm/x86/hyperv_svm_test.c b/tools/testing/selftests/kvm/x86/hyperv_svm_test.c
index 3a3dc31072e8..fe0762ae8260 100644
--- a/tools/testing/selftests/kvm/x86/hyperv_svm_test.c
+++ b/tools/testing/selftests/kvm/x86/hyperv_svm_test.c
@@ -18,6 +18,8 @@
#include "svm_util.h"
#include "hyperv.h"

+#define VMWARE_HYPERVISOR_MAGIC 0x564d5868
+
/* Exit to L1 from L2 with RDMSR instruction */
static inline void rdmsr_from_l2(u32 msr)
{
@@ -44,11 +46,14 @@ void l2_guest_code(void)

GUEST_SYNC(5);

- /* L2 TLB flush tests */
- hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE |
- HV_HYPERCALL_FAST_BIT, 0x0,
- HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES |
- HV_FLUSH_ALL_PROCESSORS);
+ /*
+ * L2 TLB flush tests. Leave VMware's magic value in RAX to check that
+ * VMware hypercall exits don't claim the flush that L0 handles.
+ */
+ hyperv_hypercall_rax(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE |
+ HV_HYPERCALL_FAST_BIT, 0x0,
+ HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES |
+ HV_FLUSH_ALL_PROCESSORS, VMWARE_HYPERVISOR_MAGIC);
rdmsr_from_l2(MSR_FS_BASE);
/*
* Note: hypercall status (RAX) is not preserved correctly by L1 after
@@ -156,7 +161,11 @@ int main(int argc, char *argv[])
TEST_REQUIRE(kvm_hv_cpu_has(HV_X64_NESTED_DIRECT_FLUSH));

/* Create VM */
- vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ vm = vm_create(1);
+ if (kvm_check_cap(KVM_CAP_X86_VMWARE) & KVM_VMWARE_ENABLE_HYPERCALL)
+ vm_enable_cap(vm, KVM_CAP_X86_VMWARE,
+ KVM_VMWARE_ENABLE_HYPERCALL);
+ vcpu = vm_vcpu_add(vm, 0, guest_code);
vcpu_set_hv_cpuid(vcpu);
vcpu_alloc_svm(vm, &nested_gva);
vcpu_alloc_hyperv_test_pages(vm, &hv_pages_gva);
--
2.53.0