* [PATCH v5 0/6] Enable TDX module extensions
@ 2026-11-11 3:48 Xu Yilun
2026-11-11 3:48 ` [PATCH v5 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper Xu Yilun
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Xu Yilun @ 2026-11-11 3:48 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: Kiryl Shutsemau, Rick Edgecombe, Dave Hansen, dave.hansen, kvm,
yilun.xu, yilun.xu, xiaoyao.li, sohil.mehta, adrian.hunter,
kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, nik.borisov
Hi,
In v4, Dave raised concerns about the first patch. I refactored it and
dropped its RBs, so it needs more review from TDX developers/reviewers.
Based on some offline discussion, I refactored changelog for patch 3/4/5.
I also simplified the changelog for the rest of the patches. But the RBs
for Patch 2-6 are kept, please speak out if you object.
If these patches (especially Patch 1) get good reviews and no major
concern, Dave, please help take a look. Otherwise, feel free to ignore.
== Changes ==
Dave pointed out that the Patch 1 contained too much information, so
that it was hard to find out why a wrapper is immediately needed. He
also pointed out that introducing the new structure, tdmr_info_pa_array,
is orthogonal. Drop the new structure part. Further simplify the
changelog to make explicit the reason for the wrapper.
Dave pointed out that the changelog of Patch 4 doesn't catch the point
of why DPAMT support is needed. He also pointed out that the
bisectability concern between Patch 3 & 4 is not well explained.
Refactor the changelog of Patch 3 & 4.
We had an internal discussion about simplifying the Quoting extension
initialization flow. The conclusion is to integrate the TDH.QUOTE.INIT
operations (and potentially similar operations for other extensions)
into TDH.EXT.INIT. This raised a question about how to handle errors for
TDH.EXT.INIT if one extension fails while other extensions are good. The
solution in this series is to fail the whole TDX initialization on any
error, though we planned a solution when a nice-to-have extension
appears in the future. Describe the error handling in the changelog of
Patch 5, no code change.
v4: https://lore.kernel.org/r/20261013-tdx-module-ext-v4-0-de5ffac65470@linux.intel.com
v3: https://lore.kernel.org/r/20261006-tdx-module-ext-v3-0-db52cb05b918@linux.intel.com
v2: https://lore.kernel.org/all/20260915102658.713079-1-yilun.xu@linux.intel.com/
v1: https://lore.kernel.org/all/20260821032920.256225-1-yilun.xu@linux.intel.com/
Quoting v2: https://lore.kernel.org/lkml/20260618081355.3253581-1-yilun.xu@linux.intel.com/
Quoting v1: https://lore.kernel.org/all/20260522034128.3144354-1-yilun.xu@linux.intel.com/
== Overview ==
To date, SEAMCALL execution must either complete quickly to avoid
stalling the host, or yield quickly at pre-defined interrupt
checkpoints. This is acceptable for the existing SEAMCALL leafs,
which perform simple, bounded operations.
However, some new features such as attestation and TD migration require
higher level security protocols inside the TDX module, which cannot fit
within that constraint. TDX solves this by making those operations
inherently preemptible and resumable like OS tasks. TDX provides a
separate SEAMCALL execution environment - the TDX module extensions -
for those operations.
This capability allows for higher-level SEAMCALL ABI design - like
"create a DICE-based attestation quote". Several new features, such as
DICE-based attestation, TDISP and TD migration, use SEAMCALL leafs
backed by the TDX module extensions.
The TDX module extensions need memory for their execution environment
to serve these SEAMCALL leafs, so they need extra setup steps during TDX
module initialization. The bulk of this series implements these setup
steps.
At runtime, the host invokes these SEAMCALL leafs just as normal ones -
if interrupted, simply re-invoke the leaf to resume.
For more information on TDX module extensions, please refer to [1].
[1] https://lore.kernel.org/lkml/20260618081355.3253581-1-yilun.xu@linux.intel.com/
== Branch stack ==
This is based on v7.4-rc1. You can find the full branch stack at [2].
The DICE part is in the full branch as an example for extensions. But it
does not include the other DICE feedbacks.
The full branch contains:
This series: Patch 1~7: This series, including this cover-letter.
Use case: Patch 8~N: The old DICE part as an example.
[2] https://github.com/intel-staging/tdx/tree/tdx-module-ext
== Test ==
We made agreement with TDX module team about some ABI changes, it
involves TDH.EXT.INIT, TDH.SYS.UPDATE.
This series is written against these unreleased changes. So we did 2
kinds of tests.
1. Add some workaround patches in kernel then test on existing TDX
module & real HW. See the full branch listed in "Branch stack"
section.
2. Customize the TDX module, mock the ABI change, run with the module
in a virtual machine environment with virtual SEAM support, no real
security in this environment.
---
Xu Yilun (6):
x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper
x86/virt/tdx: Configure add-on features on TDX module init
x86/virt/tdx: Add extra memory to TDX module for the extensions
x86/virt/tdx: Support DPAMT when adding memory for the extensions
x86/virt/tdx: Make TDX module initialize the extensions
x86/virt/tdx: Re-initialize the extensions on runtime TDX module update
arch/x86/include/asm/tdx.h | 1 +
arch/x86/include/asm/tdx_global_metadata.h | 4 +
arch/x86/virt/vmx/tdx/tdx.h | 2 +
arch/x86/virt/vmx/tdx/tdx.c | 234 ++++++++++++++++++++++++++--
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 14 ++
5 files changed, 244 insertions(+), 11 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20260916-tdx-module-ext-da1930f33c04
Best regards,
--
Xu Yilun <yilun.xu@linux.intel.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v5 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper
2026-11-11 3:48 [PATCH v5 0/6] Enable TDX module extensions Xu Yilun
@ 2026-11-11 3:48 ` Xu Yilun
2026-11-11 3:48 ` [PATCH v5 2/6] x86/virt/tdx: Configure add-on features on TDX module init Xu Yilun
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-11-11 3:48 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: Kiryl Shutsemau, Rick Edgecombe, Dave Hansen, dave.hansen, kvm,
yilun.xu, yilun.xu, xiaoyao.li, sohil.mehta, adrian.hunter,
kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, nik.borisov
TDH.SYS.CONFIG is currently not wrapped in a SEAMCALL wrapper because it
is not exposed to external callers (such as KVM), also because the
level of TDX ABI details needed to perform the call is low enough to
flow well with the calling code.
But as more configurations are being added to this SEAMCALL leaf, more
ABI details are leaked into the surrounding caller code, which decreases
the readability of the caller logic. The first case is that the Dynamic
PAMT (DPAMT) change [1] tweaks the R8 argument based on whether DPAMT is
supported by the loaded TDX module.
A follow-up case comes as the TDX architecture has evolved the ABI to
introduce new leaf versions. The host must select the version to call
based on what new features are supported by the loaded TDX module.
Before the version selection makes the caller logic even messier, move
the register ABI operations into a wrapper.
The original changelog was written by hand. AI was used to help correct
grammatical errors and polish individual sentences for better
readability.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Link: https://lore.kernel.org/all/20260904215841.303070-10-rick.p.edgecombe@intel.com/ # [1]
---
v5:
- Drop the tdmr_info_pa_array stuff (Dave)
- Simplify the changelog (Dave)
- Remove all Reviewed-by tags due to code change
- Better clarify the AI usage
v4:
- Cut the redundant opening words in the 1st paragraph (Rick)
- Collect Reviewed-by tags
v3:
- sizeof(tdmr_pa_array->phys[0]) instead of sizeof(u64) (Rick)
- Re-phrase the code comments for struct tdmr_info_pa_array (Tony)
- Refactor the changelog
- Collect Reviewed-by tags
v2:
- Remove TDH.SYS.UPDATE wrapper (Dave & Rick)
- Talk about the handling of in-memory ABIs for SEAMCALL wrappers
(Rick)
- Refactor the entire changelog according to Rick's suggestion (Rick)
- Add code comment for struct tdmr_info_pa_array (AI nitpicker)
- Use kernel data type for nr_tdmr_pa parameter (AI nitpicker)
v1:
- This patch is split out from the last series (Rick)
---
arch/x86/virt/vmx/tdx/tdx.c | 30 +++++++++++++++++++-----------
1 file changed, 19 insertions(+), 11 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 96ced0494b68..c7bd0b01786d 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1033,10 +1033,26 @@ static __init int construct_tdmrs(struct list_head *tmb_list,
#define TDX_SYS_CONFIG_DYNAMIC_PAMT BIT(16)
+static __init int tdx_sys_config(u64 *tdmr_pa_array, unsigned int nr_tdmr_pa,
+ u64 global_keyid)
+{
+ struct tdx_module_args args = {
+ .rcx = __pa(tdmr_pa_array),
+ .rdx = nr_tdmr_pa,
+ .r8 = global_keyid,
+ };
+
+ if (tdx_supports_dynamic_pamt(&tdx_sysinfo)) {
+ pr_info("Enable Dynamic PAMT\n");
+ args.r8 |= TDX_SYS_CONFIG_DYNAMIC_PAMT;
+ }
+
+ return seamcall_prerr(TDH_SYS_CONFIG, &args);
+}
+
static __init int config_tdx_module(struct tdmr_info_list *tdmr_list,
u64 global_keyid)
{
- struct tdx_module_args args = {};
u64 *tdmr_pa_array;
size_t array_sz;
int i, ret;
@@ -1058,16 +1074,8 @@ static __init int config_tdx_module(struct tdmr_info_list *tdmr_list,
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++)
tdmr_pa_array[i] = __pa(tdmr_entry(tdmr_list, i));
- args.rcx = __pa(tdmr_pa_array);
- args.rdx = tdmr_list->nr_consumed_tdmrs;
- args.r8 = global_keyid;
-
- if (tdx_supports_dynamic_pamt(&tdx_sysinfo)) {
- pr_info("Enable Dynamic PAMT\n");
- args.r8 |= TDX_SYS_CONFIG_DYNAMIC_PAMT;
- }
-
- ret = seamcall_prerr(TDH_SYS_CONFIG, &args);
+ ret = tdx_sys_config(tdmr_pa_array, tdmr_list->nr_consumed_tdmrs,
+ global_keyid);
/* Free the array as it is not required anymore. */
kfree(tdmr_pa_array);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 2/6] x86/virt/tdx: Configure add-on features on TDX module init
2026-11-11 3:48 [PATCH v5 0/6] Enable TDX module extensions Xu Yilun
2026-11-11 3:48 ` [PATCH v5 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper Xu Yilun
@ 2026-11-11 3:48 ` Xu Yilun
2026-11-11 3:48 ` [PATCH v5 3/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-11-11 3:48 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: Kiryl Shutsemau, Rick Edgecombe, Dave Hansen, dave.hansen, kvm,
yilun.xu, yilun.xu, xiaoyao.li, sohil.mehta, adrian.hunter,
kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, nik.borisov
The TDX architecture identifies some features that are off by default.
They are classified as add-on features because enabling them affects
existing TDX systems: they may change existing feature behavior, or
reserve more memory.
However, the impact of the first add-on features will be low enough to
pursue a simple approach - the kernel just enables them when supported.
If a feature with a very high overhead appears in the future, this can
be revisited.
The TDX module extends TDH.SYS.CONFIG with a new register argument to
specify which add-on features to enable. This new argument is a bitmap
that uses the same feature bits as TDX_FEATURES0. Note that Dynamic PAMT
is an exception: although it is an add-on feature, it is controlled via
a legacy, dedicated register argument [1].
Pass the bitmap of features that are supported by both kernel & TDX
module to TDH.SYS.CONFIG. Initially, the bitmap is 0. It will be updated
to contain specific feature bits as full kernel support lands.
The TDX module requires leaf version 1 for TDH.SYS.CONFIG when passing
the new bitmap argument. Set the version field to 1 if the argument is
used to enable any add-on feature, otherwise keep the version as 0. This
retains backward compatibility with older modules that don't recognize
version 1.
The original changelog was written by hand. AI was used to help correct
grammatical errors and polish individual sentences for better
readability.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Nikolay Borisov <nik.borisov@suse.com>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Link: https://lore.kernel.org/all/20260904215841.303070-10-rick.p.edgecombe@intel.com/ # [1]
---
v5:
- Simplify the changelog, saved 5 lines
- Better clarify the AI usage
v4:
- Add explanation why kernel enables add-on features solely on
available support in the 3rd paragraph of the changelog. (Rick)
- Collect Reviewed-by tags
v3:
- Update the stale changlog for bitmap arg of the wrapper (Chao)
- s/get_tdx_addon_features0()/get_tdx_usable_addon_features0() (Tony)
- Collect Reviewed-by tags
v2:
- Don't pass addon_features0 parameter around, get it in the wrappers
(Dave)
- Remove TDH.SYS.UPDATE wrapper (Dave & Rick)
- Drop the changelog section explaining why add-on feature enabling is
needed in this series, as it is a generally understood pattern (Rick)
- Add a note in the changelog that Dynamic PAMT is not enabled via the
new bitmap argument (Rick)
- Add __init tag for get_tdx_addon_features0() (AI nitpicker)
v1:
- Use tdx_module_args.version to assign SEAMCALL leaf versions (Dave)
- Remove DICE specific descriptions (Rick)
- Remove the global var tdx_addon_features0 (Chao)
- Add a Macro to collect kernel supported add-on feature bits (Rick)
- Changelog & code comments change
---
arch/x86/virt/vmx/tdx/tdx.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index c7bd0b01786d..3bfe78b4b538 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1033,9 +1033,19 @@ static __init int construct_tdmrs(struct list_head *tmb_list,
#define TDX_SYS_CONFIG_DYNAMIC_PAMT BIT(16)
+/* List all kernel-supported add-on features0 bits here */
+#define TDX_KERNEL_SUPPORTED_ADDON_FEATURES0 (0)
+
+static __init u64 get_tdx_usable_addon_features0(void)
+{
+ return tdx_sysinfo.features.tdx_features0 &
+ TDX_KERNEL_SUPPORTED_ADDON_FEATURES0;
+}
+
static __init int tdx_sys_config(u64 *tdmr_pa_array, unsigned int nr_tdmr_pa,
u64 global_keyid)
{
+ u64 addon_features0 = get_tdx_usable_addon_features0();
struct tdx_module_args args = {
.rcx = __pa(tdmr_pa_array),
.rdx = nr_tdmr_pa,
@@ -1047,6 +1057,15 @@ static __init int tdx_sys_config(u64 *tdmr_pa_array, unsigned int nr_tdmr_pa,
args.r8 |= TDX_SYS_CONFIG_DYNAMIC_PAMT;
}
+ /*
+ * Use SEAMCALL version 1 that supports add-on features if any are
+ * requested. Otherwise use version 0 for backward compatibility.
+ */
+ if (addon_features0) {
+ args.r9 = addon_features0;
+ args.version = 1;
+ }
+
return seamcall_prerr(TDH_SYS_CONFIG, &args);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 3/6] x86/virt/tdx: Add extra memory to TDX module for the extensions
2026-11-11 3:48 [PATCH v5 0/6] Enable TDX module extensions Xu Yilun
2026-11-11 3:48 ` [PATCH v5 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper Xu Yilun
2026-11-11 3:48 ` [PATCH v5 2/6] x86/virt/tdx: Configure add-on features on TDX module init Xu Yilun
@ 2026-11-11 3:48 ` Xu Yilun
2026-11-11 3:48 ` [PATCH v5 4/6] x86/virt/tdx: Support DPAMT when adding memory " Xu Yilun
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-11-11 3:48 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: Kiryl Shutsemau, Rick Edgecombe, Dave Hansen, dave.hansen, kvm,
yilun.xu, yilun.xu, xiaoyao.li, sohil.mehta, adrian.hunter,
kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, nik.borisov
TDX module extensions need memory for their execution environment to
serve SEAMCALL leafs. The TDX architecture implements the extensions
in such a way that they use the memory outside of SEAM range, so the
kernel should add the memory upfront at initialization time.
Introduce a new memory adding process backed by a new SEAMCALL leaf
TDH.EXT.MEM.ADD. The kernel queries TDX module how much memory needed,
allocates it, adds it to the module, and never gets it back. Note that
the process requires Dynamic PAMT backing. A subsequent patch adds the
support. This doesn't break bisectability cause the extensions can't be
enabled yet without a feature that uses them.
The TDX module accepts the memory in the form of an HPA array. This
array is passed via a single 64-bit SEAMCALL leaf parameter, which
encodes two values: the PFN of the container page holding the array, and
the number of entries in the array. Create a helper to encode this
format and name it after the TDX module term: HPA_LIST_INFO.
TDX module extensions consume tens of megabytes memory that will never
be returned to the host. Use contiguous page allocation to isolate these
large blocks entirely. This is a simple way to avoid permanent memory
fragmentation: requiring the full size to be contiguous is more
expensive than needed but should be good during the boot time. Print the
allocation amount on TDX module extensions initialization for
visibility.
The TDX module provides a metadata field "required_pages" to indicate
the required memory size. This metadata is meaningful only after the
add-on feature configuration. Don't read and cache the metadata in
tdx_sysinfo. Instead read it right before allocating memory for the
extensions, when it is guaranteed to be valid.
The original changelog was written by hand. AI was used to help correct
grammatical errors and polish individual sentences for better
readability.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
An alternative solution is to use a loop that gives memory on a memory
error code - TDX_EXT_MEMORY_POOL_REQUIRED, add one page per iteration
until TDH.EXT.INIT succeeds. Something like:
do {
ret = tdh_sys_init();
if (ret == TDX_EXT_MEMORY_POOL_REQUIRED)
tdh_ext_mem_add(); //single page
} while (ret == TDX_EXT_MEMORY_POOL_REQUIRED);
This approach is slightly simpler as we don't have to query the module
for the total memory, no memory pre-allocation or segmentation math.
But allocating a single 4K page per iteration may cause permanent memory
fragmentation.
v5:
- Add the DPAMT & bisectability note
- Shorten the "required_pages field" paragraph a little bit
- Collect Reviewed-by tags
- Better clarify the AI usage
v4:
- Rename memory_pool_required_pages to required_pages (Rick)
- Rename local var sysinfo_ext to ext
- Collect Reviewed-by tags
v3:
- Merge remaining pre-check operations from the previous patch (Rick)
- Don't save the metadata in tdx_sysinfo (Rick)
- Tweak the code comment for memory_pool_required_pages == 0 (Chao)
- Re-phrase the code comment for struct tdx_hpa_list (Tony)
- Put the error handling comments on top of memory add loop, to avoid
moving them around when DPAMT support is added in the following
patch.
- Print memory size before adding memory; it won't be freed afterward,
whether the operation succeeds or fails.
- Collect Reviewed-by tags
v2:
- Add code comments for container page allocation (Kiryl)
- Adjust comments/changelog for contiguous allocation of extensions
memory (Kiryl)
- s/PFN array/HPA array in commit log (Kiryl)
- State the alternative solution (Rick)
- Add code comment for struct tdx_hpa_list
v1:
- Fix return value for SEAMCALL wrappers (Chao)
- Print SEAMCALL error code for SEAMCALL wrappers (Xiaoyao)
- Rename local vars to make the ext memory adding loop clear (Rick)
- Remove input parameters for tdx_ext_mem_setup() (Kevin)
- Add a Macro for tdh_hpa_list size.
- Change the SEAMALL wrapper parameter type,
struct page *hpa_list => struct tdx_hpa_list *hpa_list
- changelog & code comments
---
arch/x86/include/asm/tdx.h | 1 +
arch/x86/include/asm/tdx_global_metadata.h | 4 +
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 138 ++++++++++++++++++++++++++++
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 14 +++
5 files changed, 158 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 1fcfaed515fe..89e7628796b0 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -37,6 +37,7 @@
#define TDX_FEATURES0_TD_PRESERVING BIT_ULL(1)
#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
#define TDX_FEATURES0_DYNAMIC_PAMT BIT_ULL(36)
+#define TDX_FEATURES0_EXT BIT_ULL(39)
#ifndef __ASSEMBLER__
diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
index 8a3cc1a2a41e..f18e63b671c2 100644
--- a/arch/x86/include/asm/tdx_global_metadata.h
+++ b/arch/x86/include/asm/tdx_global_metadata.h
@@ -47,6 +47,10 @@ struct tdx_sys_info_handoff {
u16 module_hv;
};
+struct tdx_sys_info_ext {
+ u32 required_pages;
+};
+
struct tdx_sys_info {
struct tdx_sys_info_version version;
struct tdx_sys_info_features features;
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index db209541d3cd..2adc5d31d258 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -50,6 +50,7 @@
#define TDH_SYS_UPDATE 53
#define TDH_PHYMEM_PAMT_ADD 58
#define TDH_PHYMEM_PAMT_REMOVE 59
+#define TDH_EXT_MEM_ADD 61
#define TDH_SYS_DISABLE 69
/* TDX page types */
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 3bfe78b4b538..865d8089d343 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1210,6 +1210,140 @@ static __init int init_tdmrs(struct tdmr_info_list *tdmr_list)
return 0;
}
+#define TDX_HPA_LIST_MAX_NR_PAGES (PAGE_SIZE / sizeof(u64))
+
+/*
+ * TDX module in-memory ABI to specify the memory being added to the TDX
+ * module. An array of HPAs, each element points to a 4K page.
+ */
+struct tdx_hpa_list {
+ u64 phys[TDX_HPA_LIST_MAX_NR_PAGES];
+};
+
+static_assert(sizeof(struct tdx_hpa_list) == PAGE_SIZE);
+
+#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3)
+#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
+#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
+
+static __init u64 to_hpa_list_info(struct tdx_hpa_list *hpa_list,
+ unsigned int nr_pages)
+{
+ return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) |
+ FIELD_PREP(HPA_LIST_INFO_PFN, PFN_DOWN(__pa(hpa_list))) |
+ FIELD_PREP(HPA_LIST_INFO_LAST_ENTRY, nr_pages - 1);
+}
+
+static __init int tdx_ext_mem_add(struct tdx_hpa_list *hpa_list,
+ unsigned int nr_pages)
+{
+ struct tdx_module_args args = {
+ .rcx = to_hpa_list_info(hpa_list, nr_pages),
+ };
+ u64 ret;
+
+ do {
+ /*
+ * The TDX module overwrites RCX to track progress when this
+ * SEAMCALL leaf is interrupted. Use seamcall_ret() to save and
+ * pass the updated value back on retry.
+ */
+ ret = seamcall_ret(TDH_EXT_MEM_ADD, &args);
+ } while (ret == TDX_INTERRUPTED_RESUMABLE);
+
+ if (ret != TDX_SUCCESS) {
+ pr_err("TDH.EXT.MEM.ADD failed: 0x%016llx\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static __init int tdx_ext_mem_setup(void)
+{
+ struct tdx_hpa_list *hpa_list;
+ struct tdx_sys_info_ext ext;
+ unsigned int added_pages;
+ struct page *page;
+ int ret;
+
+ ret = get_tdx_sys_info_ext(&ext);
+ if (ret)
+ return ret;
+
+ /*
+ * Skip the memory setup if no memory is required. This may happen when
+ * no add-on features requiring TDX module extensions are configured
+ * via TDH.SYS.CONFIG.
+ */
+ if (!ext.required_pages)
+ return 0;
+
+ /*
+ * Allocate the container page for the HPA_LIST. tdx_hpa_list is
+ * guaranteed to be page-sized by static_assert(), so kzalloc()
+ * guarantees the page alignment.
+ */
+ hpa_list = kzalloc_obj(*hpa_list);
+ if (!hpa_list)
+ return -ENOMEM;
+
+ /*
+ * Memory for TDX module extensions is never reclaimed and can be tens
+ * of megabytes. Allocating a physically contiguous chunk is a simple
+ * way to avoid permanent memory fragmentation: requiring the full size
+ * to be contiguous is more expensive than needed but should be good
+ * during the boot time.
+ */
+ page = alloc_contig_pages(ext.required_pages, GFP_KERNEL,
+ numa_mem_id(), &node_online_map);
+ if (!page) {
+ ret = -ENOMEM;
+ goto out_free_hpa_list;
+ }
+
+ /* Print the amount so users know the cost. */
+ pr_info("%lu KB allocated for TDX module extensions\n",
+ ext.required_pages * PAGE_SIZE / 1024);
+
+ /*
+ * Following TDX operations shouldn't fail, and if they do, things are
+ * broken enough that complex error handling isn't worth it.
+ * Intentionally leak all pages on failure, including un-added pages.
+ */
+ added_pages = 0;
+ while (added_pages < ext.required_pages) {
+ unsigned int chunk_pages = min(ext.required_pages - added_pages,
+ TDX_HPA_LIST_MAX_NR_PAGES);
+ struct page *chunk = page + added_pages;
+ unsigned int i;
+
+ for (i = 0; i < chunk_pages; i++)
+ hpa_list->phys[i] = page_to_phys(chunk + i);
+
+ ret = tdx_ext_mem_add(hpa_list, chunk_pages);
+ if (ret) {
+ WARN(1, "TDX module rejected memory for extensions, stranded all pages\n");
+ break;
+ }
+
+ added_pages += chunk_pages;
+ }
+
+out_free_hpa_list:
+ kfree(hpa_list);
+
+ return ret;
+}
+
+static __init int init_tdx_module_extensions(void)
+{
+ if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
+ return 0;
+
+ return tdx_ext_mem_setup();
+}
+
static __init int init_tdx_module(void)
{
int ret;
@@ -1268,6 +1402,10 @@ static __init int init_tdx_module(void)
if (ret)
goto err_reset_pamts;
+ ret = init_tdx_module_extensions();
+ if (ret)
+ goto err_reset_pamts;
+
pr_info("%lu KB allocated for PAMT\n", tdmrs_count_pamt_kb(&tdx_tdmr_list));
out_put_tdxmem:
diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
index 98ebf17aab1c..9ab4d021017f 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -125,6 +125,20 @@ static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff
return 0;
}
+static __init int get_tdx_sys_info_ext(struct tdx_sys_info_ext *sysinfo_ext)
+{
+ int ret;
+ u64 val;
+
+ ret = read_sys_metadata_field(0x3100000200000000, &val);
+ if (ret)
+ return ret;
+
+ sysinfo_ext->required_pages = val;
+
+ return 0;
+}
+
static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
{
int ret = 0;
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 4/6] x86/virt/tdx: Support DPAMT when adding memory for the extensions
2026-11-11 3:48 [PATCH v5 0/6] Enable TDX module extensions Xu Yilun
` (2 preceding siblings ...)
2026-11-11 3:48 ` [PATCH v5 3/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
@ 2026-11-11 3:48 ` Xu Yilun
2026-11-11 3:48 ` [PATCH v5 5/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
2026-11-11 3:48 ` [PATCH v5 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-11-11 3:48 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: Kiryl Shutsemau, Rick Edgecombe, Dave Hansen, dave.hansen, kvm,
yilun.xu, yilun.xu, xiaoyao.li, sohil.mehta, adrian.hunter,
kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, nik.borisov
The TDX module uses PAMT to track some state for private pages. The
memory for the extensions needs PAMT backing cause the TDX module
transitions it to private pages when accepting it.
The Dynamic PAMT (DPAMT) feature allows saving memory by allocating
4KB-level PAMT dynamically. The kernel must install DPAMT before adding
any 4KB pages to the TDX module. Although the memory for the extensions
is tens of megabytes and the kernel allocates it in a large chunk, the
TDX module accepts it in the form of an HPA array, which specifies each
page at 4KB granularity. Thus the kernel still needs to install DPAMT
for this memory, otherwise TDH.EXT.MEM.ADD would fail to execute.
That said, don't worry about bisectability cause the extensions can't be
enabled yet without a feature that uses them.
The kernel has helpers to install DPAMT for a 4KB page. Call
tdx_pamt_get() for each extension page before adding it to the TDX
module. Normally, these operations should not fail, and if they do,
intentionally keep the error handling as simple as before - leak all
pages, including the installed DPAMT.
The original changelog was written by hand. AI was used to help correct
grammatical errors and polish individual sentences for better
readability.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
---
v5:
- Clarify that the bisectability won't be hurt
- Refactor the first 2 paragraphs.
- Collect Reviewed-by tags
- Better clarify the AI usage
v4:
- Explain in changelog that TDH.EXT.MEM.ADD won't work without this
patch when DPAMT is on by default. (Tony)
- Move this patch right after the one that implements TDH.EXT.MEM.ADD
- Collect Reviewed-by tags
v3:
- New patch
---
arch/x86/virt/vmx/tdx/tdx.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 865d8089d343..3440f1878978 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1318,8 +1318,15 @@ static __init int tdx_ext_mem_setup(void)
struct page *chunk = page + added_pages;
unsigned int i;
- for (i = 0; i < chunk_pages; i++)
+ for (i = 0; i < chunk_pages; i++) {
+ ret = tdx_pamt_get(page_to_pfn(chunk + i), NULL);
+ if (ret) {
+ WARN(1, "DPAMT setup error for extensions, stranded all pages\n");
+ goto out_free_hpa_list;
+ }
+
hpa_list->phys[i] = page_to_phys(chunk + i);
+ }
ret = tdx_ext_mem_add(hpa_list, chunk_pages);
if (ret) {
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 5/6] x86/virt/tdx: Make TDX module initialize the extensions
2026-11-11 3:48 [PATCH v5 0/6] Enable TDX module extensions Xu Yilun
` (3 preceding siblings ...)
2026-11-11 3:48 ` [PATCH v5 4/6] x86/virt/tdx: Support DPAMT when adding memory " Xu Yilun
@ 2026-11-11 3:48 ` Xu Yilun
2026-11-11 3:48 ` [PATCH v5 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-11-11 3:48 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: Kiryl Shutsemau, Rick Edgecombe, Dave Hansen, dave.hansen, kvm,
yilun.xu, yilun.xu, xiaoyao.li, sohil.mehta, adrian.hunter,
kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, nik.borisov
Continue fleshing out the extension initialization process. At this
point the kernel has provided all memory and the TDX module is ready to
initialize the extensions on the memory.
Initialize TDX module extensions via TDH.EXT.INIT. This process takes on
the order of 100's of milliseconds, during which the TDX module loads
its internal extension image into this memory and builds the execution
environment. Once initialized, those add-on features can use their
SEAMCALL leafs normally.
Note that TDH.EXT.INIT builds all configured extensions. The SEAMCALL
leaf fails if any extension fails to initialize. The kernel does simple
error handling - just fails the whole TDX initialization on any error.
This is OK cause all extensions are considered necessary for security.
If a nice-to-have extension appears in the future, this can be
revisited. A potential solution is to have a new metadata field
indicating which extensions get enabled/failed, then the kernel decides
whether to continue or abort.
The original changelog was written by hand. AI was used to help correct
grammatical errors and polish individual sentences for better
readability.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Xiaoyao Li <xiaoyao.li@intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Adrian Hunter <adrian.hunter@intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
v5:
- A new paragraph to describe the error handling.
- Refactor the existing changelog.
- Better clarify the AI usage.
v4:
- Specify what TDH.EXT.INIT is doing in the changelog (Rick)
- Collect Reviewed-by tags
v1:
- Fix return value for SEAMCALL wrappers (Chao)
- Print SEAMCALL error code for SEAMCALL wrappers (Xiaoyao)
- Changelog & code comments
---
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 25 ++++++++++++++++++++++++-
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 2adc5d31d258..cac0a5abbff1 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -50,6 +50,7 @@
#define TDH_SYS_UPDATE 53
#define TDH_PHYMEM_PAMT_ADD 58
#define TDH_PHYMEM_PAMT_REMOVE 59
+#define TDH_EXT_INIT 60
#define TDH_EXT_MEM_ADD 61
#define TDH_SYS_DISABLE 69
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 3440f1878978..3a1ba4e73cfe 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1343,12 +1343,35 @@ static __init int tdx_ext_mem_setup(void)
return ret;
}
+static __init int tdx_ext_init(void)
+{
+ struct tdx_module_args args = {};
+ u64 ret;
+
+ do {
+ ret = seamcall(TDH_EXT_INIT, &args);
+ } while (ret == TDX_INTERRUPTED_RESUMABLE);
+
+ if (ret != TDX_SUCCESS) {
+ pr_err("TDH.EXT.INIT failed: 0x%016llx\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
static __init int init_tdx_module_extensions(void)
{
+ int ret;
+
if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
return 0;
- return tdx_ext_mem_setup();
+ ret = tdx_ext_mem_setup();
+ if (ret)
+ return ret;
+
+ return tdx_ext_init();
}
static __init int init_tdx_module(void)
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update
2026-11-11 3:48 [PATCH v5 0/6] Enable TDX module extensions Xu Yilun
` (4 preceding siblings ...)
2026-11-11 3:48 ` [PATCH v5 5/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
@ 2026-11-11 3:48 ` Xu Yilun
5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-11-11 3:48 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: Kiryl Shutsemau, Rick Edgecombe, Dave Hansen, dave.hansen, kvm,
yilun.xu, yilun.xu, xiaoyao.li, sohil.mehta, adrian.hunter,
kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, nik.borisov
Runtime TDX module update introduces a mechanism to update the module
firmware while preserving and restoring TDX operations. The extensions
functionalities should also be re-initialized as part of the restoration
process.
The TDX architecture supports an update flow which allows updated
extensions to consume more memory than their original boot-time
requirement. So the arch defines the extensions re-initialization flow
the same as boot-up initialization: the host queries TDX module how much
additional memory needed, allocates it, adds it to the module via
TDH.EXT.MEM.ADD, then re-initializes the extensions via TDH.EXT.INIT.
Linux runs the updates in stop_machine() context, which prevents memory
allocation. So for Linux, a compatible update must not install updated
extensions that require additional memory.
Given that the memory for the extensions must not increase, the
re-initialization skips the memory adding steps. It is simplified as:
- Check if the extensions are supported via TDX_FEATURES0_EXT. If not,
skip the extensions re-initialization.
- Re-initialize the extensions via TDH.EXT.INIT. The SEAMCALL leaf
will fail if the updated extensions require more memory, which
indicates the update image is not compatible.
The original changelog was written by hand. AI was used to help correct
grammatical errors and polish individual sentences for better
readability.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
v5:
- Better clarify the AI usage
v4:
- Collect Reviewed-by tags
v3:
- Add this patch back cause the latest discussion decides the
re-initialization won't be integrated in TDH.SYS.UPDATE, a dedicated
TDH.EXT.INIT call is still needed (Dave & Rick)
- Drop the ext_required check (Rick)
- Rename the reinit function as reinit_tdx_module_extensions() (Tony)
- Move the reinit function right under tdh_sys_update() (Rick)
v2:
- Removed this patch cause the TDX module is expected to re-initialize
the extensions on TDH.SYS.UPDATE
v1:
- Don't update the extensions metadata any more, only check the
metadata originated at boot time.
- Remove memory_pool_required_pages check, let TDH.EXT.INIT fail if
more memory required.
- Changelog & code comments
---
arch/x86/virt/vmx/tdx/tdx.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 3a1ba4e73cfe..afdc6b5e667d 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1343,7 +1343,7 @@ static __init int tdx_ext_mem_setup(void)
return ret;
}
-static __init int tdx_ext_init(void)
+static int tdx_ext_init(void)
{
struct tdx_module_args args = {};
u64 ret;
@@ -1374,6 +1374,19 @@ static __init int init_tdx_module_extensions(void)
return tdx_ext_init();
}
+/*
+ * Don't update the memory requirement metadata or try memory allocation in
+ * stop_machine(). If an incompatible update requires more memory, let the
+ * extensions re-initialization fail.
+ */
+static int reinit_tdx_module_extensions(void)
+{
+ if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
+ return 0;
+
+ return tdx_ext_init();
+}
+
static __init int init_tdx_module(void)
{
int ret;
@@ -1557,6 +1570,10 @@ int tdx_module_run_update(void)
if (ret)
return ret;
+ ret = reinit_tdx_module_extensions();
+ if (ret)
+ return ret;
+
ret = get_tdx_sys_info_version(&tdx_sysinfo.version);
/*
* Only fails if there is something unexpected
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-11 3:57 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 3:48 [PATCH v5 0/6] Enable TDX module extensions Xu Yilun
2026-11-11 3:48 ` [PATCH v5 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper Xu Yilun
2026-11-11 3:48 ` [PATCH v5 2/6] x86/virt/tdx: Configure add-on features on TDX module init Xu Yilun
2026-11-11 3:48 ` [PATCH v5 3/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
2026-11-11 3:48 ` [PATCH v5 4/6] x86/virt/tdx: Support DPAMT when adding memory " Xu Yilun
2026-11-11 3:48 ` [PATCH v5 5/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
2026-11-11 3:48 ` [PATCH v5 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox