Re: [PATCH v4 02/17] x86/virt/tdx: Add a SEAMCALL wrapper to demote a 2MB huge page

From: Yan Zhao

Date: Fri Oct 23 2026 - 04:14:05 EST


On Thu, Oct 22, 2026 at 11:51:48PM +0800, Edgecombe, Rick P wrote:
> On Thu, 2026-10-22 at 16:46 +0800, Yan Zhao wrote:
> > u64 tdh_mem_page_demote_2mb(struct tdx_td *td, u64 gpa, kvm_pfn_t pfn, struct
> > page *new_sept_pt,
> >                             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(PG_LEVEL_2M),
> >                 .rdx = tdx_tdr_pa(td),
> >                 .r8 = page_to_phys(new_sept_pt),
> >         };
> >         struct page *dpamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT];
> >         atomic_t *dpamt_refcount;
> >         u64 ret;
> >
> >         /*
> >          * For implementation simplicity, only support demotion when
> >          * DPAMT is enabled.
> >          */
> >         WARN_ON_ONCE(!tdx_supports_dynamic_pamt(&tdx_sysinfo));
> >
> >         /* A NULL pamt_cache is unexpected, so failure indicates a software
> > error */
>
> On the edge of unnecessary to me, but ok.
I'll remove this comment then :)

> >         ret = alloc_pamt_array(dpamt_pages, pamt_cache);
> >         if (ret) {
> >                 *ext_err1 = 0;
> >                 *ext_err2 = 0;
> >                 return TDX_SW_ERROR;
> >         }
> >
> >         dpamt_refcount = tdx_find_dpamt_refcount(pfn);
> >
> >         args.r12 = page_to_phys(dpamt_pages[0]);
> >         args.r13 = page_to_phys(dpamt_pages[1]);
> >
> >         /*
> >          * The dpamt_lock is used to avoid contention between the
> >          * TDH_MEM_PAGE_DEMOTE and TDH_PHYMEM_PAMT_{ADD/REMOVE} SEAMCALLs.
> >          */
> >         spin_lock(&dpamt_lock);
> >
> >         ret = seamcall_saved_ret(TDH_MEM_PAGE_DEMOTE, &args);
> >         *ext_err1 = args.rcx;
> >         *ext_err2 = args.rdx;
> >
> >         if (WARN_ON_ONCE(ret != TDX_SUCCESS))
> >                 goto out_free;
> >
> >         atomic_set(dpamt_refcount, PTRS_PER_PMD);
> >         spin_unlock(&dpamt_lock);
> >
> >         return TDX_SUCCESS;
> >
> > out_free:
> >         spin_unlock(&dpamt_lock);
> >         free_pamt_array(pamt_cache, dpamt_pages);
> >
> >         return ret;
> > }
>
> LGTM
Oops, I accidentally included changes for the workaround for a TDX module that
does not support uninterruptible demote, and forgot that the busy error is valid.

Could you please check if the following version looks good to you?
Changes vs the last version:
- Added WARN on !tdx_huge_page_demote_uninterruptible() and updated the comment
above the warning.
- Do not warn on TDX_OPERAND_BUSY.
- Removed the comment above alloc_pamt_array().

Thanks a lot!

u64 tdh_mem_page_demote_2mb(struct tdx_td *td, u64 gpa, kvm_pfn_t pfn,
struct page *new_pt, 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(PG_LEVEL_2M),
.rdx = tdx_tdr_pa(td),
.r8 = page_to_phys(new_pt),
};
struct page *dpamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT];
atomic_t *dpamt_refcount;
u64 ret;

/*
* For simplicity, only support demotion when DPAMT is enabled and when the
* TDH_MEM_PAGE_DEMOTE SEAMCALL does not return TDX_INTERRUPTED_RESTARTABLE.
*/
WARN_ON_ONCE(!tdx_huge_page_demote_uninterruptible(&tdx_sysinfo) ||
!tdx_supports_dynamic_pamt(&tdx_sysinfo));

if (alloc_pamt_array(dpamt_pages, pamt_cache)) {
*ext_err1 = 0;
*ext_err2 = 0;
return TDX_SW_ERROR;
}

dpamt_refcount = tdx_find_dpamt_refcount(pfn);

args.r12 = page_to_phys(dpamt_pages[0]);
args.r13 = page_to_phys(dpamt_pages[1]);

/*
* The dpamt_lock is used to avoid contention between the
* TDH_MEM_PAGE_DEMOTE and TDH_PHYMEM_PAMT_{ADD/REMOVE} SEAMCALLs.
*/
spin_lock(&dpamt_lock);

ret = seamcall_saved_ret(TDH_MEM_PAGE_DEMOTE, &args);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;

if (ret != TDX_SUCCESS) {
WARN_ON_ONCE((ret & TDX_SEAMCALL_STATUS_MASK) != TDX_OPERAND_BUSY);
goto out_free;
}

atomic_set(dpamt_refcount, PTRS_PER_PMD);
spin_unlock(&dpamt_lock);

return TDX_SUCCESS;

out_free:
spin_unlock(&dpamt_lock);
free_pamt_array(pamt_cache, dpamt_pages);
return ret;
}
EXPORT_SYMBOL_FOR_KVM(tdh_mem_page_demote_2mb);