From c9fc85f1e44e4e933c7e6702b100b9505715f6a8 Mon Sep 17 00:00:00 2001 From: Xu Yilun Date: Mon, 21 Sep 2026 20:39:23 +0800 Subject: [PATCH] x86/virt/tdx: Formalize SEAMCALL leaf version encoding support The TDX architecture includes a syscall-like ABI for OSes to communicate with SEAM mode software. This ABI has the concept of a "leaf". Each leaf is roughly analogous to a Linux syscall: it has a set of register arguments and does one logical thing like adding a page of memory to a VM or running a VM. The TDX architecture refers to this interface function as a "SEAMCALL leaf". Just like syscalls, the TDX architecture wants to evolve the ABI to extend functionality while keeping compatibility. But unlike syscalls, which do this by picking a totally new syscall number, the ABI encodes the "version" number directly into the bits of a register argument. So instead of openatN being whatever the next free number is, the SEAMCALL leaf number and version number are encoded into certain bits in how the RAX register is defined in the ABI. In Linux, several seamcall*() wrappers have been introduced to invoke SEAMCALL leafs. They all take a u64 "fn" argument for the leaf number which eventually gets set in the register. As above, the version number lives in the same register as the leaf number. So callers that want to select a specific version of the leaf, can jam it in the right place in the register by passing it in the "fn" argument. Today only the caller of TDH.VP.INIT does this hack, but future kernel changes will need to select versions for more SEAMCALL leafs. So a less hacky solution is needed. Explicitly define the arguments for seamcall*() wrappers: - Add a build-time assertion to ensure "fn" only contains valid SEAMCALL leaf number bits. Note the P-SEAMLDR selector bit (bit 63) is part of the leaf number for SEAM loader calls, so it is allowed. This also means the "fn" can't be a narrower type, such as u16, to exclude unrelated bits. - Add a "version" field in struct tdx_module_args [1], so most existing callers get a default "version == 0" behavior without code churn. Update the TDH.VP.INIT caller to specify the version descriptively. Encode the leaf number and tdx_module_args.version into RAX in assembly, because this is the place where the "fn" and struct tdx_module_args fields are marshaled into registers. AI was used under supervision to review code and workshop logs. In particular, it helped evaluate the assembly changes. Signed-off-by: Xu Yilun Signed-off-by: Dave Hansen Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: Nikolay Borisov Reviewed-by: Tony Lindgren Reviewed-by: Rick Edgecombe Reviewed-by: Kishen Maloor Link: https://lore.kernel.org/kvm/4f4b0f29-424b-45ed-8cfd-c77da2ea390f@intel.com/ # [1] Link: https://patch.msgid.link/20260921-seamcall-version-v7-1-cf05fe76b467@linux.intel.com --- arch/x86/include/asm/shared/tdx.h | 5 +++++ arch/x86/kernel/asm-offsets.c | 1 + arch/x86/virt/vmx/tdx/seamcall_internal.h | 19 +++++++++++++++++++ arch/x86/virt/vmx/tdx/tdx.c | 5 +++-- arch/x86/virt/vmx/tdx/tdx.h | 8 -------- arch/x86/virt/vmx/tdx/tdxcall.S | 10 ++++++++-- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h index f20e91d7ac35..f2cfa71cf0ea 100644 --- a/arch/x86/include/asm/shared/tdx.h +++ b/arch/x86/include/asm/shared/tdx.h @@ -143,6 +143,11 @@ struct tdx_module_args { u64 rbx; u64 rdi; u64 rsi; + /* + * Leaf ABI version. Note that it gets encoded into RAX along with the + * leaf number. + */ + u8 version; }; /* Used to communicate with the TDX module */ diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c index 081816888f7a..b3c00ff4d819 100644 --- a/arch/x86/kernel/asm-offsets.c +++ b/arch/x86/kernel/asm-offsets.c @@ -95,6 +95,7 @@ static void __used common(void) OFFSET(TDX_MODULE_rbx, tdx_module_args, rbx); OFFSET(TDX_MODULE_rdi, tdx_module_args, rdi); OFFSET(TDX_MODULE_rsi, tdx_module_args, rsi); + OFFSET(TDX_MODULE_version, tdx_module_args, version); BLANK(); OFFSET(BP_scratch, boot_params, scratch); diff --git a/arch/x86/virt/vmx/tdx/seamcall_internal.h b/arch/x86/virt/vmx/tdx/seamcall_internal.h index be5f446467df..051ad2d45cab 100644 --- a/arch/x86/virt/vmx/tdx/seamcall_internal.h +++ b/arch/x86/virt/vmx/tdx/seamcall_internal.h @@ -11,6 +11,7 @@ #ifndef _X86_VIRT_SEAMCALL_INTERNAL_H #define _X86_VIRT_SEAMCALL_INTERNAL_H +#include #include #include #include @@ -23,9 +24,27 @@ u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args); typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args); +/* + * SEAMCALL leaf: + * + * Bit 15:0 Leaf number + * Bit 23:16 Leaf ABI version number + * Bit 24 Pending interrupts detection mode + * Bit 63 1 for P-SEAMLDR leaf, 0 for TDX module leaf + */ +#define SEAMCALL_LEAF_MASK GENMASK_U64(15, 0) +#define SEAMCALL_SEAMLDR_MASK BIT_U64(63) + static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn, struct tdx_module_args *args) { + /* + * fn contains leaf number for TDX module calls and P-SEAMLDR calls. + * Other fields in SEAMCALL leaf like leaf ABI version number are in + * struct tdx_module_args. + */ + BUILD_BUG_ON(fn & ~(SEAMCALL_LEAF_MASK | SEAMCALL_SEAMLDR_MASK)); + lockdep_assert_preemption_disabled(); /* diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 063574ed8625..96ced0494b68 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1959,10 +1959,11 @@ u64 tdh_vp_init(struct tdx_vp *vp, u64 initial_rcx, u32 x2apicid) .rcx = vp->tdvpr_pa, .rdx = initial_rcx, .r8 = x2apicid, + /* apicid requires version == 1. */ + .version = 1, }; - /* apicid requires version == 1. */ - return seamcall(TDH_VP_INIT | (1ULL << TDX_VERSION_SHIFT), &args); + return seamcall(TDH_VP_INIT, &args); } EXPORT_SYMBOL_FOR_KVM(tdh_vp_init); diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index a886c54decaa..db209541d3cd 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -52,14 +52,6 @@ #define TDH_PHYMEM_PAMT_REMOVE 59 #define TDH_SYS_DISABLE 69 -/* - * SEAMCALL leaf: - * - * Bit 15:0 Leaf number - * Bit 23:16 Version number - */ -#define TDX_VERSION_SHIFT 16 - /* TDX page types */ #define PT_NDA 0x0 #define PT_RSVD 0x1 diff --git a/arch/x86/virt/vmx/tdx/tdxcall.S b/arch/x86/virt/vmx/tdx/tdxcall.S index 016a2a1ec1d6..a194e83613e7 100644 --- a/arch/x86/virt/vmx/tdx/tdxcall.S +++ b/arch/x86/virt/vmx/tdx/tdxcall.S @@ -45,8 +45,14 @@ .macro TDX_MODULE_CALL host:req ret=0 saved=0 FRAME_BEGIN - /* Move Leaf ID to RAX */ - mov %rdi, %rax + /* Leaf ABI version -> RAX[23:16]. Zero rest of RAX. */ + movzbl TDX_MODULE_version(%rsi), %eax + shl $16, %eax + /* + * Combine leaf number arg and leaf ABI version into RAX, they don't + * overlap. + */ + or %rdi, %rax /* Move other input regs from 'struct tdx_module_args' */ movq TDX_MODULE_rcx(%rsi), %rcx