Re: [PATCH v16.1] KVM: selftests: Add support for TDX ucalls, via TDVMCALL_REPORT_FATAL_ERROR

From: Sagi Shahar

Date: Mon Nov 09 2026 - 12:15:30 EST


On Fri, Nov 6, 2026 at 10:47 AM Sean Christopherson <seanjc@xxxxxxxxxx> wrote:
>
> Add support for doing ucalls on TDX by abusing TDVMCALL_REPORT_FATAL_ERROR
> to pass the address of the payload to the host. The "fatal error" TDVMCALL
> is perfectly suited for passing information to host userspace, as both the
> TDX Module and KVM allow the guest to pass (almost) all registers to the
> host, i.e. provide enough of a data payload to make a collision with a real
> fatal error practically impossible.
>
> TDX can't use port I/O, as the TDX ABI doesn't allow the guest to share
> arbitrary register state with the host on a port I/O exit, and the port I/O
> data payload is limited to 4 bytes, i.e. would potentially truncate the
> ucall address.
>
> Alternatively, TDX could use MMIO, but using a magic emulated MMIO address
> is fragile (see the TODO in __vm_create()), especially for TDX since TDX
> doesn't support read-only memslots, i.e. doesn't have line of sight towards
> addressing the TODO. E.g. TDX could hardcode the address to something that
> is all but guaranteed to be unused on x86, e.g. the I/O APIC base address
> or the HPET address, but that doesn't truly address the fragility concerns,
> and it's ugly because ucall_arch_init() would completely ignore the passed
> in @mmio_gpa despite obviously utilizing emulated MMIO.
>
> Use a simple flag to route vCPUs/VMs to the right do_ucall() routine, and
> take care to set the flag via write_guest_global() so that ucall_arch_init()
> is thread safe (in the *extremely* unlikely scenario a single test creates
> both TDX and non-TDX VMs *concurrently).
>
> Alternatively, a virtual function pointer could be used to dispatch to the
> correct do_ucall() via an indirect branch, but that has zero advantages
> other than making ucall_arch_do_ucall() look prettier, at the cost of being
> slightly harder to understand and making ucall_arch_init() uglier. KISS.
>
> Cc: Sagi Shahar <sagis@xxxxxxxxxx>
> Cc: Lisa Wang <wyihan@xxxxxxxxxx>
> Cc: Xiaoyao Li <xiaoyao.li@xxxxxxxxx>
> Cc: Binbin Wu <binbin.wu@xxxxxxxxxxxxxxx>
> Cc: Ackerley Tng <ackerleytng@xxxxxxxxxx>
> Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
> ---
> .../selftests/kvm/include/x86/tdx/tdx.h | 7 +++
> .../testing/selftests/kvm/include/x86/ucall.h | 6 +--
> tools/testing/selftests/kvm/lib/x86/ucall.c | 53 +++++++++++++++++--
> 3 files changed, 58 insertions(+), 8 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx.h
> index d41a1efc8a63..9982aaaba885 100644
> --- a/tools/testing/selftests/kvm/include/x86/tdx/tdx.h
> +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx.h
> @@ -4,6 +4,13 @@
>
> #include <linux/types.h>
>
> +/* TDX hypercall Leaf IDs */
> +#define TDVMCALL_GET_TD_VM_CALL_INFO 0x10000
> +#define TDVMCALL_MAP_GPA 0x10001
> +#define TDVMCALL_GET_QUOTE 0x10002
> +#define TDVMCALL_REPORT_FATAL_ERROR 0x10003
> +#define TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT 0x10004
> +
> u64 __tdvmcall(u64 fn, u64 r12, u64 r13, u64 r14, u64 r15);
>
> #endif /* SELFTEST_KVM_TDX_TDX_H */
> diff --git a/tools/testing/selftests/kvm/include/x86/ucall.h b/tools/testing/selftests/kvm/include/x86/ucall.h
> index 0e4950041e3e..aca582bb2cd5 100644
> --- a/tools/testing/selftests/kvm/include/x86/ucall.h
> +++ b/tools/testing/selftests/kvm/include/x86/ucall.h
> @@ -2,12 +2,8 @@
> #ifndef SELFTEST_KVM_UCALL_H
> #define SELFTEST_KVM_UCALL_H
>
> -#include "kvm_util.h"
> +#include <linux/kvm.h>
>
> #define UCALL_EXIT_REASON KVM_EXIT_IO
>
> -static inline void ucall_arch_init(struct kvm_vm *vm, gpa_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 38050c60a067..bb4a7215d9b1 100644
> --- a/tools/testing/selftests/kvm/lib/x86/ucall.c
> +++ b/tools/testing/selftests/kvm/lib/x86/ucall.c
> @@ -5,16 +5,40 @@
> * Copyright (C) 2018, Red Hat, Inc.
> */
> #include "kvm_util.h"
> +#include "tdx/tdx.h"
> +#include "tdx/tdx_util.h"
>
> -#define UCALL_PIO_PORT ((u16)0x1000)
> +#define UCALL_PIO_PORT ((u16)0x1000)
> +#define UCALL_TDX_MAGIC 0xabacadabaULL
>
> -void ucall_arch_do_ucall(gva_t uc)
> +static void ucall_tdx_do_ucall(gva_t uc)
> +{
> + __tdvmcall(TDVMCALL_REPORT_FATAL_ERROR, UCALL_TDX_MAGIC, uc, 0, 0);
> +}
> +
> +static void *ucall_tdx_get_ucall(struct kvm_vcpu *vcpu)
> +{
> + struct kvm_run *run = vcpu->run;
> +
> + /*
> + * KVM_SYSTEM_EVENT_TDX_FATAL stores R0..R15 in data[0..15]. R12
> + * holds the ucall magic and R13 holds the ucall GVA.
> + */
> + if (run->exit_reason == KVM_EXIT_SYSTEM_EVENT &&
> + run->system_event.type == KVM_SYSTEM_EVENT_TDX_FATAL &&
> + run->system_event.data[12] == UCALL_TDX_MAGIC)
> + return (void *)(run->system_event.data[13]);
> +
> + return NULL;
> +}
> +
> +static void ucall_x86_do_ucall(gva_t uc)
> {
> asm volatile("in %[port], %%al"
> : : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax", "memory");
> }
>
> -void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
> +static void *ucall_x86_get_ucall(struct kvm_vcpu *vcpu)
> {
> struct kvm_run *run = vcpu->run;
>
> @@ -26,3 +50,26 @@ void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
> }
> return NULL;
> }
> +
> +static bool ucall_is_tdx_vm;
> +
> +void ucall_arch_init(struct kvm_vm *vm, gpa_t mmio_gpa)
> +{
> + write_guest_global(vm, ucall_is_tdx_vm, is_tdx_vm(vm));
> +}
> +
> +void ucall_arch_do_ucall(gva_t uc)
> +{
> + if (ucall_is_tdx_vm)
> + return ucall_tdx_do_ucall(uc);
> +
> + return ucall_x86_do_ucall(uc);
> +}
> +
> +void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)
> +{
> + if (is_tdx_vm(vcpu->vm))
> + return ucall_tdx_get_ucall(vcpu);
> +
> + return ucall_x86_get_ucall(vcpu);
> +}
>
> base-commit: 4aeb1c3931544fb3bb64397391c2e7153a81f824
> --
> 2.56.0.567.gdb80ae8cd5-goog
>

Reviewed-by: Sagi Shahar <sagis@xxxxxxxxxx>