Re: [PATCH v4 12/16] x86/virt/tdx: Add helpers to allow for pre-allocating pages

From: Binbin Wu

Date: Tue Nov 25 2025 - 22:40:40 EST




On 11/21/2025 8:51 AM, Rick Edgecombe wrote:
In the KVM fault path page, tables and private pages need to be
"In the KVM fault path page, tables ..." should be
"In the KVM fault path, page tables ..."

installed under a spin lock. This means that the operations around
installing PAMT pages for them will not be able to allocate pages.

[...]
@@ -141,7 +142,46 @@ int tdx_guest_keyid_alloc(void);
u32 tdx_get_nr_guest_keyids(void);
void tdx_guest_keyid_free(unsigned int keyid);
-int tdx_pamt_get(struct page *page);
+int tdx_dpamt_entry_pages(void);
+
+/*
+ * Simple structure for pre-allocating Dynamic
+ * PAMT pages outside of locks.

It's not just for Dynamic PAMT pages, but also external page table pages.

+ */
+struct tdx_prealloc {
+ struct list_head page_list;
+ int cnt;
+};
+
+static inline struct page *get_tdx_prealloc_page(struct tdx_prealloc *prealloc)
+{
+ struct page *page;
+
+ page = list_first_entry_or_null(&prealloc->page_list, struct page, lru);
+ if (page) {
+ list_del(&page->lru);
+ prealloc->cnt--;
+ }
+
+ return page;
+}
+
+static inline int topup_tdx_prealloc_page(struct tdx_prealloc *prealloc, unsigned int min_size)
+{
+ while (prealloc->cnt < min_size) {
+ struct page *page = alloc_page(GFP_KERNEL_ACCOUNT);
+
+ if (!page)
+ return -ENOMEM;
+
+ list_add(&page->lru, &prealloc->page_list);
+ prealloc->cnt++;
+ }
+
+ return 0;
+}
+
+int tdx_pamt_get(struct page *page, struct tdx_prealloc *prealloc);
void tdx_pamt_put(struct page *page);
struct page *tdx_alloc_page(void);
@@ -219,6 +259,7 @@ static inline int tdx_enable(void) { return -ENODEV; }
static inline u32 tdx_get_nr_guest_keyids(void) { return 0; }
static inline const char *tdx_dump_mce_info(struct mce *m) { return NULL; }
static inline const struct tdx_sys_info *tdx_get_sysinfo(void) { return NULL; }
+static inline int tdx_dpamt_entry_pages(void) { return 0; }
#endif /* CONFIG_INTEL_TDX_HOST */
#ifdef CONFIG_KEXEC_CORE
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 260bb0e6eb44..61a058a8f159 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -1644,23 +1644,34 @@ static int tdx_mem_page_add(struct kvm *kvm, gfn_t gfn, enum pg_level level,
static void *tdx_alloc_external_fault_cache(struct kvm_vcpu *vcpu)
{
- struct vcpu_tdx *tdx = to_tdx(vcpu);
+ struct page *page = get_tdx_prealloc_page(&to_tdx(vcpu)->prealloc);
- return kvm_mmu_memory_cache_alloc(&tdx->mmu_external_spt_cache);
+ if (WARN_ON_ONCE(!page))
+ return (void *)__get_free_page(GFP_ATOMIC | __GFP_ACCOUNT);

kvm_mmu_memory_cache_alloc() calls BUG_ON() if the atomic allocation failed.
Do we want to follow?

+
+ return page_address(page);
}
static int tdx_topup_external_fault_cache(struct kvm_vcpu *vcpu, unsigned int cnt)
{
- struct vcpu_tdx *tdx = to_tdx(vcpu);
+ struct tdx_prealloc *prealloc = &to_tdx(vcpu)->prealloc;
+ int min_fault_cache_size;
- return kvm_mmu_topup_memory_cache(&tdx->mmu_external_spt_cache, cnt);
+ /* External page tables */
+ min_fault_cache_size = cnt;
+ /* Dynamic PAMT pages (if enabled) */
+ min_fault_cache_size += tdx_dpamt_entry_pages() * PT64_ROOT_MAX_LEVEL;

Is the value PT64_ROOT_MAX_LEVEL intended, since dynamic PAMT pages are only
needed for 4KB level?

+
+ return topup_tdx_prealloc_page(prealloc, min_fault_cache_size);
}

[...]