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 <yilun.xu@linux.intel.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Nikolay Borisov <nik.borisov@suse.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Reviewed-by: Kishen Maloor <kishen.maloor@intel.com>
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
This commit is contained in:
Xu Yilun 2026-09-21 20:39:23 +08:00 committed by Dave Hansen
parent e61c837817
commit c9fc85f1e4
6 changed files with 36 additions and 12 deletions

View File

@ -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 */

View File

@ -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);

View File

@ -11,6 +11,7 @@
#ifndef _X86_VIRT_SEAMCALL_INTERNAL_H
#define _X86_VIRT_SEAMCALL_INTERNAL_H
#include <linux/bitfield.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <asm/archrandom.h>
@ -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();
/*

View File

@ -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);

View File

@ -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

View File

@ -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