[PATCH v9 18/19] KVM: selftests: Add ucall support for TDX

Sagi Shahar posted 19 patches 1 month, 1 week ago
There is a newer version of this series
[PATCH v9 18/19] KVM: selftests: Add ucall support for TDX
Posted by Sagi Shahar 1 month, 1 week ago
From: Ackerley Tng <ackerleytng@google.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.

Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sagi Shahar <sagis@google.com>
Signed-off-by: Sagi Shahar <sagis@google.com>
---
 .../testing/selftests/kvm/include/x86/ucall.h |  4 +-
 tools/testing/selftests/kvm/lib/x86/ucall.c   | 45 ++++++++++++++++---
 2 files changed, 41 insertions(+), 8 deletions(-)

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/x86/ucall.c b/tools/testing/selftests/kvm/lib/x86/ucall.c
index 1265cecc7dd1..0ad24baaa3c4 100644
--- a/tools/testing/selftests/kvm/lib/x86/ucall.c
+++ b/tools/testing/selftests/kvm/lib/x86/ucall.c
@@ -5,11 +5,34 @@
  * Copyright (C) 2018, Red Hat, Inc.
  */
 #include "kvm_util.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;
+
+	if (vm_type == KVM_X86_TDX_VM)
+		ucall_mmio_gpa |= vm->arch.s_bit;
+
+	sync_global_to_guest(vm, ucall_mmio_gpa);
+}
+
 void ucall_arch_do_ucall(vm_vaddr_t uc)
 {
+	if (vm_type == KVM_X86_TDX_VM) {
+		tdg_vp_vmcall_ve_request_mmio_write(ucall_mmio_gpa, 8, uc);
+		return;
+	}
+
 	/*
 	 * 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.
@@ -46,11 +69,23 @@ 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, &regs);
-		return (void *)regs.rdi;
+			vcpu_regs_get(vcpu, &regs);
+			return (void *)regs.rdi;
+		}
+		return NULL;
 	}
-	return NULL;
 }
-- 
2.51.0.rc1.193.gad69d77794-goog
Re: [PATCH v9 18/19] KVM: selftests: Add ucall support for TDX
Posted by Binbin Wu 1 month, 1 week ago

On 8/21/2025 12:29 PM, Sagi Shahar wrote:
> From: Ackerley Tng <ackerleytng@google.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.
>
> Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> Co-developed-by: Sagi Shahar <sagis@google.com>
> Signed-off-by: Sagi Shahar <sagis@google.com>
> ---
>   .../testing/selftests/kvm/include/x86/ucall.h |  4 +-
>   tools/testing/selftests/kvm/lib/x86/ucall.c   | 45 ++++++++++++++++---
>   2 files changed, 41 insertions(+), 8 deletions(-)
>
> 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/x86/ucall.c b/tools/testing/selftests/kvm/lib/x86/ucall.c
> index 1265cecc7dd1..0ad24baaa3c4 100644
> --- a/tools/testing/selftests/kvm/lib/x86/ucall.c
> +++ b/tools/testing/selftests/kvm/lib/x86/ucall.c
> @@ -5,11 +5,34 @@
>    * Copyright (C) 2018, Red Hat, Inc.
>    */
>   #include "kvm_util.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;
> +
> +	if (vm_type == KVM_X86_TDX_VM)
> +		ucall_mmio_gpa |= vm->arch.s_bit;
> +
> +	sync_global_to_guest(vm, ucall_mmio_gpa);
> +}
> +
>   void ucall_arch_do_ucall(vm_vaddr_t uc)
>   {
> +	if (vm_type == KVM_X86_TDX_VM) {
> +		tdg_vp_vmcall_ve_request_mmio_write(ucall_mmio_gpa, 8, uc);
> +		return;
> +	}
> +
>   	/*
>   	 * 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.
> @@ -46,11 +69,23 @@ 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;

My first thought was how did SEV_ES or SNP work for this since they are not
able to get RDI neither.
Then I had a check in sev_smoke_test.c, both guest_sev_es_code() and
guest_snp_code() call GUEST_ASSERT(), which finally calls ucall_assert(), but
in test_sev(), the code doesn't handle ucall for SEV_ES or SNP.
Does it mean GUEST_ASSERT() is currently not working and ignored for SEV_ES
and SNP? Or did I miss anything?

> +	default:
> +		if (run->exit_reason == KVM_EXIT_IO && run->io.port == UCALL_PIO_PORT) {
> +			struct kvm_regs regs;
>   
> -		vcpu_regs_get(vcpu, &regs);
> -		return (void *)regs.rdi;
> +			vcpu_regs_get(vcpu, &regs);
> +			return (void *)regs.rdi;
> +		}
> +		return NULL;
>   	}
> -	return NULL;
>   }
Re: [PATCH v9 18/19] KVM: selftests: Add ucall support for TDX
Posted by Sean Christopherson 1 month ago
On Wed, Aug 27, 2025, Binbin Wu wrote:
> On 8/21/2025 12:29 PM, Sagi Shahar wrote:
> > @@ -46,11 +69,23 @@ 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;
> 
> My first thought was how did SEV_ES or SNP work for this since they are not
> able to get RDI neither.
> Then I had a check in sev_smoke_test.c, both guest_sev_es_code() and
> guest_snp_code() call GUEST_ASSERT(), which finally calls ucall_assert(), but
> in test_sev(), the code doesn't handle ucall for SEV_ES or SNP.
> Does it mean GUEST_ASSERT() is currently not working and ignored for SEV_ES
> and SNP? Or did I miss anything?

GUEST_ASSERT() "works" for -ES and -SNP in the sense that it generates as test
failure due to the #VC not being handled (leads to SHUTDOWN).  But you're correct
that ucall isn't functional yet.  x86/sev_smoke_test.c fudges around lack of ucall
by using the GHCB MSR protocol to signal "done".

        /*
         * TODO: Add GHCB and ucall support for SEV-ES guests.  For now, simply
         * force "termination" to signal "done" via the GHCB MSR protocol.
         */
        wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
        vmgexit();
Re: [PATCH v9 18/19] KVM: selftests: Add ucall support for TDX
Posted by Binbin Wu 1 month ago

On 9/2/2025 11:45 PM, Sean Christopherson wrote:
> On Wed, Aug 27, 2025, Binbin Wu wrote:
>> On 8/21/2025 12:29 PM, Sagi Shahar wrote:
>>> @@ -46,11 +69,23 @@ 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;
>> My first thought was how did SEV_ES or SNP work for this since they are not
>> able to get RDI neither.
>> Then I had a check in sev_smoke_test.c, both guest_sev_es_code() and
>> guest_snp_code() call GUEST_ASSERT(), which finally calls ucall_assert(), but
>> in test_sev(), the code doesn't handle ucall for SEV_ES or SNP.
>> Does it mean GUEST_ASSERT() is currently not working and ignored for SEV_ES
>> and SNP? Or did I miss anything?
> GUEST_ASSERT() "works" for -ES and -SNP in the sense that it generates as test
> failure due to the #VC not being handled (leads to SHUTDOWN).  But you're correct
> that ucall isn't functional yet.  x86/sev_smoke_test.c fudges around lack of ucall
> by using the GHCB MSR protocol to signal "done".
>
>          /*
>           * TODO: Add GHCB and ucall support for SEV-ES guests.  For now, simply
>           * force "termination" to signal "done" via the GHCB MSR protocol.
>           */
>          wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
>          vmgexit();
>
OK, thanks for the explanation!