On 8/8/2025 4:16 AM, Sagi Shahar wrote: > From: Roger Wang <runanwang@xxxxxxxxxx> > > Adds a test for TDG.VP.INFO. > > Introduce __tdx_module_call() that does needed shuffling from function > parameters to registers used by the TDCALL instruction that is used by the > guest to communicate with the TDX module. The first function parameter is > the leaf number indicating which guest side function should be run, for > example, TDG.VP.INFO. > > The guest uses new __tdx_module_call() to call TDG.VP.INFO to obtain TDX > TD execution environment information from the TDX module. All returned > registers are passed back to the host that verifies values for > correctness. > > Co-developed-by: Sagi Shahar <sagis@xxxxxxxxxx> > Signed-off-by: Sagi Shahar <sagis@xxxxxxxxxx> > Signed-off-by: Roger Wang <runanwang@xxxxxxxxxx> > Signed-off-by: Sagi Shahar <sagis@xxxxxxxxxx> Duplicated SOB. Please change the order according to submmitting-patches.rst. And check the whole series for such SOB issue. > --- > .../selftests/kvm/include/x86/tdx/tdcall.h | 19 +++ > .../selftests/kvm/include/x86/tdx/tdx.h | 5 + > .../selftests/kvm/lib/x86/tdx/tdcall.S | 68 +++++++++ > tools/testing/selftests/kvm/lib/x86/tdx/tdx.c | 27 ++++ > tools/testing/selftests/kvm/x86/tdx_vm_test.c | 133 +++++++++++++++++- > 5 files changed, 251 insertions(+), 1 deletion(-) > > diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdcall.h b/tools/testing/selftests/kvm/include/x86/tdx/tdcall.h > index e7440f7fe259..ab1a97a82fa9 100644 > --- a/tools/testing/selftests/kvm/include/x86/tdx/tdcall.h > +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdcall.h > @@ -32,4 +32,23 @@ struct tdx_hypercall_args { > /* Used to request services from the VMM */ > u64 __tdx_hypercall(struct tdx_hypercall_args *args, unsigned long flags); > > +/* > + * Used to gather the output registers values of the TDCALL and SEAMCALL This series only uses this struct to gather values for TDCALL. Please remove the "SEAMCALL" for accuracy. > + * instructions when requesting services from the TDX module. > + * > + * This is a software only structure and not part of the TDX module/VMM ABI. > + */ > +struct tdx_module_output { > + u64 rcx; > + u64 rdx; > + u64 r8; > + u64 r9; > + u64 r10; > + u64 r11; > +}; > + > +/* Used to communicate with the TDX module */ > +u64 __tdx_module_call(u64 fn, u64 rcx, u64 rdx, u64 r8, u64 r9, > + struct tdx_module_output *out); > + > #endif // SELFTESTS_TDX_TDCALL_H > diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx.h b/tools/testing/selftests/kvm/include/x86/tdx/tdx.h > index 060158cb046b..801ca879664e 100644 > --- a/tools/testing/selftests/kvm/include/x86/tdx/tdx.h > +++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx.h > @@ -6,6 +6,8 @@ > > #include "kvm_util.h" > > +#define TDG_VP_INFO 1 > + > #define TDG_VP_VMCALL_GET_TD_VM_CALL_INFO 0x10000 > #define TDG_VP_VMCALL_REPORT_FATAL_ERROR 0x10003 > > @@ -31,5 +33,8 @@ uint64_t tdg_vp_vmcall_ve_request_mmio_write(uint64_t address, uint64_t size, > uint64_t tdg_vp_vmcall_instruction_cpuid(uint32_t eax, uint32_t ecx, > uint32_t *ret_eax, uint32_t *ret_ebx, > uint32_t *ret_ecx, uint32_t *ret_edx); > +uint64_t tdg_vp_info(uint64_t *rcx, uint64_t *rdx, > + uint64_t *r8, uint64_t *r9, > + uint64_t *r10, uint64_t *r11); > > #endif // SELFTEST_TDX_TDX_H > diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S b/tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S > index b10769d1d557..c393a0fb35be 100644 > --- a/tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S > +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdcall.S > @@ -91,5 +91,73 @@ __tdx_hypercall: > pop %rbp > ret > > +#define TDX_MODULE_rcx 0 /* offsetof(struct tdx_module_output, rcx) */ > +#define TDX_MODULE_rdx 8 /* offsetof(struct tdx_module_output, rdx) */ > +#define TDX_MODULE_r8 16 /* offsetof(struct tdx_module_output, r8) */ > +#define TDX_MODULE_r9 24 /* offsetof(struct tdx_module_output, r9) */ > +#define TDX_MODULE_r10 32 /* offsetof(struct tdx_module_output, r10) */ > +#define TDX_MODULE_r11 40 /* offsetof(struct tdx_module_output, r11) */ > + > +.globl __tdx_module_call > +.type __tdx_module_call, @function > +__tdx_module_call: > + /* Set up stack frame */ > + push %rbp > + movq %rsp, %rbp > + > + /* Callee-saved, so preserve it */ > + push %r12 > + > + /* > + * Push output pointer to stack. > + * After the operation, it will be fetched into R12 register. > + */ > + push %r9 > + > + /* Mangle function call ABI into TDCALL/SEAMCALL ABI: */ > + /* Move Leaf ID to RAX */ > + mov %rdi, %rax > + /* Move input 4 to R9 */ > + mov %r8, %r9 > + /* Move input 3 to R8 */ > + mov %rcx, %r8 > + /* Move input 1 to RCX */ > + mov %rsi, %rcx > + /* Leave input param 2 in RDX */ > + > + tdcall > + > + /* > + * Fetch output pointer from stack to R12 (It is used > + * as temporary storage) > + */ > + pop %r12 > + > + /* > + * Since this macro can be invoked with NULL as an output pointer, > + * check if caller provided an output struct before storing output > + * registers. > + * > + * Update output registers, even if the call failed (RAX != 0). > + * Other registers may contain details of the failure. > + */ > + test %r12, %r12 > + jz .Lno_output_struct > + > + /* Copy result registers to output struct: */ > + movq %rcx, TDX_MODULE_rcx(%r12) > + movq %rdx, TDX_MODULE_rdx(%r12) > + movq %r8, TDX_MODULE_r8(%r12) > + movq %r9, TDX_MODULE_r9(%r12) > + movq %r10, TDX_MODULE_r10(%r12) > + movq %r11, TDX_MODULE_r11(%r12) > + > +.Lno_output_struct: > + /* Restore the state of R12 register */ > + pop %r12 > + > + pop %rbp > + ret > + > /* Disable executable stack */ > .section .note.GNU-stack,"",%progbits > diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx.c b/tools/testing/selftests/kvm/lib/x86/tdx/tdx.c > index fb391483d2fa..ab6fd3d7ae4b 100644 > --- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx.c > +++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx.c > @@ -162,3 +162,30 @@ uint64_t tdg_vp_vmcall_instruction_cpuid(uint32_t eax, uint32_t ecx, > > return ret; > } > + > +uint64_t tdg_vp_info(uint64_t *rcx, uint64_t *rdx, > + uint64_t *r8, uint64_t *r9, > + uint64_t *r10, uint64_t *r11) > +{ > + struct tdx_module_output out; > + uint64_t ret; > + > + memset(&out, 0, sizeof(struct tdx_module_output)); > + > + ret = __tdx_module_call(TDG_VP_INFO, 0, 0, 0, 0, &out); > + > + if (rcx) > + *rcx = out.rcx; > + if (rdx) > + *rdx = out.rdx; > + if (r8) > + *r8 = out.r8; > + if (r9) > + *r9 = out.r9; > + if (r10) > + *r10 = out.r10; > + if (r11) > + *r11 = out.r11; > + > + return ret; > +} > diff --git a/tools/testing/selftests/kvm/x86/tdx_vm_test.c b/tools/testing/selftests/kvm/x86/tdx_vm_test.c > index b6ef0348746c..82acc17a66ab 100644 > --- a/tools/testing/selftests/kvm/x86/tdx_vm_test.c > +++ b/tools/testing/selftests/kvm/x86/tdx_vm_test.c > @@ -1038,6 +1038,135 @@ void verify_host_reading_private_mem(void) > printf("\t ... PASSED\n"); > } > > +/* > + * Do a TDG.VP.INFO call from the guest > + */ > +void guest_tdcall_vp_info(void) > +{ > + uint64_t rcx, rdx, r8, r9, r10, r11; > + uint64_t err; > + > + err = tdg_vp_info(&rcx, &rdx, &r8, &r9, &r10, &r11); > + tdx_assert_error(err); > + > + /* return values to user space host */ > + err = tdx_test_report_64bit_to_user_space(rcx); > + tdx_assert_error(err); > + > + err = tdx_test_report_64bit_to_user_space(rdx); > + tdx_assert_error(err); > + > + err = tdx_test_report_64bit_to_user_space(r8); > + tdx_assert_error(err); > + > + err = tdx_test_report_64bit_to_user_space(r9); > + tdx_assert_error(err); > + > + err = tdx_test_report_64bit_to_user_space(r10); > + tdx_assert_error(err); > + > + err = tdx_test_report_64bit_to_user_space(r11); > + tdx_assert_error(err); > + > + tdx_test_success(); > +} > + > +/* > + * TDG.VP.INFO call from the guest. Verify the right values are returned > + */ > +void verify_tdcall_vp_info(void) > +{ > + const struct kvm_cpuid_entry2 *cpuid_entry; > + uint32_t ret_num_vcpus, ret_max_vcpus; > + uint64_t rcx, rdx, r8, r9, r10, r11; > + const int num_vcpus = 2; > + struct kvm_vcpu *vcpus[num_vcpus]; > + uint64_t attributes; > + struct kvm_vm *vm; > + int gpa_bits = -1; > + uint32_t i; > + > + vm = td_create(); > + > +#define TDX_TDPARAM_ATTR_SEPT_VE_DISABLE_BIT BIT(28) > + /* Setting attributes parameter used by TDH.MNG.INIT to 0x10000000 */ > + attributes = TDX_TDPARAM_ATTR_SEPT_VE_DISABLE_BIT; > + > + td_initialize(vm, VM_MEM_SRC_ANONYMOUS, attributes); > + > + for (i = 0; i < num_vcpus; i++) > + vcpus[i] = td_vcpu_add(vm, i, guest_tdcall_vp_info); > + > + td_finalize(vm); > + > + printf("Verifying TDG.VP.INFO call:\n"); > + > + /* Get KVM CPUIDs for reference */ > + > + for (i = 0; i < num_vcpus; i++) { > + struct kvm_vcpu *vcpu = vcpus[i]; > + > + cpuid_entry = vcpu_get_cpuid_entry(vcpu, 0x80000008); > + TEST_ASSERT(cpuid_entry, "CPUID entry missing\n"); > + gpa_bits = (cpuid_entry->eax & GENMASK(23, 16)) >> 16; > + TEST_ASSERT_EQ((1UL << (gpa_bits - 1)), tdx_s_bit); > + > + /* Wait for guest to report rcx value */ > + tdx_run(vcpu); > + rcx = tdx_test_read_64bit_report_from_guest(vcpu); > + > + /* Wait for guest to report rdx value */ > + tdx_run(vcpu); > + rdx = tdx_test_read_64bit_report_from_guest(vcpu); > + > + /* Wait for guest to report r8 value */ > + tdx_run(vcpu); > + r8 = tdx_test_read_64bit_report_from_guest(vcpu); > + > + /* Wait for guest to report r9 value */ > + tdx_run(vcpu); > + r9 = tdx_test_read_64bit_report_from_guest(vcpu); > + > + /* Wait for guest to report r10 value */ > + tdx_run(vcpu); > + r10 = tdx_test_read_64bit_report_from_guest(vcpu); > + > + /* Wait for guest to report r11 value */ > + tdx_run(vcpu); > + r11 = tdx_test_read_64bit_report_from_guest(vcpu); > + > + ret_num_vcpus = r8 & 0xFFFFFFFF; > + ret_max_vcpus = (r8 >> 32) & 0xFFFFFFFF; > + > + /* first bits 5:0 of rcx represent the GPAW */ > + TEST_ASSERT_EQ(rcx & 0x3F, gpa_bits); > + /* next 63:6 bits of rcx is reserved and must be 0 */ > + TEST_ASSERT_EQ(rcx >> 6, 0); > + TEST_ASSERT_EQ(rdx, attributes); > + TEST_ASSERT_EQ(ret_num_vcpus, num_vcpus); > + TEST_ASSERT_EQ(ret_max_vcpus, vm_check_cap(vm, KVM_CAP_MAX_VCPUS)); > + /* VCPU_INDEX = i */ > + TEST_ASSERT_EQ(r9, i); The format for r9 is - 31:0 VCPU_INDEX - 62:32 Reserved to 0 Maybe add the check for reserved bits as other registers