Re: [PATCH v2 10/31] x86/virt/tdx: Add extra memory to TDX Module for Extensions

Edgecombe, Rick P
2026-03-30T23:37:06Z

On Sat, 2026-03-28 at 00:01 +0800, Xu Yilun wrote:
> Adding more memory to TDX Module is the first step to enable Extensions.
>
> Currently, TDX Module memory use is relatively static. But, some new
> features (called "TDX Module Extensions") need to use memory more
> dynamically. While 'static' here means the kernel provides necessary
> amount of memory to TDX Module for its basic functionalities, 'dynamic'
> means extra memory is needed only if new optional features are to be
> enabled. So add a new memory feeding process backed by a new SEAMCALL
> TDH.EXT.MEM.ADD.
>
> The process is mostly the same as adding PAMT. The kernel queries TDX
> Module how much memory needed, allocates it, hands it over, and never
> gets it back.
>
> TDH.EXT.MEM.ADD uses tdx_page_array to provide control (private) pages
> to TDX Module. Introduce a tdx_clflush_page_array() helper to flush
> shared cache before SEAMCALL, to avoid shared cache write back damages
> these private pages.
>
> For now, TDX Module Extensions consume relatively large amount of
> memory (~50MB). Use contiguous page allocation to avoid permanently
> fragment too much memory. Print this readout value on TDX Module
> Extensions initialization for visibility.
>
> Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
> Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
> Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
> ---
> arch/x86/virt/vmx/tdx/tdx.h | 1 +
> arch/x86/virt/vmx/tdx/tdx.c | 92 ++++++++++++++++++++++++++++++++++++-
> 2 files changed, 91 insertions(+), 2 deletions(-)
>
> diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
> index 870bb75da3ba..31ccdfcf518c 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.h
> +++ b/arch/x86/virt/vmx/tdx/tdx.h
> @@ -60,6 +60,7 @@
> #define TDH_VP_WR 43
> #define TDH_SYS_CONFIG_V0 45
> #define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
> +#define TDH_EXT_MEM_ADD 61
>
> /* TDX page types */
> #define PT_NDA 0x0
> diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
> index 4fb56bb442f0..5fae17c13191 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -560,7 +560,7 @@ static int tdx_alloc_pages_contig(unsigned int nr_pages, struct page **pages,
> * Similar to tdx_page_array_alloc(), after allocating with this
> * function, call tdx_page_array_populate() to populate the tdx_page_array.
> */
> -static __maybe_unused struct tdx_page_array *
> +static struct tdx_page_array *
> tdx_page_array_alloc_contig(unsigned int nr_pages)
> {
> return tdx_page_array_alloc(nr_pages, tdx_alloc_pages_contig, NULL);
> @@ -643,7 +643,7 @@ EXPORT_SYMBOL_GPL(tdx_page_array_create_iommu_mt);
> #define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
> #define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
>
> -static u64 __maybe_unused hpa_list_info_assign_raw(struct tdx_page_array *array)
> +static u64 hpa_list_info_assign_raw(struct tdx_page_array *array)
> {
> return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) |
> FIELD_PREP(HPA_LIST_INFO_PFN,
> @@ -1513,6 +1513,94 @@ static void tdx_clflush_page(struct page *page)
> clflush_cache_range(page_to_virt(page), PAGE_SIZE);
> }
>
> +static void tdx_clflush_page_array(struct tdx_page_array *array)

It doesn't clflush the page array, it clflushes the current populate chunk. Hmm.
Does it suggest that the page array and the format for handing them to the TDX
module are two different things?

> +{
> + for (int i = 0; i < array->nents; i++)
> + tdx_clflush_page(array->pages[array->offset + i]);
> +}
> +
> +static int tdx_ext_mem_add(struct tdx_page_array *ext_mem)
> +{
> + struct tdx_module_args args = {
> + .rcx = hpa_list_info_assign_raw(ext_mem),
> + };
> + u64 r;
> +
> + tdx_clflush_page_array(ext_mem);
> +
> + do {
> + r = seamcall_ret(TDH_EXT_MEM_ADD, &args);
> + cond_resched();
> + } while (r == TDX_INTERRUPTED_RESUMABLE);
> +
> + if (r != TDX_SUCCESS)
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int tdx_ext_mem_setup(struct tdx_page_array *ext_mem)
> +{
> + unsigned int populated, offset = 0;
> + int ret;
> +
> + /*
> + * tdx_page_array's root page can hold 512 HPAs at most. We have ~50MB
> + * memory to add, re-populate the array and add pages bulk by bulk.
> + */
> + while (1) {
> + populated = tdx_page_array_populate(ext_mem, offset);
> + if (!populated)
> + break;

For this case of populate it seems like it would be ok to keep an array of PA's
instead of an array of struct pages. Not sure on it yet.

> +
> + ret = tdx_ext_mem_add(ext_mem);
> + if (ret)
> + return ret;
> +
> + offset += populated;
> + }
> +
> + return 0;
> +}
> +
> +static int __maybe_unused init_tdx_ext(void)
> +{
> + struct tdx_page_array *ext_mem = NULL;
> + unsigned int nr_pages;
> + int ret;
> +
> + if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
> + return 0;
> +
> + nr_pages = tdx_sysinfo.ext.memory_pool_required_pages;
> + /*
> + * memory_pool_required_pages == 0 means no need to add more pages,
> + * skip the memory setup.
> + */

Is this ever expected? Extensions are supported, but require no pages?

> + if (nr_pages) {
> + ext_mem = tdx_page_array_alloc_contig(nr_pages);
> + if (!ext_mem)
> + return -ENOMEM;
> +
> + ret = tdx_ext_mem_setup(ext_mem);
> + if (ret)
> + goto out_ext_mem;
> + }
> +
> + /* Extension memory is never reclaimed once assigned */
> + tdx_page_array_ctrl_leak(ext_mem);

This looks very weird to call "leak" in the success path.

> +
> + pr_info("%lu KB allocated for TDX Module Extensions\n",
> + nr_pages * PAGE_SIZE / 1024);
> +
> + return 0;
> +
> +out_ext_mem:
> + tdx_page_array_free(ext_mem);
> +
> + return ret;
> +}
> +
> static int init_tdx_module(void)
> {
> int ret;