mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/6] Enable TDX module extensions
@ 2026-10-05 17:41 Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 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-10-05 17:41 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,

This is another respin of the TDX module extensions series. Thank you
all for the design discussion and architectural review. During the last
version, some architecture discussions lead to changes. And the need to
support DPAMT creates more touch points. But the all the design issues
are settled, we don't have any major opens.

So Kiryl, Rick and TDX developers, please help review. If there is no
major change, I hope to get your RBs and then ask Dave to look at this
version.

== Changes ==

One change is that the extensions re-initialization patch is picked back
into this series. In v2, the extensions re-initialization operation
(TDH.EXT.INIT) was expected to be integrated in TDH.SYS.UPDATE. Host
didn't have to explicitly invoke it on update anymore. But later an
off-list discussion concludes that the separation of TDH.SYS.UPDATE &
TDH.EXT.INIT brings more flexibility for needs from different types of
VMMs, such as reducing the black-out time of update, allowing for new
feature enabling... So now the host needs to call TDH.EXT.INIT right
after TDH.SYS.UPDATE to restore the extensions functionalities.

However the add-on feature re-configuration is orthogonal to the
update flow change. We should still start with the basic update
functionality that restores the previous settings. Re-configuring new
feature is a separate functionality and the use case is unclear now.
So I still expect that a TDH.SYS.UPDATE v0 would restore add-on features
that was configured during boot.

Rick pointed out that if TDH.EXT.INIT could always return a reasonable
return code, we don't have to query ext_required metadata. A fix for the
ABI is: TDH.EXT.INIT should return success if no extensions are
required. We reached agreement with TDX module team and removed
ext_required. After ext_required removal, the v2:Patch 3 became too
trivial so merge it into v2:Patch 4.

Rick raised a question that a single 4K allocation per iteration on
boot may not cause much fragmentation. Kiryl answered
CONFIG_SHUFFLE_PAGE_ALLOCATOR may affect the actual behavior but we'd
better verify this in practice. The test result shows the allocation is
affected by several aspects, e.g. MPOL_INTERLEAVED, per-CPU page cache.
The allocated pages were scattered across many more page blocks than
expected.

The DPAMT is now queued in tip x86/tdx and will be default on in next
rc1. Adding memory to the extensions without first installing DPAMT will
trigger SEAMCALL failure and in turn fail the whole TDX module
initialization. Add a new patch to support DPAMT.

Other changes:
  - Patch 1: sizeof(tdmr_pa_array->phys[0]) instead of sizeof(u64)
    (Rick)

  - Patch 1: Re-phrase the code comments for struct tdmr_info_pa_array
    (Tony)

  - Patch 2: Update the stale changlog for bitmap arg of the wrapper
    (Chao)

  - Patch 2: s/get_tdx_addon_features0()/get_tdx_usable_addon_features0()
    (Tony)

  - Patch 3: Don't save the metadata in tdx_sysinfo (Rick)

  - Patch 3: Tweak the code comment for memory_pool_required_pages == 0
    (Chao)

  - Patch 3: Re-phrase the code comment for struct tdx_hpa_list (Tony)

  - Patch 5: Rename the reinit function as reinit_tdx_module_extensions()
    (Tony)

  - Patch 5: Move the extensions re-init under TDH.SYS.UPDATE (Rick)

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 tip x86/tdx. 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

---
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: Make TDX module initialize the extensions
      x86/virt/tdx: Re-initialize the extensions on runtime TDX module update
      x86/virt/tdx: Support DPAMT when adding memory for the extensions

 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                 | 252 ++++++++++++++++++++++++++--
 arch/x86/virt/vmx/tdx/tdx_global_metadata.c |  14 ++
 5 files changed, 259 insertions(+), 14 deletions(-)
---
base-commit: 5b8212d45d99b77c84e3ad305a295e9e65d5ef20
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 v3 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper
  2026-10-05 17:41 [PATCH v3 0/6] Enable TDX module extensions Xu Yilun
@ 2026-10-05 17:41 ` Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 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-10-05 17:41 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

In Linux, SEAMCALL wrappers are introduced to avoid broad SEAMCALL
access by exposing only a selection of SEAMCALL leafs, but also to
abstract the SEAMCALL register ABIs. The abstraction improves
readability and reuse for SEAMCALL leafs that are called multiple times.

Some SEAMCALL leafs are not explicitly wrapped because the level of TDX
ABI details needed to perform the call is low enough to flow well with
the calling code.

For some of the currently unwrapped SEAMCALL leafs, the TDX architecture
has evolved the ABI to introduce new leaf versions. The host can select
the version to call based on what is supported by the loaded TDX module.
When future kernel implements this version selection, more ABI details
will leak into the surrounding caller code, which decreases the
readability of the caller logic. To keep the ABI details contained, move
the SEAMCALL leaf that will need version selection into a wrapper.

When defining the wrapper, the cleanest separation would be to have
kernel data types for the SEAMCALL wrapper arguments, and have them
marshaled into SEAMCALL leaf ABI types (often u64s) inside the wrapper.
This works for many SEAMCALL leafs but becomes cumbersome when the
register ABI type is a physical address which points to a buffer for an
in-memory ABI. If the SEAMCALL wrapper only accepts kernel data types,
it may need duplicate buffer allocation and copies to match the
in-memory ABI. Another solution is to define a named helper structure
that mirrors the in-memory ABI, populate it in a separate flow, then
pass it to the SEAMCALL wrapper. struct seamldr_params is an existing
example of this pattern.

TDH.SYS.CONFIG requires a list of TDMR information in the form of a PA
array. The PA array is the in-memory ABI. Create a structure for the PA
array, use it as the argument when creating the wrapper for
TDH.SYS.CONFIG.

For readability, adjust a bit of the existing PA array size calculation:
use the new PA array structure's element type instead of hardcoding u64.

Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Nikolay Borisov <nik.borisov@suse.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
---
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 | 46 +++++++++++++++++++++++++++++++--------------
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 96ced0494b68..b099aa4056f5 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1033,11 +1033,36 @@ static __init int construct_tdmrs(struct list_head *tmb_list,
 
 #define TDX_SYS_CONFIG_DYNAMIC_PAMT	BIT(16)
 
+/*
+ * TDX module in-memory ABI to specify the TD Memory Regions (TDMRs) and their
+ * associated PAMT memory. An array of HPAs, each element points to a
+ * TDMR_INFO, see struct tdmr_info.
+ */
+struct tdmr_info_pa_array {
+	DECLARE_FLEX_ARRAY(u64, phys);
+};
+
+static __init int tdx_sys_config(struct tdmr_info_pa_array *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;
+	struct tdmr_info_pa_array *tdmr_pa_array;
 	size_t array_sz;
 	int i, ret;
 
@@ -1046,7 +1071,8 @@ static __init int config_tdx_module(struct tdmr_info_list *tdmr_list,
 	 * addresses of each TDMR.  The array itself also has certain
 	 * alignment requirement.
 	 */
-	array_sz = tdmr_list->nr_consumed_tdmrs * sizeof(u64);
+	array_sz = tdmr_list->nr_consumed_tdmrs *
+		sizeof(tdmr_pa_array->phys[0]);
 	array_sz = roundup_pow_of_two(array_sz);
 	if (array_sz < TDMR_INFO_PA_ARRAY_ALIGNMENT)
 		array_sz = TDMR_INFO_PA_ARRAY_ALIGNMENT;
@@ -1056,18 +1082,10 @@ static __init int config_tdx_module(struct tdmr_info_list *tdmr_list,
 		return -ENOMEM;
 
 	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;
-	}
+		tdmr_pa_array->phys[i] = __pa(tdmr_entry(tdmr_list, i));
 
-	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	[flat|nested] 7+ messages in thread

* [PATCH v3 2/6] x86/virt/tdx: Configure add-on features on TDX module init
  2026-10-05 17:41 [PATCH v3 0/6] Enable TDX module extensions Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper Xu Yilun
@ 2026-10-05 17:41 ` Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 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-10-05 17:41 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
but can be enabled by the host during TDX module initialization. They
are classified as add-on features because enabling them affects existing
TDX systems: they may change existing feature behavior, or reserve more
memory.

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].

The kernel needs to enable these add-on features when it supports them.
Add a get_tdx_usable_addon_features0() helper to return the bitmap of
the add-on features that the module & kernel both support. Initially,
this helper returns 0. It will be updated to return specific feature
bits as full kernel support lands. Pass this bitmap as an argument to
TDH.SYS.CONFIG.

The TDX module requires SEAMCALL leaf version 1 for TDH.SYS.CONFIG when
passing the new bitmap argument. A previous change [2] supports the
versioned SEAMCALL leafs by adding a "version" field in
struct tdx_module_args. Set the version field to 1 if the new bitmap
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.

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>
Link: https://lore.kernel.org/all/20260904215841.303070-10-rick.p.edgecombe@intel.com/ # [1]
Link: https://lore.kernel.org/all/20260921-seamcall-version-v7-1-cf05fe76b467@linux.intel.com/ # [2]
---
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 b099aa4056f5..f08278a47aff 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1042,9 +1042,19 @@ struct tdmr_info_pa_array {
 	DECLARE_FLEX_ARRAY(u64, phys);
 };
 
+/* 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(struct tdmr_info_pa_array *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,
@@ -1056,6 +1066,15 @@ static __init int tdx_sys_config(struct tdmr_info_pa_array *tdmr_pa_array,
 		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	[flat|nested] 7+ messages in thread

* [PATCH v3 3/6] x86/virt/tdx: Add extra memory to TDX module for the extensions
  2026-10-05 17:41 [PATCH v3 0/6] Enable TDX module extensions Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 2/6] x86/virt/tdx: Configure add-on features on TDX module init Xu Yilun
@ 2026-10-05 17:41 ` Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 4/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-10-05 17:41 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.

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 "memory_pool_required_pages" to
indicate the required memory size. This metadata is present only when the
module supports the extensions and is meaningful only after the add-on
feature configuration. Don't read and cache the metadata in tdx_sysinfo
at the very beginning of TDX module initialization. Instead read it
right before allocating memory for the extensions, when it is guaranteed
to be valid.

Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.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.

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                 | 140 ++++++++++++++++++++++++++++
 arch/x86/virt/vmx/tdx/tdx_global_metadata.c |  14 +++
 5 files changed, 160 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..0ae9773b6ef6 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 memory_pool_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 f08278a47aff..c44535933551 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1220,6 +1220,142 @@ 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)
+{
+	unsigned int required_pages, added_pages;
+	struct tdx_sys_info_ext sysinfo_ext;
+	struct tdx_hpa_list *hpa_list;
+	struct page *page;
+	int ret;
+
+	ret = get_tdx_sys_info_ext(&sysinfo_ext);
+	if (ret)
+		return ret;
+
+	required_pages = sysinfo_ext.memory_pool_required_pages;
+
+	/*
+	 * 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 (!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(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",
+		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 < required_pages) {
+		unsigned int chunk_pages = min(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;
@@ -1278,6 +1414,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..a6fc0ef0d834 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->memory_pool_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	[flat|nested] 7+ messages in thread

* [PATCH v3 4/6] x86/virt/tdx: Make TDX module initialize the extensions
  2026-10-05 17:41 [PATCH v3 0/6] Enable TDX module extensions Xu Yilun
                   ` (2 preceding siblings ...)
  2026-10-05 17:41 ` [PATCH v3 3/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
@ 2026-10-05 17:41 ` Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 5/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 6/6] x86/virt/tdx: Support DPAMT when adding memory for the extensions Xu Yilun
  5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-10-05 17:41 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. Several add-on features depend on the
extensions to execute their SEAMCALL leafs.

After providing all required memory to the TDX module, initialize TDX
module extensions via TDH.EXT.INIT, then those add-on features can use
their SEAMCALL leafs normally.

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>
---
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 c44535933551..d8df3b2b1d17 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1348,12 +1348,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	[flat|nested] 7+ messages in thread

* [PATCH v3 5/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update
  2026-10-05 17:41 [PATCH v3 0/6] Enable TDX module extensions Xu Yilun
                   ` (3 preceding siblings ...)
  2026-10-05 17:41 ` [PATCH v3 4/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
@ 2026-10-05 17:41 ` Xu Yilun
  2026-10-05 17:41 ` [PATCH v3 6/6] x86/virt/tdx: Support DPAMT when adding memory for the extensions Xu Yilun
  5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-10-05 17:41 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.

Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
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 d8df3b2b1d17..5d5f9da1ab02 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1348,7 +1348,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;
@@ -1379,6 +1379,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;
@@ -1562,6 +1575,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	[flat|nested] 7+ messages in thread

* [PATCH v3 6/6] x86/virt/tdx: Support DPAMT when adding memory for the extensions
  2026-10-05 17:41 [PATCH v3 0/6] Enable TDX module extensions Xu Yilun
                   ` (4 preceding siblings ...)
  2026-10-05 17:41 ` [PATCH v3 5/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
@ 2026-10-05 17:41 ` Xu Yilun
  5 siblings, 0 replies; 7+ messages in thread
From: Xu Yilun @ 2026-10-05 17:41 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 Physical Address Metadata Table (PAMT) to track some
state for each page of physical memory that it might use. 3 levels of
PAMTs are used to track pages of different sizes - 1GB, 2MB and 4KB.
Dynamic PAMT (DPAMT) allows saving memory by allocating 4KB PAMT
dynamically, while the 1GB and 2MB levels remain allocated on TDX module
initialization. The kernel has helpers to install 4K DPAMT.

Although the memory for the extensions is tens of megabytes and the host
allocates it in a large chunk, the TDX module accepts it at 4K
granularity. Thus the host has to install 4K DPAMT for each page of it.

Call tdx_pamt_get() for each page before adding it to 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 4K DPAMT metadata.

Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
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 5d5f9da1ab02..27a7039ee443 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1330,8 +1330,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	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-05 17:46 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-05 17:41 [PATCH v3 0/6] Enable TDX module extensions Xu Yilun
2026-10-05 17:41 ` [PATCH v3 1/6] x86/virt/tdx: Move TDH.SYS.CONFIG operations into a wrapper Xu Yilun
2026-10-05 17:41 ` [PATCH v3 2/6] x86/virt/tdx: Configure add-on features on TDX module init Xu Yilun
2026-10-05 17:41 ` [PATCH v3 3/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
2026-10-05 17:41 ` [PATCH v3 4/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
2026-10-05 17:41 ` [PATCH v3 5/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
2026-10-05 17:41 ` [PATCH v3 6/6] x86/virt/tdx: Support DPAMT when adding memory for the extensions Xu Yilun

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®