[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <09d68c4748c0804f86aa6d943cf416742ef0f741.1749672978.git.afranji@google.com>
Date: Wed, 11 Jun 2025 21:16:36 +0000
From: Ryan Afranji <afranji@...gle.com>
To: kvm@...r.kernel.org, linux-kernel@...r.kernel.org, x86@...nel.org
Cc: sagis@...gle.com, bp@...en8.de, chao.p.peng@...ux.intel.com,
dave.hansen@...ux.intel.com, dmatlack@...gle.com, erdemaktas@...gle.com,
isaku.yamahata@...el.com, kai.huang@...el.com, mingo@...hat.com,
pbonzini@...hat.com, seanjc@...gle.com, tglx@...utronix.de,
zhi.wang.linux@...il.com, ackerleytng@...gle.com, andrew.jones@...ux.dev,
david@...hat.com, hpa@...or.com, kirill.shutemov@...ux.intel.com,
linux-kselftest@...r.kernel.org, tabba@...gle.com, vannapurve@...gle.com,
yan.y.zhao@...el.com, rick.p.edgecombe@...el.com,
Ryan Afranji <afranji@...gle.com>
Subject: [RFC PATCH v2 09/10] KVM: selftests: Add TDX support for ucalls
From: Ackerley Tng <ackerleytng@...gle.com>
ucalls for non-Coco VMs work by having the guest write to the rdi
register, then perform an io instruction to exit to the host. The host
then reads rdi using kvm_get_regs().
CPU registers can't be read using kvm_get_regs() for TDX, so TDX
guests use MMIO to pass the struct ucall's hva to the host. MMIO was
chosen because it is one of the simplest (hence unlikely to fail)
mechanisms that support passing 8 bytes from guest to host.
A new kvm_mem_region_type, MEM_REGION_UCALL, is added so TDX VMs can
set up a different memslot for the ucall_pool that is set up as shared
memory.
Signed-off-by: Ackerley Tng <ackerleytng@...gle.com>
Signed-off-by: Ryan Afranji <afranji@...gle.com>
---
.../testing/selftests/kvm/include/kvm_util.h | 1 +
.../testing/selftests/kvm/include/x86/ucall.h | 4 +-
.../testing/selftests/kvm/lib/ucall_common.c | 2 +-
.../selftests/kvm/lib/x86/tdx/tdx_util.c | 40 +++++++
tools/testing/selftests/kvm/lib/x86/ucall.c | 108 ++++++++++++------
5 files changed, 118 insertions(+), 37 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 1b6489081e74..8b252a668c78 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -80,6 +80,7 @@ enum kvm_mem_region_type {
MEM_REGION_PT,
MEM_REGION_TEST_DATA,
MEM_REGION_TDX_BOOT_PARAMS,
+ MEM_REGION_UCALL,
NR_MEM_REGIONS,
};
diff --git a/tools/testing/selftests/kvm/include/x86/ucall.h b/tools/testing/selftests/kvm/include/x86/ucall.h
index d3825dcc3cd9..0494a4a21557 100644
--- a/tools/testing/selftests/kvm/include/x86/ucall.h
+++ b/tools/testing/selftests/kvm/include/x86/ucall.h
@@ -6,8 +6,6 @@
#define UCALL_EXIT_REASON KVM_EXIT_IO
-static inline void ucall_arch_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa)
-{
-}
+void ucall_arch_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa);
#endif
diff --git a/tools/testing/selftests/kvm/lib/ucall_common.c b/tools/testing/selftests/kvm/lib/ucall_common.c
index 42151e571953..5f195d4d15dc 100644
--- a/tools/testing/selftests/kvm/lib/ucall_common.c
+++ b/tools/testing/selftests/kvm/lib/ucall_common.c
@@ -33,7 +33,7 @@ void ucall_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa)
int i;
vaddr = vm_vaddr_alloc_shared(vm, sizeof(*hdr), KVM_UTIL_MIN_VADDR,
- MEM_REGION_DATA);
+ MEM_REGION_UCALL);
hdr = (struct ucall_header *)addr_gva2hva(vm, vaddr);
memset(hdr, 0, sizeof(*hdr));
diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c
index ef03d42f58d0..a3612bf187a0 100644
--- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c
+++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c
@@ -11,6 +11,7 @@
#include "tdx/td_boot.h"
#include "tdx/tdx.h"
#include "test_util.h"
+#include "ucall_common.h"
uint64_t tdx_s_bit;
@@ -568,6 +569,43 @@ static void td_setup_boot_parameters(struct kvm_vm *vm, enum vm_mem_backing_src_
TEST_ASSERT_EQ(addr, TD_BOOT_PARAMETERS_GPA);
}
+/*
+ * GPA where ucall headers/pool will be set up
+ *
+ * TD_UCALL_POOL_GPA is arbitrarily chosen to
+ *
+ * + Be within the 4GB address space
+ * + Not clash with the other memslots for boot parameters, boot code and test
+ * code
+ */
+#define TD_UCALL_POOL_GPA 0x30000000
+/*
+ * GPA to use for ucall MMIO writes
+ *
+ * TD_UCALL_MMIO_GPA is arbitrarily chosen to
+ *
+ * + Be within the 4GB address space
+ * + Not clash with the other memslots for boot parameters, boot code and test
+ * code
+ * + Not be configured in any memslot (unconfigured GPAs are treated as
+ * MMIOs). For now, TDX VMs can't be used with KVM_MEM_READONLY so using
+ * readonly memslots won't work for TDX VMs.
+ */
+#define TD_UCALL_MMIO_GPA 0x40000000
+#define TD_UCALL_MEMSLOT 4
+
+static void td_setup_ucall(struct kvm_vm *vm)
+{
+ int npages;
+
+ npages = ucall_nr_pages_required(PAGE_SIZE);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, TD_UCALL_POOL_GPA,
+ TD_UCALL_MEMSLOT, npages, 0);
+ vm->memslots[MEM_REGION_UCALL] = TD_UCALL_MEMSLOT;
+
+ ucall_init(vm, TD_UCALL_MMIO_GPA);
+}
+
void td_initialize(struct kvm_vm *vm, enum vm_mem_backing_src_type src_type,
uint64_t attributes)
{
@@ -593,6 +631,8 @@ void td_initialize(struct kvm_vm *vm, enum vm_mem_backing_src_type src_type,
td_setup_boot_code(vm, src_type);
td_setup_boot_parameters(vm, src_type);
+
+ td_setup_ucall(vm);
}
void td_finalize(struct kvm_vm *vm)
diff --git a/tools/testing/selftests/kvm/lib/x86/ucall.c b/tools/testing/selftests/kvm/lib/x86/ucall.c
index 1265cecc7dd1..5cf915dbb588 100644
--- a/tools/testing/selftests/kvm/lib/x86/ucall.c
+++ b/tools/testing/selftests/kvm/lib/x86/ucall.c
@@ -5,52 +5,94 @@
* Copyright (C) 2018, Red Hat, Inc.
*/
#include "kvm_util.h"
+#include "kvm_util_types.h"
+#include "tdx/tdx.h"
#define UCALL_PIO_PORT ((uint16_t)0x1000)
+static uint8_t vm_type;
+static vm_paddr_t host_ucall_mmio_gpa;
+static vm_paddr_t ucall_mmio_gpa;
+
+void ucall_arch_init(struct kvm_vm *vm, vm_paddr_t mmio_gpa)
+{
+ vm_type = vm->type;
+ sync_global_to_guest(vm, vm_type);
+
+ host_ucall_mmio_gpa = ucall_mmio_gpa = mmio_gpa;
+
+#ifdef __x86_64__
+ if (vm_type == KVM_X86_TDX_VM)
+ ucall_mmio_gpa |= vm->arch.s_bit;
+#endif
+
+ sync_global_to_guest(vm, ucall_mmio_gpa);
+}
+
void ucall_arch_do_ucall(vm_vaddr_t uc)
{
- /*
- * FIXME: Revert this hack (the entire commit that added it) once nVMX
- * preserves L2 GPRs across a nested VM-Exit. If a ucall from L2, e.g.
- * to do a GUEST_SYNC(), lands the vCPU in L1, any and all GPRs can be
- * clobbered by L1. Save and restore non-volatile GPRs (clobbering RBP
- * in particular is problematic) along with RDX and RDI (which are
- * inputs), and clobber volatile GPRs. *sigh*
- */
-#define HORRIFIC_L2_UCALL_CLOBBER_HACK \
+ switch (vm_type) {
+ case KVM_X86_TDX_VM:
+ tdg_vp_vmcall_ve_request_mmio_write(ucall_mmio_gpa, 8, uc);
+ return;
+ default:
+ /*
+ * FIXME: Revert this hack (the entire commit that added it)
+ * once nVMX preserves L2 GPRs across a nested VM-Exit. If a
+ * ucall from L2, e.g. to do a GUEST_SYNC(), lands the vCPU in
+ * L1, any and all GPRs can be clobbered by L1. Save and
+ * restore non-volatile GPRs (clobbering RBP in particular is
+ * problematic) along with RDX and RDI (which are inputs), and
+ * clobber volatile GPRs. *sigh*
+ */
+#define HORRIFIC_L2_UCALL_CLOBBER_HACK \
"rcx", "rsi", "r8", "r9", "r10", "r11"
- asm volatile("push %%rbp\n\t"
- "push %%r15\n\t"
- "push %%r14\n\t"
- "push %%r13\n\t"
- "push %%r12\n\t"
- "push %%rbx\n\t"
- "push %%rdx\n\t"
- "push %%rdi\n\t"
- "in %[port], %%al\n\t"
- "pop %%rdi\n\t"
- "pop %%rdx\n\t"
- "pop %%rbx\n\t"
- "pop %%r12\n\t"
- "pop %%r13\n\t"
- "pop %%r14\n\t"
- "pop %%r15\n\t"
- "pop %%rbp\n\t"
- : : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax", "memory",
- HORRIFIC_L2_UCALL_CLOBBER_HACK);
+ asm volatile("push %%rbp\n\t"
+ "push %%r15\n\t"
+ "push %%r14\n\t"
+ "push %%r13\n\t"
+ "push %%r12\n\t"
+ "push %%rbx\n\t"
+ "push %%rdx\n\t"
+ "push %%rdi\n\t"
+ "in %[port], %%al\n\t"
+ "pop %%rdi\n\t"
+ "pop %%rdx\n\t"
+ "pop %%rbx\n\t"
+ "pop %%r12\n\t"
+ "pop %%r13\n\t"
+ "pop %%r14\n\t"
+ "pop %%r15\n\t"
+ "pop %%rbp\n\t"
+ :
+ : [port] "d"(UCALL_PIO_PORT), "D"(uc)
+ : "rax", "memory", HORRIFIC_L2_UCALL_CLOBBER_HACK);
+ }
}
void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
{
struct kvm_run *run = vcpu->run;
- if (run->exit_reason == KVM_EXIT_IO && run->io.port == UCALL_PIO_PORT) {
- struct kvm_regs regs;
+ switch (vm_type) {
+ case KVM_X86_TDX_VM:
+ if (vcpu->run->exit_reason == KVM_EXIT_MMIO &&
+ vcpu->run->mmio.phys_addr == host_ucall_mmio_gpa &&
+ vcpu->run->mmio.len == 8 && vcpu->run->mmio.is_write) {
+ uint64_t data = *(uint64_t *)vcpu->run->mmio.data;
+
+ return (void *)data;
+ }
+ return NULL;
+ default:
+ if (run->exit_reason == KVM_EXIT_IO &&
+ run->io.port == UCALL_PIO_PORT) {
+ struct kvm_regs regs;
- vcpu_regs_get(vcpu, ®s);
- return (void *)regs.rdi;
+ vcpu_regs_get(vcpu, ®s);
+ return (void *)regs.rdi;
+ }
+ return NULL;
}
- return NULL;
}
--
2.50.0.rc1.591.g9c95f17f64-goog
Powered by blists - more mailing lists