* [PATCH 1/6] x86/virt/tdx: Wrap TDH.SYS.CONFIG/UPDATE operations in helpers
2026-08-21 3:29 [PATCH 0/6] Enable TDX module extensions Xu Yilun
@ 2026-08-21 3:29 ` Xu Yilun
2026-08-21 20:53 ` Edgecombe, Rick P
2026-08-21 3:29 ` [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update Xu Yilun
` (4 subsequent siblings)
5 siblings, 1 reply; 16+ messages in thread
From: Xu Yilun @ 2026-08-21 3:29 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: kas, rick.p.edgecombe, yilun.xu, yilun.xu, xiaoyao.li,
sohil.mehta, adrian.hunter, kishen.maloor, tony.lindgren,
peter.fang, baolu.lu, zhenzhong.duan, chao.gao, artem.bityutskiy,
kvm
As part of the TDX module initialization, the kernel configures the TDX
module with several information, such as TDX-usable memory regions
(TDMRs) and the global KeyID for protecting TDX metadata. During the
configuration, the kernel does 2 operations: constructing kernel data
types for the SEAMCALL leaf arguments and turning these data types into
u64's according to TDX ABI.
Both operations are implemented in one function - config_tdx_module().
This blurs the boundary between kernel managed structures and TDX ABI
definitions. Moreover, future kernel change will need to add more
nuances mandated by TDX ABI, such as setting the TDH.SYS.CONFIG leaf
version to allow configuring add-on features. Keeping these operations
tangled would further clutter the code.
Just like other SEAMCALL leaf helpers, wrap the invocation of
TDH.SYS.CONFIG in a helper. Introduce a more descriptive kernel data
type for the physical address array of TDMR information. This data type
is similar to struct seamldr_params in that it is the container of the
PA array layout which is an in-memory ABI. So the previous u64 * type
for the array is not wrong, but a named structure provides better type
safety and self-documentation. Use the data type as the argument of the
TDH.SYS.CONFIG helper.
Future kernel change will also need to set the TDH.SYS.UPDATE leaf
version for the same purpose. Add a similar helper to prepare for the
change.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
v1:
- This patch is split out from the last series (Rick)
---
arch/x86/virt/vmx/tdx/tdx.c | 37 ++++++++++++++++++++++++++++---------
1 file changed, 28 insertions(+), 9 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 7a89e29b118c..e6b664b76141 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -998,11 +998,26 @@ static __init int construct_tdmrs(struct list_head *tmb_list,
return ret;
}
+struct tdmr_info_pa_array {
+ DECLARE_FLEX_ARRAY(u64, phys);
+};
+
+static __init int tdx_sys_config(struct tdmr_info_pa_array *tdmr_pa_array,
+ u64 nr_tdmr_pa, u64 global_keyid)
+{
+ struct tdx_module_args args = {
+ .rcx = __pa(tdmr_pa_array),
+ .rdx = nr_tdmr_pa,
+ .r8 = global_keyid,
+ };
+
+ 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;
@@ -1021,12 +1036,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));
+ tdmr_pa_array->phys[i] = __pa(tdmr_entry(tdmr_list, i));
- args.rcx = __pa(tdmr_pa_array);
- args.rdx = tdmr_list->nr_consumed_tdmrs;
- args.r8 = global_keyid;
- 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);
@@ -1306,12 +1319,18 @@ int tdx_module_shutdown(void)
return 0;
}
-int tdx_module_run_update(void)
+static int tdx_sys_update(void)
{
struct tdx_module_args args = {};
+
+ return seamcall_prerr(TDH_SYS_UPDATE, &args);
+}
+
+int tdx_module_run_update(void)
+{
int ret;
- ret = seamcall_prerr(TDH_SYS_UPDATE, &args);
+ ret = tdx_sys_update();
if (ret)
return ret;
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH 1/6] x86/virt/tdx: Wrap TDH.SYS.CONFIG/UPDATE operations in helpers
2026-08-21 3:29 ` [PATCH 1/6] x86/virt/tdx: Wrap TDH.SYS.CONFIG/UPDATE operations in helpers Xu Yilun
@ 2026-08-21 20:53 ` Edgecombe, Rick P
0 siblings, 0 replies; 16+ messages in thread
From: Edgecombe, Rick P @ 2026-08-21 20:53 UTC (permalink / raw)
To: linux-coco@lists.linux.dev, linux-kernel@vger.kernel.org,
yilun.xu@linux.intel.com, x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, Duan, Zhenzhong, kas@kernel.org,
baolu.lu@linux.intel.com, Li, Xiaoyao, Maloor, Kishen,
Hunter, Adrian, tony.lindgren@linux.intel.com, Mehta, Sohil,
Fang, Peter, kvm@vger.kernel.org,
artem.bityutskiy@linux.intel.com
On Fri, 2026-08-21 at 11:29 +0800, Xu Yilun wrote:
> As part of the TDX module initialization, the kernel configures the TDX
> module with several information
>
The tense is weird here around "several information". Should be "several pieces
of information". For curiosity sake, I looked it up and found a new-to-me
linguistic term:
https://en.wikipedia.org/wiki/Mass_noun
> , such as TDX-usable memory regions
> (TDMRs) and the global KeyID for protecting TDX metadata. During the
> configuration, the kernel does 2 operations: constructing kernel data
> types for the SEAMCALL leaf arguments and turning these data types into
> u64's according to TDX ABI.
What do you mean by "constructing kernel data types for the SEAMCALL leaf
arguments"? You are splitting the calculation of the pa via offset from setting
it in a u64? If that is what you are saying, it makes it seem like a much bigger
set of work.
>
> Both operations are implemented in one function - config_tdx_module().
Well I guess my guess above was wrong, because the construction of the pa kernel
data type happens in tdmr_entry()
> This blurs the boundary between kernel managed structures and TDX ABI
> definitions.
>
In the code after this patch it is still setting a u64 in config_tdx_module()...
so what is really changed with respect to this?
> Moreover, future kernel change will need to add more
> nuances mandated by TDX ABI, such as setting the TDH.SYS.CONFIG leaf
> version to allow configuring add-on features. Keeping these operations
> tangled would further clutter the code.
This seems like a stronger reason to me.
>
> Just like other SEAMCALL leaf helpers, wrap the invocation of
> TDH.SYS.CONFIG in a helper. Introduce a more descriptive kernel data
> type for the physical address array of TDMR information. This data type
> is similar to struct seamldr_params in that it is the container of the
> PA array layout which is an in-memory ABI. So the previous u64 * type
> for the array is not wrong, but a named structure provides better type
> safety and self-documentation. Use the data type as the argument of the
> TDH.SYS.CONFIG helper.
>
> Future kernel change will also need to set the TDH.SYS.UPDATE leaf
> version for the same purpose. Add a similar helper to prepare for the
> change.
>
> Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
The change looks good to me, but I'm wondering if it will need a better
justification. How about something that hits these points:
We have SEAMCALL wrappers mainly to not expose broad seamcall access, by
exporting only a selection of seamcalls, but also to abstract the seamcall
register ABIs. The latter improves readability and re-use for seamcalls that
are made multiple times.
Some seamcalls leafs are not explicitly wrapped because the level of TDX ABI
details needed to perform the call is low enough that it can flow well enough
with the calling code.
For some of the currently unwrapped seamcall leafs, future changes will add
seamcall version selection that will adjust the ABI depending on TDX module
version support. This will result in more ABI details to surrounding caller
code and decrease readability of the other logic. To contain this, wrap the
functions that will get version selections in seamcall wrappers.
The cleanest separation would be to have kernel data types for the seamcall
wrapper args, and have them marshaled into SEAMCALL ABI types (often u64s)
inside the wrappers. But to avoid duplicating allocations and copies, don't
do this when creating the wrapper for TDH.SYS.CONFIG. Instead clarify that
the u64's in the array passed are pa's with explicit naming of the helper
struct.
It's a bit rough, but as a general argument for the change, does it seem better?
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update
2026-08-21 3:29 [PATCH 0/6] Enable TDX module extensions Xu Yilun
2026-08-21 3:29 ` [PATCH 1/6] x86/virt/tdx: Wrap TDH.SYS.CONFIG/UPDATE operations in helpers Xu Yilun
@ 2026-08-21 3:29 ` Xu Yilun
2026-08-21 14:38 ` Dave Hansen
2026-08-21 22:01 ` Edgecombe, Rick P
2026-08-21 3:29 ` [PATCH 3/6] x86/virt/tdx: Detect if the extensions initialization is required Xu Yilun
` (3 subsequent siblings)
5 siblings, 2 replies; 16+ messages in thread
From: Xu Yilun @ 2026-08-21 3:29 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: kas, rick.p.edgecombe, yilun.xu, yilun.xu, xiaoyao.li,
sohil.mehta, adrian.hunter, kishen.maloor, tony.lindgren,
peter.fang, baolu.lu, zhenzhong.duan, chao.gao, artem.bityutskiy,
kvm
The TDX architecture identifies some features that must be explicitly
enabled when the kernel supports them. These add-on features affect
existing TDX systems: they may change existing feature behavior, reserve
more memory, or impact TDX initialization performance. The kernel must
enable these add-on features at boot or post-update time.
TDISP, DICE-based quoting and TD migration are among those add-on
features, as their SEAMCALL leaves depend on a SEAMCALL execution
context built by the TDX module extensions. On the other hand, the TDX
architecture doesn't allow the extensions to be initialized if none of
these features are enabled. Add support for configuring add-on features,
as the prerequisite for enabling the extensions.
The TDX module extends TDH.SYS.CONFIG and TDH.SYS.UPDATE with new bitmap
parameters to specify which add-on features to enable. The bitmap
uses the same feature bits as TDX_FEATURES0. Add a
get_tdx_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 extra bitmap to TDH.SYS.CONFIG helper.
The TDX module requires SEAMCALL leaf version 1 for TDH.SYS.CONFIG and
TDH.SYS.UPDATE when passing the new bitmap parameter. A previous
change [1] supports the versioned SEAMCALL leaves by adding a "version"
field in struct tdx_module_args. Set the version field to 1 if any bit
is set in this bitmap.
Compatible updates keep the reported features unchanged across updates,
so that existing TDX users can continue to operate without disruption.
To adhere to this, provide TDH.SYS.UPDATE with the same bitmap returned
by get_tdx_addon_features0(). This works because the module supported
feature bits are cached at boot and never refreshed after updates, so
the returned bitmap always matches the initial TDH.SYS.CONFIG input.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Link: https://lore.kernel.org/all/20260722084634.131020-1-yilun.xu@linux.intel.com/ # [1]
---
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 | 38 +++++++++++++++++++++++++++++++++----
1 file changed, 34 insertions(+), 4 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index e6b664b76141..66b43350c6c3 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -998,12 +998,22 @@ static __init int construct_tdmrs(struct list_head *tmb_list,
return ret;
}
+/* List all kernel supported add-on features0 bits here */
+#define TDX_KERNEL_SUPPORTED_ADDON_FEATURES0 (0)
+
+static u64 get_tdx_addon_features0(void)
+{
+ return tdx_sysinfo.features.tdx_features0 &
+ TDX_KERNEL_SUPPORTED_ADDON_FEATURES0;
+}
+
struct tdmr_info_pa_array {
DECLARE_FLEX_ARRAY(u64, phys);
};
static __init int tdx_sys_config(struct tdmr_info_pa_array *tdmr_pa_array,
- u64 nr_tdmr_pa, u64 global_keyid)
+ u64 nr_tdmr_pa, u64 global_keyid,
+ u64 addon_features0)
{
struct tdx_module_args args = {
.rcx = __pa(tdmr_pa_array),
@@ -1011,12 +1021,22 @@ static __init int tdx_sys_config(struct tdmr_info_pa_array *tdmr_pa_array,
.r8 = global_keyid,
};
+ /*
+ * Use SEAMCALL version 1 that supports add-on features if any are
+ * requested. Use version 0 if none for backward compatibility.
+ */
+ if (addon_features0) {
+ args.r9 = addon_features0;
+ args.version = 1;
+ }
+
return seamcall_prerr(TDH_SYS_CONFIG, &args);
}
static __init int config_tdx_module(struct tdmr_info_list *tdmr_list,
u64 global_keyid)
{
+ u64 addon_features0 = get_tdx_addon_features0();
struct tdmr_info_pa_array *tdmr_pa_array;
size_t array_sz;
int i, ret;
@@ -1039,7 +1059,7 @@ static __init int config_tdx_module(struct tdmr_info_list *tdmr_list,
tdmr_pa_array->phys[i] = __pa(tdmr_entry(tdmr_list, i));
ret = tdx_sys_config(tdmr_pa_array, tdmr_list->nr_consumed_tdmrs,
- global_keyid);
+ global_keyid, addon_features0);
/* Free the array as it is not required anymore. */
kfree(tdmr_pa_array);
@@ -1319,18 +1339,28 @@ int tdx_module_shutdown(void)
return 0;
}
-static int tdx_sys_update(void)
+static int tdx_sys_update(u64 addon_features0)
{
struct tdx_module_args args = {};
+ /*
+ * Use SEAMCALL version 1 that supports add-on features if any are
+ * requested. Use version 0 if none for backward compatibility.
+ */
+ if (addon_features0) {
+ args.r9 = addon_features0;
+ args.version = 1;
+ }
+
return seamcall_prerr(TDH_SYS_UPDATE, &args);
}
int tdx_module_run_update(void)
{
+ u64 addon_features0 = get_tdx_addon_features0();
int ret;
- ret = tdx_sys_update();
+ ret = tdx_sys_update(addon_features0);
if (ret)
return ret;
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update
2026-08-21 3:29 ` [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update Xu Yilun
@ 2026-08-21 14:38 ` Dave Hansen
2026-08-21 21:18 ` Edgecombe, Rick P
2026-08-21 22:01 ` Edgecombe, Rick P
1 sibling, 1 reply; 16+ messages in thread
From: Dave Hansen @ 2026-08-21 14:38 UTC (permalink / raw)
To: Xu Yilun, x86, linux-coco, linux-kernel
Cc: kas, rick.p.edgecombe, yilun.xu, xiaoyao.li, sohil.mehta,
adrian.hunter, kishen.maloor, tony.lindgren, peter.fang, baolu.lu,
zhenzhong.duan, chao.gao, artem.bityutskiy, kvm
On 8/20/26 20:29, Xu Yilun wrote:
> static __init int tdx_sys_config(struct tdmr_info_pa_array *tdmr_pa_array,
> - u64 nr_tdmr_pa, u64 global_keyid)
> + u64 nr_tdmr_pa, u64 global_keyid,
> + u64 addon_features0)
addon_features0 is just a copy of the global, static, not changing
get_tdx_addon_features0() return code, right?
Why pass it around as a function argument?
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update
2026-08-21 14:38 ` Dave Hansen
@ 2026-08-21 21:18 ` Edgecombe, Rick P
0 siblings, 0 replies; 16+ messages in thread
From: Edgecombe, Rick P @ 2026-08-21 21:18 UTC (permalink / raw)
To: linux-coco@lists.linux.dev, Hansen, Dave,
linux-kernel@vger.kernel.org, yilun.xu@linux.intel.com,
x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, Duan, Zhenzhong, kas@kernel.org,
baolu.lu@linux.intel.com, Li, Xiaoyao, Maloor, Kishen,
Hunter, Adrian, tony.lindgren@linux.intel.com, Mehta, Sohil,
Fang, Peter, kvm@vger.kernel.org,
artem.bityutskiy@linux.intel.com
On Fri, 2026-08-21 at 07:38 -0700, Dave Hansen wrote:
> On 8/20/26 20:29, Xu Yilun wrote:
> > static __init int tdx_sys_config(struct tdmr_info_pa_array *tdmr_pa_array,
> > - u64 nr_tdmr_pa, u64 global_keyid)
> > + u64 nr_tdmr_pa, u64 global_keyid,
> > + u64 addon_features0)
>
> addon_features0 is just a copy of the global, static, not changing
> get_tdx_addon_features0() return code, right?
>
> Why pass it around as a function argument?
+1. In the past there was confusion about accessing global metadata while still
reading the metadata, and it wasn't clear whether the global reference was
updated yet. But here it doesn't seem confusing as long as we assume that config
happens before metadata reading.
And for the similar update case outside the above quote, we would call a change
to that field an incompatible update I'd think.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update
2026-08-21 3:29 ` [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update Xu Yilun
2026-08-21 14:38 ` Dave Hansen
@ 2026-08-21 22:01 ` Edgecombe, Rick P
1 sibling, 0 replies; 16+ messages in thread
From: Edgecombe, Rick P @ 2026-08-21 22:01 UTC (permalink / raw)
To: linux-coco@lists.linux.dev, linux-kernel@vger.kernel.org,
yilun.xu@linux.intel.com, x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, Duan, Zhenzhong, kas@kernel.org,
baolu.lu@linux.intel.com, Li, Xiaoyao, Maloor, Kishen,
Hunter, Adrian, tony.lindgren@linux.intel.com, Mehta, Sohil,
Fang, Peter, kvm@vger.kernel.org,
artem.bityutskiy@linux.intel.com
On Fri, 2026-08-21 at 11:29 +0800, Xu Yilun wrote:
> The TDX architecture identifies some features that must be explicitly
> enabled when the kernel supports them.
>
It sounds like this is saying that TDX module is demanding that the kernel
enable these features if it can. I think it's not true.
> These add-on features affect
> existing TDX systems: they may change existing feature behavior, reserve
> more memory, or impact TDX initialization performance.
>
What are you trying to get at by saying they affect existing TDX systems? It's
important that if a new module gains these features, an upgrade *doesn't* affect
existing TDX systems. Are you trying to say instead that they *would* affect
existing systems, so they are add-ons?
> The kernel must
> enable these add-on features at boot or post-update time.
>
> TDISP, DICE-based quoting and TD migration are among those add-on
> features, as their SEAMCALL leaves depend on a SEAMCALL execution
> context built by the TDX module extensions. On the other hand, the TDX
> architecture doesn't allow the extensions to be initialized if none of
> these features are enabled.
>
So we have add-ons features and extensions. Some add-on features depend on
extensions. And also, the extensions can't be initialized if the add-on features
are not enabled? I'm not sure what you are trying to get at. That the kernel
doesn't have the option to blindly initialize all extensions?
> Add support for configuring add-on features,
> as the prerequisite for enabling the extensions.
I kinda know how this stuff works and I'm still struggling to understand what
you are trying to say...
I think optional features are pretty common pattern that will be generally
understood. So the only thing that needs explanation is that some optional (or
add-on) features need extra memory to save state, etc. But actually, this patch
doesn't deal with this, just turning on optional features.
>
> The TDX module extends TDH.SYS.CONFIG and TDH.SYS.UPDATE with new bitmap
> parameters to specify which add-on features to enable.
>
Because some other VMM was passing garbage in r9? Hmm, how does this work with
other features0 bits? Like for dynamic PAMT is a feature0 bit, but we pass it in
r8. But for add-on features that use extensions we pass it in r9? Or do we pass
all features0 bits in r9 for when using v1 of TDH.SYS.CONFIG? The docs say:
If the requested version in RAX is 1 or higher, R9 specifies TDX Module
feature enabling flags, formatted similarly to TDX_FEATURES0, readable by
TDH.SYS.RD*. A bit may be set to 1 if the corresponding TDX_FEATURES0 bit is
1.
I wonder why they didn't just use the many reserved bits 63:17 of r8 for new
features instead of this new seamcall version...
> The bitmap
> uses the same feature bits as TDX_FEATURES0. Add a
> get_tdx_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 extra bitmap to TDH.SYS.CONFIG helper.
>
> The TDX module requires SEAMCALL leaf version 1 for TDH.SYS.CONFIG and
> TDH.SYS.UPDATE when passing the new bitmap parameter. A previous
> change [1] supports the versioned SEAMCALL leaves by adding a "version"
> field in struct tdx_module_args. Set the version field to 1 if any bit
> is set in this bitmap.
>
> Compatible updates keep the reported features unchanged across updates,
> so that existing TDX users can continue to operate without disruption.
> To adhere to this
>
Compatible updates keep from disturbing the kernel. So the kernel shouldn't need
to adhere to anything. Just say the kernel doesn't need to re-fetch it.
> , provide TDH.SYS.UPDATE with the same bitmap returned
> by get_tdx_addon_features0(). This works because the module supported
> feature bits are cached at boot and never refreshed after updates, so
> the returned bitmap always matches the initial TDH.SYS.CONFIG input.
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 3/6] x86/virt/tdx: Detect if the extensions initialization is required
2026-08-21 3:29 [PATCH 0/6] Enable TDX module extensions Xu Yilun
2026-08-21 3:29 ` [PATCH 1/6] x86/virt/tdx: Wrap TDH.SYS.CONFIG/UPDATE operations in helpers Xu Yilun
2026-08-21 3:29 ` [PATCH 2/6] x86/virt/tdx: Configure add-on features on TDX module init and update Xu Yilun
@ 2026-08-21 3:29 ` Xu Yilun
2026-08-21 15:22 ` Kiryl Shutsemau
2026-08-21 3:29 ` [PATCH 4/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
` (2 subsequent siblings)
5 siblings, 1 reply; 16+ messages in thread
From: Xu Yilun @ 2026-08-21 3:29 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: kas, rick.p.edgecombe, yilun.xu, yilun.xu, xiaoyao.li,
sohil.mehta, adrian.hunter, kishen.maloor, tony.lindgren,
peter.fang, baolu.lu, zhenzhong.duan, chao.gao, artem.bityutskiy,
kvm
Some add-on features require TDX module extensions. The TDX module
provides a metadata field "ext_required" to indicate this requirement.
Add the first step of TDX module extensions initialization by detecting
if the extensions are required:
1. Check if the extensions are supported via TDX_FEATURES0_EXT. If
not, ext_required is not readable.
2. Check if any TDX feature needs the extensions via ext_required.
Skip the extensions initialization when it is not required.
Currently all metadata fields are read at the very beginning of TDX
module initialization. However, ext_required is only valid after the
add-on feature configuration, so it cannot use the existing metadata
reading method.
Add a dedicated metadata reading interface for the extensions, call it
after add-on feature configuration.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
---
v1:
- Include struct tdx_sys_info_ext in struct tdx_sys_info.
---
arch/x86/include/asm/tdx.h | 1 +
arch/x86/include/asm/tdx_global_metadata.h | 5 ++++
arch/x86/virt/vmx/tdx/tdx.c | 28 +++++++++++++++++++++
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 14 +++++++++++
4 files changed, 48 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 89e97d5761d8..6657f2db0330 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -36,6 +36,7 @@
/* Bit definitions of TDX_FEATURES0 metadata field */
#define TDX_FEATURES0_TD_PRESERVING BIT_ULL(1)
#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
+#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 41150d546589..fe3fe91de71f 100644
--- a/arch/x86/include/asm/tdx_global_metadata.h
+++ b/arch/x86/include/asm/tdx_global_metadata.h
@@ -44,12 +44,17 @@ struct tdx_sys_info_handoff {
u16 module_hv;
};
+struct tdx_sys_info_ext {
+ bool ext_required;
+};
+
struct tdx_sys_info {
struct tdx_sys_info_version version;
struct tdx_sys_info_features features;
struct tdx_sys_info_tdmr tdmr;
struct tdx_sys_info_td_ctrl td_ctrl;
struct tdx_sys_info_td_conf td_conf;
+ struct tdx_sys_info_ext ext;
};
#endif
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 66b43350c6c3..a0c370894c0b 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1175,6 +1175,30 @@ static __init int init_tdmrs(struct tdmr_info_list *tdmr_list)
return 0;
}
+static __init int init_tdx_module_extensions(void)
+{
+ int ret;
+
+ if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
+ return 0;
+
+ ret = get_tdx_sys_info_ext(&tdx_sysinfo.ext);
+ if (ret)
+ return ret;
+
+ /*
+ * ext_required indicates if any add-on features requiring TDX module
+ * extensions are configured via TDH.SYS.CONFIG. If none, skip the
+ * initialization.
+ */
+ if (!tdx_sysinfo.ext.ext_required)
+ return 0;
+
+ /* TODO: add the extensions enabling steps here */
+
+ return 0;
+}
+
static __init int init_tdx_module(void)
{
int ret;
@@ -1229,6 +1253,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 e49c300f23d4..b9e1c011a990 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -131,3 +131,17 @@ static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
return ret;
}
+
+static __init int get_tdx_sys_info_ext(struct tdx_sys_info_ext *sysinfo_ext)
+{
+ int ret;
+ u64 val;
+
+ ret = read_sys_metadata_field(0x3100000000000001, &val);
+ if (ret)
+ return ret;
+
+ sysinfo_ext->ext_required = val;
+
+ return 0;
+}
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH 3/6] x86/virt/tdx: Detect if the extensions initialization is required
2026-08-21 3:29 ` [PATCH 3/6] x86/virt/tdx: Detect if the extensions initialization is required Xu Yilun
@ 2026-08-21 15:22 ` Kiryl Shutsemau
2026-08-21 22:22 ` Edgecombe, Rick P
0 siblings, 1 reply; 16+ messages in thread
From: Kiryl Shutsemau @ 2026-08-21 15:22 UTC (permalink / raw)
To: Xu Yilun
Cc: x86, linux-coco, linux-kernel, rick.p.edgecombe, yilun.xu,
xiaoyao.li, sohil.mehta, adrian.hunter, kishen.maloor,
tony.lindgren, peter.fang, baolu.lu, zhenzhong.duan, chao.gao,
artem.bityutskiy, kvm
On Fri, Aug 21, 2026 at 11:29:17AM +0800, Xu Yilun wrote:
> static __init int init_tdx_module(void)
> {
> int ret;
> @@ -1229,6 +1253,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;
> +
Hm. Don't we need TDH.SYS.SHUTDOWN/DISABLE on error?
Once TDMRs are initialized the module is functional. Yanking PAMT from
under it is not safe AFAICS.
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH 3/6] x86/virt/tdx: Detect if the extensions initialization is required
2026-08-21 15:22 ` Kiryl Shutsemau
@ 2026-08-21 22:22 ` Edgecombe, Rick P
0 siblings, 0 replies; 16+ messages in thread
From: Edgecombe, Rick P @ 2026-08-21 22:22 UTC (permalink / raw)
To: kas@kernel.org, yilun.xu@linux.intel.com
Cc: Gao, Chao, Xu, Yilun, x86@kernel.org, baolu.lu@linux.intel.com,
kvm@vger.kernel.org, Li, Xiaoyao, linux-kernel@vger.kernel.org,
Maloor, Kishen, Hunter, Adrian, Mehta, Sohil,
linux-coco@lists.linux.dev, tony.lindgren@linux.intel.com,
Duan, Zhenzhong, Fang, Peter, artem.bityutskiy@linux.intel.com
On Fri, 2026-08-21 at 16:22 +0100, Kiryl Shutsemau wrote:
> On Fri, Aug 21, 2026 at 11:29:17AM +0800, Xu Yilun wrote:
> > static __init int init_tdx_module(void)
> > {
> > int ret;
> > @@ -1229,6 +1253,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;
> > +
>
> Hm. Don't we need TDH.SYS.SHUTDOWN/DISABLE on error?
>
> Once TDMRs are initialized the module is functional. Yanking PAMT from
> under it is not safe AFAICS.
If we reset all the memory, how is it different than bailing halfway when
initializing the TDMRs? Some special state changes after the last TDMR is
inited?
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 4/6] x86/virt/tdx: Add extra memory to TDX module for the extensions
2026-08-21 3:29 [PATCH 0/6] Enable TDX module extensions Xu Yilun
` (2 preceding siblings ...)
2026-08-21 3:29 ` [PATCH 3/6] x86/virt/tdx: Detect if the extensions initialization is required Xu Yilun
@ 2026-08-21 3:29 ` Xu Yilun
2026-08-21 15:44 ` Kiryl Shutsemau
2026-08-21 3:29 ` [PATCH 5/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
2026-08-21 3:29 ` [PATCH 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
5 siblings, 1 reply; 16+ messages in thread
From: Xu Yilun @ 2026-08-21 3:29 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: kas, rick.p.edgecombe, yilun.xu, yilun.xu, xiaoyao.li,
sohil.mehta, adrian.hunter, kishen.maloor, tony.lindgren,
peter.fang, baolu.lu, zhenzhong.duan, chao.gao, artem.bityutskiy,
kvm
TDX module extensions need memory for their own internal state and data
to serve SEAMCALL leaves. 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, add it to the module, and never gets it back.
The TDX module accepts the memory in the form of a PFN 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 host. Use contiguous page allocation to isolate these
large blocks entirely, avoiding permanent memory fragmentation and buddy
allocator efficiency loss. Print the allocation amount on TDX module
extensions initialization for visibility.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
v1:
- Fix return value for SEAMCALL helpers (Chao)
- Print SEAMCALL error code for SEAMCALL helpers (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 helper parameter type,
struct page *hpa_list => struct tdx_hpa_list *hpa_list
- changelog & code comments
---
arch/x86/include/asm/tdx_global_metadata.h | 1 +
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 118 +++++++++++++++++++-
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 6 +
4 files changed, 123 insertions(+), 3 deletions(-)
diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
index fe3fe91de71f..43b8761c0854 100644
--- a/arch/x86/include/asm/tdx_global_metadata.h
+++ b/arch/x86/include/asm/tdx_global_metadata.h
@@ -45,6 +45,7 @@ struct tdx_sys_info_handoff {
};
struct tdx_sys_info_ext {
+ u32 memory_pool_required_pages;
bool ext_required;
};
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 63e3acfb5d0c..52888424fe7d 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -48,6 +48,7 @@
#define TDH_SYS_CONFIG 45
#define TDH_SYS_SHUTDOWN 52
#define TDH_SYS_UPDATE 53
+#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 a0c370894c0b..8c2fdaf0b8c0 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1175,6 +1175,120 @@ static __init int init_tdmrs(struct tdmr_info_list *tdmr_list)
return 0;
}
+#define TDX_HPA_LIST_MAX_NR_PAGES (PAGE_SIZE / sizeof(u64))
+
+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 = tdx_sysinfo.ext.memory_pool_required_pages;
+ struct tdx_hpa_list *hpa_list;
+ unsigned int added_pages;
+ struct page *page;
+ int ret;
+
+ /*
+ * TDX module uses the metadata memory_pool_required_pages to indicate
+ * how much memory is still needed. This value decreases each time
+ * memory is added via TDH.EXT.MEM.ADD.
+ *
+ * On first time initialization, a value of 0 before any memory is
+ * added is unusual. But host makes no assumptions. Skip the memory
+ * setup and let subsequent steps catch any actual errors.
+ */
+ if (!required_pages)
+ return 0;
+
+ hpa_list = kzalloc_obj(*hpa_list);
+ if (!hpa_list)
+ return -ENOMEM;
+
+ page = alloc_contig_pages(required_pages, GFP_KERNEL, numa_mem_id(),
+ &node_online_map);
+ if (!page) {
+ ret = -ENOMEM;
+ goto out_free_hpa_list;
+ }
+
+ 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) {
+ /*
+ * This SEAMCALL leaf shouldn't fail, and if it does,
+ * things are broken enough that complex error handling
+ * isn't worth it. Intentionally leak all pages,
+ * including un-added pages.
+ */
+ WARN(1, "Fatal: TDX module rejected memory for extensions, stranded all pages\n");
+ break;
+ }
+
+ added_pages += chunk_pages;
+ }
+
+ /*
+ * Memory for TDX module extensions is never reclaimed and can be tens
+ * of megabytes. Print the amount so users know the cost.
+ */
+ pr_info("%lu KB consumed for TDX module extensions\n",
+ required_pages * PAGE_SIZE / 1024);
+
+out_free_hpa_list:
+ kfree(hpa_list);
+
+ return ret;
+}
+
static __init int init_tdx_module_extensions(void)
{
int ret;
@@ -1194,9 +1308,7 @@ static __init int init_tdx_module_extensions(void)
if (!tdx_sysinfo.ext.ext_required)
return 0;
- /* TODO: add the extensions enabling steps here */
-
- return 0;
+ return tdx_ext_mem_setup();
}
static __init int init_tdx_module(void)
diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
index b9e1c011a990..720cdaf76492 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -137,6 +137,12 @@ 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;
+
ret = read_sys_metadata_field(0x3100000000000001, &val);
if (ret)
return ret;
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH 4/6] x86/virt/tdx: Add extra memory to TDX module for the extensions
2026-08-21 3:29 ` [PATCH 4/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
@ 2026-08-21 15:44 ` Kiryl Shutsemau
0 siblings, 0 replies; 16+ messages in thread
From: Kiryl Shutsemau @ 2026-08-21 15:44 UTC (permalink / raw)
To: Xu Yilun
Cc: x86, linux-coco, linux-kernel, rick.p.edgecombe, yilun.xu,
xiaoyao.li, sohil.mehta, adrian.hunter, kishen.maloor,
tony.lindgren, peter.fang, baolu.lu, zhenzhong.duan, chao.gao,
artem.bityutskiy, kvm
On Fri, Aug 21, 2026 at 11:29:18AM +0800, Xu Yilun wrote:
> TDX module extensions need memory for their own internal state and data
> to serve SEAMCALL leaves. 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, add it to the module, and never gets it back.
>
> The TDX module accepts the memory in the form of a PFN 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.
The array entries are physical addresses, not PFNs. HPA_LIST_INFO encodes
a PFN, the array does not.
> TDX module extensions consume tens of megabytes memory that will never
> be returned to host. Use contiguous page allocation to isolate these
> large blocks entirely, avoiding permanent memory fragmentation and buddy
> allocator efficiency loss. Print the allocation amount on TDX module
> extensions initialization for visibility.
>
> Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
> ---
> v1:
> - Fix return value for SEAMCALL helpers (Chao)
> - Print SEAMCALL error code for SEAMCALL helpers (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 helper parameter type,
> struct page *hpa_list => struct tdx_hpa_list *hpa_list
> - changelog & code comments
> ---
> arch/x86/include/asm/tdx_global_metadata.h | 1 +
> arch/x86/virt/vmx/tdx/tdx.h | 1 +
> arch/x86/virt/vmx/tdx/tdx.c | 118 +++++++++++++++++++-
> arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 6 +
> 4 files changed, 123 insertions(+), 3 deletions(-)
>
> diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
> index fe3fe91de71f..43b8761c0854 100644
> --- a/arch/x86/include/asm/tdx_global_metadata.h
> +++ b/arch/x86/include/asm/tdx_global_metadata.h
> @@ -45,6 +45,7 @@ struct tdx_sys_info_handoff {
> };
>
> struct tdx_sys_info_ext {
> + u32 memory_pool_required_pages;
> bool ext_required;
> };
>
> diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
> index 63e3acfb5d0c..52888424fe7d 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.h
> +++ b/arch/x86/virt/vmx/tdx/tdx.h
> @@ -48,6 +48,7 @@
> #define TDH_SYS_CONFIG 45
> #define TDH_SYS_SHUTDOWN 52
> #define TDH_SYS_UPDATE 53
> +#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 a0c370894c0b..8c2fdaf0b8c0 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -1175,6 +1175,120 @@ static __init int init_tdmrs(struct tdmr_info_list *tdmr_list)
> return 0;
> }
>
> +#define TDX_HPA_LIST_MAX_NR_PAGES (PAGE_SIZE / sizeof(u64))
> +
> +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 = tdx_sysinfo.ext.memory_pool_required_pages;
> + struct tdx_hpa_list *hpa_list;
> + unsigned int added_pages;
> + struct page *page;
> + int ret;
> +
> + /*
> + * TDX module uses the metadata memory_pool_required_pages to indicate
> + * how much memory is still needed. This value decreases each time
> + * memory is added via TDH.EXT.MEM.ADD.
> + *
> + * On first time initialization, a value of 0 before any memory is
> + * added is unusual. But host makes no assumptions. Skip the memory
> + * setup and let subsequent steps catch any actual errors.
> + */
> + if (!required_pages)
> + return 0;
> +
> + hpa_list = kzalloc_obj(*hpa_list);
> + if (!hpa_list)
> + return -ENOMEM;
to_hpa_list_info() expects hpa_list to be page-aligned. It happens to
work with kmalloc for PAGE_SIZE allocation.
Maybe it is better to allocate it with buddy allocator instead?
> +
> + page = alloc_contig_pages(required_pages, GFP_KERNEL, numa_mem_id(),
> + &node_online_map);
Why contiguous? TDH.EXT.MEM.ADD takes a list of page addresses and the loop
below writes every one of them out separately.
alloc_pages_bulk() fits the chunking that is already here, and a short
return can be handled per chunk. alloc_contig_pages() isolates and migrates
to get its range and fails TDX init outright when it cannot find one. PAMT
needs it because the TDMR ABI describes each PAMT as base+size. This does
not.
> + if (!page) {
> + ret = -ENOMEM;
> + goto out_free_hpa_list;
> + }
> +
> + 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) {
> + /*
> + * This SEAMCALL leaf shouldn't fail, and if it does,
> + * things are broken enough that complex error handling
> + * isn't worth it. Intentionally leak all pages,
> + * including un-added pages.
> + */
> + WARN(1, "Fatal: TDX module rejected memory for extensions, stranded all pages\n");
> + break;
It supposed to be
goto out_free_hpa_list;
No?
> + }
> +
> + added_pages += chunk_pages;
> + }
> +
> + /*
> + * Memory for TDX module extensions is never reclaimed and can be tens
> + * of megabytes. Print the amount so users know the cost.
> + */
> + pr_info("%lu KB consumed for TDX module extensions\n",
> + required_pages * PAGE_SIZE / 1024);
> +
> +out_free_hpa_list:
> + kfree(hpa_list);
> +
> + return ret;
> +}
> +
> static __init int init_tdx_module_extensions(void)
> {
> int ret;
> @@ -1194,9 +1308,7 @@ static __init int init_tdx_module_extensions(void)
> if (!tdx_sysinfo.ext.ext_required)
> return 0;
>
> - /* TODO: add the extensions enabling steps here */
> -
> - return 0;
> + return tdx_ext_mem_setup();
> }
>
> static __init int init_tdx_module(void)
> diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
> index b9e1c011a990..720cdaf76492 100644
> --- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
> +++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
> @@ -137,6 +137,12 @@ 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;
> +
Why above ext_required read? Is it even valid to read it in such case?
> ret = read_sys_metadata_field(0x3100000000000001, &val);
> if (ret)
> return ret;
> --
> 2.25.1
>
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 5/6] x86/virt/tdx: Make TDX module initialize the extensions
2026-08-21 3:29 [PATCH 0/6] Enable TDX module extensions Xu Yilun
` (3 preceding siblings ...)
2026-08-21 3:29 ` [PATCH 4/6] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
@ 2026-08-21 3:29 ` Xu Yilun
2026-08-21 23:55 ` Edgecombe, Rick P
2026-08-21 3:29 ` [PATCH 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
5 siblings, 1 reply; 16+ messages in thread
From: Xu Yilun @ 2026-08-21 3:29 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: kas, rick.p.edgecombe, yilun.xu, yilun.xu, xiaoyao.li,
sohil.mehta, adrian.hunter, kishen.maloor, tony.lindgren,
peter.fang, baolu.lu, zhenzhong.duan, chao.gao, artem.bityutskiy,
kvm
TDX module extensions need memory for their own internal state and data
to serve SEAMCALL leaves. Several add-on features depend on the
extensions to execute their SEAMCALL leaves.
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 leaves 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 helpers (Chao)
- Print SEAMCALL error code for SEAMCALL helpers (Xiaoyao)
- Changelog & code comments
---
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 23 ++++++++++++++++++++++-
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 52888424fe7d..1f43d2eb2345 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -48,6 +48,7 @@
#define TDH_SYS_CONFIG 45
#define TDH_SYS_SHUTDOWN 52
#define TDH_SYS_UPDATE 53
+#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 8c2fdaf0b8c0..873b8393f32f 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1289,6 +1289,23 @@ 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;
@@ -1308,7 +1325,11 @@ static __init int init_tdx_module_extensions(void)
if (!tdx_sysinfo.ext.ext_required)
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] 16+ messages in thread* Re: [PATCH 5/6] x86/virt/tdx: Make TDX module initialize the extensions
2026-08-21 3:29 ` [PATCH 5/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
@ 2026-08-21 23:55 ` Edgecombe, Rick P
0 siblings, 0 replies; 16+ messages in thread
From: Edgecombe, Rick P @ 2026-08-21 23:55 UTC (permalink / raw)
To: linux-coco@lists.linux.dev, linux-kernel@vger.kernel.org,
yilun.xu@linux.intel.com, x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, Duan, Zhenzhong, kas@kernel.org,
baolu.lu@linux.intel.com, Li, Xiaoyao, Maloor, Kishen,
Hunter, Adrian, tony.lindgren@linux.intel.com, Mehta, Sohil,
Fang, Peter, kvm@vger.kernel.org,
artem.bityutskiy@linux.intel.com
On Fri, 2026-08-21 at 11:29 +0800, Xu Yilun wrote:
> TDX module extensions need memory for their own internal state and data
> to serve SEAMCALL leaves.
>
For "their own", I think "own" is superfluous.
> Several add-on features depend on the
> extensions to execute their SEAMCALL leaves.
>
> After providing all required memory to the TDX module, initialize TDX
> module extensions via TDH.EXT.INIT
>
What exactly does TDH.EXT.INIT actually do, and why can't whatever that is
happen during TDH.EXT.MEM.ADD? Not saying we need to jam anything in awkwardly.
Can we say this is analogous to TDH.SYS.CONFIG but for the extensions? It only
exists because SYS.CONFIG gives the features to enable before the memory is
there to actually do it. So it needs a second call?
> , then those add-on features can use
> their SEAMCALL leaves normally.
Out of curiosity, what happens if you don't complete these steps and call one of
the extension seamcalls?
>
> 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>
Code looks good. Except that it leaves me wondering if this could have all been
a while loop around tdh_sys_confg() that gives memory on a memory error code.
Actually it looks like we have a TDX_EXT_MEMORY_POOL_REQUIRED error code to use.
Consider something like this:
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)
If that could technically work, I guess the benefit of the current approach is
that it allows to use contiguous physical allocations. Not sure if you think
that simpler snippet would actually be that simple in the real world. But if we
are basically building all this to help fragmentation, we should say it.
> ---
> v1:
> - Fix return value for SEAMCALL helpers (Chao)
> - Print SEAMCALL error code for SEAMCALL helpers (Xiaoyao)
> - Changelog & code comments
> ---
> arch/x86/virt/vmx/tdx/tdx.h | 1 +
> arch/x86/virt/vmx/tdx/tdx.c | 23 ++++++++++++++++++++++-
> 2 files changed, 23 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
> index 52888424fe7d..1f43d2eb2345 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.h
> +++ b/arch/x86/virt/vmx/tdx/tdx.h
> @@ -48,6 +48,7 @@
> #define TDH_SYS_CONFIG 45
> #define TDH_SYS_SHUTDOWN 52
> #define TDH_SYS_UPDATE 53
> +#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 8c2fdaf0b8c0..873b8393f32f 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -1289,6 +1289,23 @@ 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);
What happens if no init is needed? Does it return success or error? I'm
wondering if we need to really check ext_required and can just call TDH_EXT_INIT
unconditionally. We need to check for TDX_FEATURES0_EXT in any case. But do we
need both checks?
> + } 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;
> @@ -1308,7 +1325,11 @@ static __init int init_tdx_module_extensions(void)
> if (!tdx_sysinfo.ext.ext_required)
> 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)
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update
2026-08-21 3:29 [PATCH 0/6] Enable TDX module extensions Xu Yilun
` (4 preceding siblings ...)
2026-08-21 3:29 ` [PATCH 5/6] x86/virt/tdx: Make TDX module initialize " Xu Yilun
@ 2026-08-21 3:29 ` Xu Yilun
2026-08-22 0:01 ` Edgecombe, Rick P
5 siblings, 1 reply; 16+ messages in thread
From: Xu Yilun @ 2026-08-21 3:29 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: kas, rick.p.edgecombe, yilun.xu, yilun.xu, xiaoyao.li,
sohil.mehta, adrian.hunter, kishen.maloor, tony.lindgren,
peter.fang, baolu.lu, zhenzhong.duan, chao.gao, artem.bityutskiy,
kvm
Runtime TDX module update introduces a mechanism to update the module
firmware while preserving and restoring TDX operations. As part of the
restoration process, the host must re-initialize the extensions to
restore their functionality.
Linux runs the updates in stop_machine() context, which prevents memory
allocation. This introduces a hard restriction that the updated TDX
environment must not consume more memory for the extensions.
The impact of the memory allocation restriction can be mitigated by
another requirement. Runtime updates should keep the add-on features
unchanged across updates, so that existing TDX users can continue to
operate without disruption. This requirement minimizes the chance of
increased memory demand. As a result, the restriction only affects the
compatibility rule for choosing the update image.
To adhere to these requirements, the post-update initialization for the
extensions can be simplified as:
- Check if the extensions were originally initialized during boot up.
If not, skip the re-initialization.
- Assume no more memory needed, skip the memory adding step.
- Re-initialize the extensions via TDH.EXT.INIT. The SEAMCALL leaf
will fail if the updated module requires more memory, or if it drops
the extensions initialization entirely, which indicates the update
image is not compatible.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
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 873b8393f32f..1ca3996f32dc 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1289,7 +1289,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;
@@ -1332,6 +1332,19 @@ static __init int init_tdx_module_extensions(void)
return tdx_ext_init();
}
+/*
+ * Don't update the extensions metadata, just follow the requirement originated
+ * during TDX module initialization. Let the extensions re-initialization fail
+ * if more memory is needed, or if ext_required is dropped after updates.
+ */
+static int update_tdx_module_extensions(void)
+{
+ if (!tdx_sysinfo.ext.ext_required)
+ return 0;
+
+ return tdx_ext_init();
+}
+
static __init int init_tdx_module(void)
{
int ret;
@@ -1532,6 +1545,10 @@ int tdx_module_run_update(void)
*/
WARN_ON_ONCE(ret);
+ ret = update_tdx_module_extensions();
+ if (ret)
+ return ret;
+
tdx_module_state.initialized = true;
return 0;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update
2026-08-21 3:29 ` [PATCH 6/6] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
@ 2026-08-22 0:01 ` Edgecombe, Rick P
0 siblings, 0 replies; 16+ messages in thread
From: Edgecombe, Rick P @ 2026-08-22 0:01 UTC (permalink / raw)
To: linux-coco@lists.linux.dev, linux-kernel@vger.kernel.org,
yilun.xu@linux.intel.com, x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, Duan, Zhenzhong, kas@kernel.org,
baolu.lu@linux.intel.com, Li, Xiaoyao, Maloor, Kishen,
Hunter, Adrian, tony.lindgren@linux.intel.com, Mehta, Sohil,
Fang, Peter, kvm@vger.kernel.org,
artem.bityutskiy@linux.intel.com
On Fri, 2026-08-21 at 11:29 +0800, Xu Yilun wrote:
> Runtime TDX module update introduces a mechanism to update the module
> firmware while preserving and restoring TDX operations. As part of the
> restoration process, the host must re-initialize the extensions to
> restore their functionality.
>
> Linux runs the updates in stop_machine() context, which prevents memory
> allocation. This introduces a hard restriction that the updated TDX
> environment must not consume more memory for the extensions.
>
> The impact of the memory allocation restriction can be mitigated by
> another requirement. Runtime updates should keep the add-on features
> unchanged across updates, so that existing TDX users can continue to
> operate without disruption. This requirement minimizes the chance of
> increased memory demand.
>
Above it says it's a "hard restriction that the updated TDX environment must not
consume more memory", but here it says if everything is left the same, it only
minimizes the chances. Can it be consistent?
> As a result, the restriction only affects the
> compatibility rule for choosing the update image.
>
> To adhere to these requirements, the post-update initialization for the
> extensions can be simplified as:
>
> - Check if the extensions were originally initialized during boot up.
> If not, skip the re-initialization.
> - Assume no more memory needed, skip the memory adding step.
> - Re-initialize the extensions via TDH.EXT.INIT. The SEAMCALL leaf
> will fail if the updated module requires more memory, or if it drops
> the extensions initialization entirely, which indicates the update
> image is not compatible.
Why do we need to reinitialize the extensions if we don't change anything? I'm
not exactly sure what TDH.EXT.INIT is doing, but if we don't change any add-on
features, are we sure any work needs to be done? Just wondering if maybe some
docs talked about doing this because other VMMs might have been wanting to turn
on new extensions or something like that. Which would need initial setup. But
not Linux. So what exactly is needed that doesn't happen in TDH.SYS.UPDATE?
>
> Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
> ---
> 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 873b8393f32f..1ca3996f32dc 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -1289,7 +1289,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;
> @@ -1332,6 +1332,19 @@ static __init int init_tdx_module_extensions(void)
> return tdx_ext_init();
> }
>
> +/*
> + * Don't update the extensions metadata, just follow the requirement originated
> + * during TDX module initialization. Let the extensions re-initialization fail
> + * if more memory is needed, or if ext_required is dropped after updates.
> + */
> +static int update_tdx_module_extensions(void)
> +{
> + if (!tdx_sysinfo.ext.ext_required)
> + return 0;
> +
> + return tdx_ext_init();
> +}
> +
> static __init int init_tdx_module(void)
> {
> int ret;
> @@ -1532,6 +1545,10 @@ int tdx_module_run_update(void)
> */
> WARN_ON_ONCE(ret);
>
> + ret = update_tdx_module_extensions();
> + if (ret)
> + return ret;
> +
Above it has:
/*
* Only fails if there is something unexpected
* and severely wrong with the module.
*/
WARN_ON_ONCE(ret);
Is the tdx_ext_init() possible to fail here? Otherwise it seems unclear why to
only not handle get_tdx_sys_info_version() failure. Maybe at least group it with
the other ones that are allowed to fail.
> tdx_module_state.initialized = true;
> return 0;
> }
^ permalink raw reply [flat|nested] 16+ messages in thread