Re: [RFC PATCH v5 30/45] x86/virt/tdx: Add API to demote a 2MB mapping to 512 4KB mappings

From: Yan Zhao

Date: Wed Jul 01 2026 - 23:35:09 EST


On Wed, Jul 01, 2026 at 12:35:34AM +0800, Edgecombe, Rick P wrote:
> On Tue, 2026-06-30 at 10:36 +0800, Yan Zhao wrote:
> > > It feels kind of hacky. The refcount stuff is already a bit hard to follow.
> > After demotion, there would be 512 4KB mappings for this 2MB range.
> > Then, in later tdx_sept_remove_leaf_spte(), tdx_pamt_put() would be invoked
> > for 512 times. That's why we need to set the refcount to 512 after a
> > successful
> > demotion here.
>
> Oops, I thought I deleted it before I sent the mail. After thinking more, it
> seems reasonable. But I didn't check it thoroughly with the other flows yet.

The new wrapper implementation is as below.
I adjusted the pamt_refcount part and made TDX huge page rely on DPAMT.
Will add the following justification for the dependency on DPAMT:
A TDX module supporting non-interruptible DEMOTE usually also supports DPAMT.
Having TDX huge page rely on DPAMT as well simplifies the implementation.

u64 tdh_mem_page_demote(struct tdx_td *td, u64 gpa, enum pg_level level, u64 pfn,
struct page *new_sp, struct tdx_pamt_cache *pamt_cache,
u64 *ext_err1, u64 *ext_err2)
{
struct tdx_module_args args = {
.rcx = gpa | pg_level_to_tdx_sept_level(level),
.rdx = tdx_tdr_pa(td),
.r8 = page_to_phys(new_sp),
};
atomic_t *pamt_refcount = tdx_find_pamt_refcount(pfn);
struct page *pamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT];
u64 ret;

if (!tdx_huge_page_demote_uninterruptible(&tdx_sysinfo) ||
!tdx_supports_dynamic_pamt(&tdx_sysinfo) || level != PG_LEVEL_2M)
return TDX_SW_ERROR;

if (alloc_pamt_array(pamt_pages, pamt_cache))
return TDX_SW_ERROR;

/* Flush the new S-EPT page to be added */
tdx_clflush_page(new_sp);

/* PAMT page pair to track the 2MB guest memory */
args.r12 = page_to_phys(pamt_pages[0]);
args.r13 = page_to_phys(pamt_pages[1]);

spin_lock(&pamt_lock);

/*
* Before demotion, the 2MB guest memory range is tracked in a
* statically allocated pamt 2MB entry, so the pamt_refcount of the 2MB
* range should be 0.
*/
if (atomic_read(pamt_refcount)) {
WARN_ON_ONCE(1);
ret = TDX_SW_ERROR;
goto out;
}

ret = seamcall_saved_ret(TDH_MEM_PAGE_DEMOTE, &args);
if (ret)
goto out;

/* After a sucessful demotion from 2MB to 4KB, set the refcount to 512.
* Subsequent removal of each 4KB mapping will decrease the refcount by
* 1.
*/
atomic_set(pamt_refcount, PTRS_PER_PMD);

out:
spin_unlock(&pamt_lock);

if (ret) {
free_pamt_array(pamt_pages);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;
}

return ret;
}