Re: [PATCH v6 03/11] x86/virt/tdx: Add tdx_alloc/free_control_page() helpers

From: Sohil Mehta

Date: Tue Jul 07 2026 - 23:51:11 EST


How about?

x86/virt/tdx: Add tdx_{alloc,free}_control_page() helpers

On 5/25/2026 7:35 PM, Rick Edgecombe wrote:
> From: "Kirill A. Shutemov" <kirill.shutemov@xxxxxxxxxxxxxxx>
>
> Add helpers to use when allocating or preparing pages that are handed to
> the TDX-Module for use as control/S-EPT pages, and thus need Dynamic PAMT
> adjustments.
>
> The TDX module tracks some state for each page of physical memory that it
> might use. It calls this state the PAMT. It includes separate state for
> each page size a physical page could be utilized at within the TDX module
> (1GB, 2MB, 4KB). In Dynamic PAMT, only the 4KB page size state is
> allocated dynamically. So for pages that TDX will use as 2MB physically
> contiguous pages, Dynamic PAMT backing is not needed.

I lost the continuation in the last sentence. Why does it only talk
about 2MB if only 4KB is dynamically allocated. What about 1GB?

(Probably due to my lack of TDX knowledge)
Similarly, why do these functions only refer to 2MB only and not 1GB?

pamt_2mb_arg(), tdh_phymem_pamt_add(), tdh_phymem_pamt_remove().

>
> KVM will need to hand pages to the TDX module that it will use at 4KB
> granularity. So these pages will need Dynamic PAMT backing added before
> they are used by the TDX module, and removed afterwards.
>

...

> diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
> index 82dc27aecf297..74e75db5728c7 100644
> --- a/arch/x86/include/asm/tdx.h
> +++ b/arch/x86/include/asm/tdx.h
> @@ -37,6 +37,7 @@
>
> #include <uapi/asm/mce.h>
> #include <asm/tdx_global_metadata.h>
> +#include <linux/mm.h>
> #include <linux/pgtable.h>
>
> /*
> @@ -160,6 +161,12 @@ void tdx_guest_keyid_free(unsigned int keyid);
>
> void tdx_quirk_reset_paddr(unsigned long base, unsigned long size);
>
> +/* Number PAMT pages to be provided to TDX module per 2MB region of PA */

^^^ of PAMT pages


> +#define TDX_DPAMT_ENTRY_PAGE_CNT 2
> +
> +struct page *tdx_alloc_control_page(void);
> +void tdx_free_control_page(struct page *page);
> +
> struct tdx_td {
> /* TD root structure: */
> struct page *tdr_page;
> diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
> index 9ebd192cb5c17..9e0812d87ab06 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -1919,6 +1919,165 @@ u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, kvm_pfn_t pfn)
> }
> EXPORT_SYMBOL_FOR_KVM(tdh_phymem_page_wbinvd_hkid);
>
> +static int alloc_pamt_array(struct page **pamt_pages)
> +{
> + int i, j;
> +
> + for (i = 0; i < TDX_DPAMT_ENTRY_PAGE_CNT; i++) {
> + pamt_pages[i] = alloc_page(GFP_KERNEL_ACCOUNT);
> + if (!pamt_pages[i])
> + goto err;
> + }
> +
> + return 0;
> +err:
> + for (j = 0; j < i; j++)
> + __free_page(pamt_pages[j]);
> + return -ENOMEM;

Add a blank line before the return to separate from the for loop?

> +}
> +
> +static void free_pamt_array(struct page **pamt_pages)
> +{
> + for (int i = 0; i < TDX_DPAMT_ENTRY_PAGE_CNT; i++) {
> + /*
> + * Reset pages unconditionally to cover cases
> + * where they were passed to the TDX module.
> + */
> + tdx_quirk_reset_paddr(page_to_phys(pamt_pages[i]), PAGE_SIZE);
> +
> + __free_page(pamt_pages[i]);
> + }
> +}
> +
> +/*
> + * Calculate the arg needed for operating on the DPAMT backing for
> + * a given 4KB page.
> + */
> +static u64 pamt_2mb_arg(kvm_pfn_t pfn)
> +{
> + unsigned long hpa_2mb = ALIGN_DOWN(pfn << PAGE_SHIFT, PMD_SIZE);
> +
> + return hpa_2mb | TDX_PS_2M;
> +}
> +
> +/* Add PAMT backing for the given page. */
> +static u64 tdh_phymem_pamt_add(kvm_pfn_t pfn, struct page **pamt_pages)
> +{
> + struct tdx_module_args args = {
> + .rcx = pamt_2mb_arg(pfn),
> + .rdx = page_to_phys(pamt_pages[0]),
> + .r8 = page_to_phys(pamt_pages[1]),
> + };
> +
> + return seamcall(TDH_PHYMEM_PAMT_ADD, &args);
> +}
> +
> +/* Remove PAMT backing for the given page. */
> +static u64 tdh_phymem_pamt_remove(kvm_pfn_t pfn, struct page **pamt_pages)
> +{
> + struct tdx_module_args args = {
> + .rcx = pamt_2mb_arg(pfn),
> + };
> + u64 ret;
> +
> + ret = seamcall_ret(TDH_PHYMEM_PAMT_REMOVE, &args);
> + if (ret)
> + return ret;
> +
> + /* Copy PAMT pages out of the struct per the TDX ABI */
> + pamt_pages[0] = phys_to_page(args.rdx);
> + pamt_pages[1] = phys_to_page(args.r8);
> +
> + return 0;
> +}
> +
> +/* Allocate PAMT memory for the given page */
> +static int tdx_pamt_get(kvm_pfn_t pfn)
> +{
> + struct page *pamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT];
> + u64 tdx_status;
> + int ret;
> +
> + if (!tdx_supports_dynamic_pamt(&tdx_sysinfo))
> + return 0;
> +
> + ret = alloc_pamt_array(pamt_pages);
> + if (ret)
> + return ret;
> +
> + tdx_status = tdh_phymem_pamt_add(pfn, pamt_pages);
> + if (tdx_status != TDX_SUCCESS) {
> + ret = -EIO;
> + goto out_free;
> + }
> +
> + return 0;

Blank line here as well.
> +out_free:
> + free_pamt_array(pamt_pages);
> + return ret;
> +}
> +