[PATCH v10 04/11] x86/virt/tdx: Allocate refcounts for Dynamic PAMT memory
From: Rick Edgecombe
Date: Wed Sep 02 2026 - 21:54:44 EST
The PAMT memory holds metadata for all possible TDX protected memory. Each
physical address range is covered by PAMT entries at three levels (1GB,
2MB, 4KB). With Dynamic PAMT (DPAMT), the 4KB level of PAMT is allocated
on demand. The kernel supplies the TDX module with page pairs to store the
4KB level entries, which cover 2MB of host physical memory. The kernel must
provide this page pair before using pages from the range for TDX. If this
is not done, SEAMCALLs that give the pages to be protected by the TDX
module will fail.
Allocate reference counters for every 2MB range to track TDX memory usage.
This can be used to handle concurrent get/put callers, in order to
accurately determine when the dynamic 4KB level of DPAMT needs to be
allocated and when it can be freed.
This allocation will currently consume 2MB for every 1TB of address
space from 0 to max_pfn. The allocation size will depend on how the RAM is
physically laid out. In a worst case scenario where the entire 52 bit
address space is covered this would be 8GB. Then the DPAMT refcount
allocations could hypothetically cause the savings from DPAMT to go
negative on exotic platforms with sparse, small amounts of memory.
Future changes could reduce this refcount overhead to be only allocating
refcounts for physical ranges that contain memory that TDX can use.
However, this is left for future work.
AI was used under supervision to collect/apply feedback, review code and
workshop logs.
Based on a patch originally by Kiryl Shutsemau.
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@xxxxxxxxx>
Tested-by: Hongyu Ning <hongyu.ning@xxxxxxxxxxxxxxx>
Reviewed-by: Binbin Wu <binbin.wu@xxxxxxxxxxxxxxx>
Reviewed-by: Chao Gao <chao.gao@xxxxxxxxx>
Reviewed-by: Yan Zhao <yan.y.zhao@xxxxxxxxx>
Reviewed-by: Tony Lindgren <tony.lindgren@xxxxxxxxxxxxxxx>
Reviewed-by: Vishal Annapurve <vannapurve@xxxxxxxxxx>
Acked-by: Sohil Mehta <sohil.mehta@xxxxxxxxx>
---
v10:
- Change "Dynamic PAMT" to "DPAMT" in comments, and in the second
reference in the logs. (Dave)
- Rename some other pamt gunk to dpamt (Dave)
v7:
- Annotate functions __init. (Chao)
- Log tweaks. (Yan)
- Standardize on memory units in the text. (Sohil)
- Delete unneeded comment. (Sohil)
- Use vzalloc(). (Sohil)
- Drop Assisted-by tag and cover AI use in log. (Dave)
v6:
- Remove confusing reference to allocating PAMT memory in
pamt_refcounts comment. (Yan)
- Rename "metadata" function names that really deal with refcounts, as
metadata already has a different meaning in TDX.
- Move tdx_find_pamt_refcount() to this patch to aid in reviewability.
---
arch/x86/virt/vmx/tdx/tdx.c | 57 +++++++++++++++++++++++++++++++++++--
1 file changed, 54 insertions(+), 3 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 9caaa5de28817..305289bd673be 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -30,6 +30,7 @@
#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include <linux/idr.h>
+#include <linux/vmalloc.h>
#include <asm/page.h>
#include <asm/special_insns.h>
#include <asm/msr-index.h>
@@ -66,6 +67,14 @@ static DEFINE_PER_CPU(bool, tdx_lp_initialized);
static struct tdmr_info_list tdx_tdmr_list;
+/*
+ * On a machine with DPAMT, the kernel maintains a reference counter
+ * for every 2MB range. The counter indicates how many users there are for
+ * the DPAMT at the 2MB range. The kernel allocates DPAMT refcounts at
+ * initialization.
+ */
+static atomic_t *dpamt_refcounts;
+
/* All TDX-usable memory regions. Protected by mem_hotplug_lock. */
static LIST_HEAD(tdx_memlist);
@@ -255,6 +264,42 @@ static struct syscore tdx_syscore = {
.ops = &tdx_syscore_ops,
};
+/*
+ * Allocate DPAMT reference counters for all physical memory.
+ *
+ * It consumes 2MB for every 1TB of physical memory.
+ */
+static __init int init_dpamt_refcounts(void)
+{
+ size_t size = DIV_ROUND_UP(max_pfn, PTRS_PER_PTE) * sizeof(*dpamt_refcounts);
+
+ if (!tdx_supports_dynamic_pamt(&tdx_sysinfo))
+ return 0;
+
+ dpamt_refcounts = vzalloc(size);
+ if (!dpamt_refcounts)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static __init void free_dpamt_refcounts(void)
+{
+ if (!tdx_supports_dynamic_pamt(&tdx_sysinfo))
+ return;
+
+ vfree(dpamt_refcounts);
+ dpamt_refcounts = NULL;
+}
+
+static __maybe_unused atomic_t *tdx_find_dpamt_refcount(unsigned long pfn)
+{
+ /* Find which PMD a PFN is in. */
+ unsigned long index = pfn >> (PMD_SHIFT - PAGE_SHIFT);
+
+ return &dpamt_refcounts[index];
+}
+
/*
* Add a memory region as a TDX memory block. The caller must make sure
* all memory regions are added in address ascending order and don't
@@ -1155,10 +1200,14 @@ static __init int init_tdx_module(void)
*/
get_online_mems();
- ret = build_tdx_memlist(&tdx_memlist);
+ ret = init_dpamt_refcounts();
if (ret)
goto out_put_tdxmem;
+ ret = build_tdx_memlist(&tdx_memlist);
+ if (ret)
+ goto err_free_dpamt_refcounts;
+
/* Allocate enough space for constructing TDMRs */
ret = alloc_tdmr_list(&tdx_tdmr_list, &tdx_sysinfo.tdmr);
if (ret)
@@ -1208,6 +1257,8 @@ static __init int init_tdx_module(void)
free_tdmr_list(&tdx_tdmr_list);
err_free_tdxmem:
free_tdx_memlist(&tdx_memlist);
+err_free_dpamt_refcounts:
+ free_dpamt_refcounts();
goto out_put_tdxmem;
}
@@ -2159,7 +2210,7 @@ static void __tdx_pamt_put(kvm_pfn_t pfn)
* This function is currently only safe to call once. And not safe to call
* if __tdx_pamt_get() is called before or after.
*/
-static struct page * __maybe_unused __tdx_alloc_control_page(void)
+static __maybe_unused struct page *__tdx_alloc_control_page(void)
{
struct page *page;
@@ -2184,7 +2235,7 @@ static struct page * __maybe_unused __tdx_alloc_control_page(void)
* it, and no other pages in the aligned 2MB physical region will
* still need the backing.
*/
-static void __maybe_unused __tdx_free_control_page(struct page *page)
+static __maybe_unused void __tdx_free_control_page(struct page *page)
{
if (!page)
return;
--
2.55.0