Re: [PATCH v4 01/17] x86/virt/tdx: Enhance tdx_pamt_get/put() to support huge pages

From: Yan Zhao

Date: Sat Oct 10 2026 - 06:21:03 EST


On Fri, Oct 02, 2026 at 07:02:40AM +0800, Edgecombe, Rick P wrote:
> On Mon, 2026-09-28 at 17:08 +0800, Yan Zhao wrote:
> > The TDX module uses PAMT to track metadata for every physical page (within
> > TDMR) across three levels: 1GB, 2MB, and 4KB. The VMM is responsible for
> > providing backing pages for the PAMT. Without Dynamic PAMT (DPAMT), all
> > PAMT backing pages are allocated statically at system boot time. With DPAMT
> > enabled, PAMT backing pages tracking physical pages at the 4KB level
> > (referred to as DPAMT pages) are dynamically installed and uninstalled at
> > runtime.
> >
> > Currently, the VMM invokes tdx_pamt_get()/tdx_pamt_put() to install and
> > uninstall DPAMT pages under the assumption that all physical pages are
> > tracked at 4KB level. However, when a guest page is mapped as a huge page
> > in the S-EPT, the TDX module tracks it directly at the huge page level,
> > meaning installation and uninstallation of DPAMT pages for huge pages is
> > neither needed nor applicable.
> >
> > Add a "level" parameter to tdx_pamt_get()/tdx_pamt_put() so the helpers
> > can skip DPAMT page installation and uninstallation for huge pages.
>
> I guess this means we need to make sure to get/put at the right level that the
> TDX module is using it as. For example in this implementation, if a huge page
> gets demoted, we can't call them like this:
> 1. tdx_pamt_get(huge_pfn, PG_LEVEL_2MB)
> 2. demote(huge_pfn)
> 3. tdx_pamt_put(huge_pfn, PG_LEVEL_2MB)
Actually, calling in this way is fine, since steps 1 & 3 are effectively no-ops.
However, we can't call them like below.

1. tdx_pamt_get(huge_pfn, PG_LEVEL_2MB)
2. demote(huge_pfn)
3. tdx_pamt_put(huge_pfn, PG_LEVEL_4KB)

That's why 1 & 3 are not required and DPAMT pages management for the private
guest page being demoted are handled in demote().

> So it is up to the caller to reason about what the mapping level is.
Yes.

> Which makes me wonder what is the point of passing level? The caller has to
> reason about what level the TDX module is using the memory at. The
> pamt_get/put() have to take their best guess at the ambiguity. For example line
> 3 above could actually free the PAMT, but it can't know to do that. Unless we
> added some internal tracking, but it seems too much.
>
> There are only three places where a non-4KB level is passed to get/put. We could
> alternatively drop this patch and have KVM decide whether to call get/put or
> not. Since it is actually the only thing that knows the right page size.
> What do you think?
Are you suggesting the implementation in v3 [1]?

In DPAMT combined v5, Sean moved the level checking from KVM to
tdx_pamt_{get/put}() [2].
However, Sean's version introduced __tdx_pamt_{get/put}() for invocations where
4KB size is enforced.

During our internal review, you said:
"Can you also justify why to have two functions instead of page LEVEL_4K in a
few places?"

So, compared to passing level to tdx_pamt_{get/put}() while introducing
__tdx_pamt_{get/put}() for the always-4KB invocations, I updated the patch to
drop __tdx_pamt_{get/put}(), having KVM pass 4KB directly in the always-4KB
invocations.

I'm actually ok with any of these approaches. It seems they all require the
caller to know about the right level size and whether to invoke PAMT get/put.

[1] https://lore.kernel.org/all/20260106102304.25211-1-yan.y.zhao@xxxxxxxxx
[2] https://lore.kernel.org/all/20260129011517.3545883-30-seanjc@xxxxxxxxxx