Re: [RFC PATCH 01/14] KVM: guest_memfd: Clamp the order of PFN mode blocks to what KVM can map

From: Ackerley Tng

Date: Thu Oct 29 2026 - 09:26:07 EST


Fred Griffoul <griffoul@xxxxxxxxx> writes:

> From: Fred Griffoul <fgriffo@xxxxxxxxxxxx>
>
> KVM maps guest memory in 4K, 2M or 1G blocks, and uses guest_memfd's
> max_order to choose. A folio of order N is physically aligned and never
> mixes shared and private pages, so the order needs no check. A PFN mode
> provider's frames need not be aligned

I'll need to think about max_order > 0 more for PFNs. I put it out of my
mind because my frame of mind is that only order-0 folios are supported
today. Perhaps with PFNs it's actually a lot easier to support order >
0.

> and the provider does not know
> which pages are private, yet kvm_gmem_get_pfn() passes its order to KVM
> unchanged. KVM can then map a block over the wrong frames, or with one
> attribute over shared and private pages.
>

Yes, makes sense that private/shared contiguity needs to be taken into
account. I didn't consider this because order-0 is always safe to
map. Thank you!

>
> [...snip...]
>
> -static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
> +static void __kvm_gmem_invalidate_start(struct inode *inode,
> + struct gmem_file *f, pgoff_t start,
> pgoff_t end,
> enum kvm_gfn_range_filter attr_filter)
> {
> + const gfn_t blk = kvm_gmem_is_pfn_mode(GMEM_I(inode)) ?
> + 1UL << PUD_ORDER : 1;
> bool flush = false, found_memslot = false;
> struct kvm_memory_slot *slot;
> struct kvm *kvm = f->kvm;
> @@ -355,6 +360,16 @@ static void __kvm_gmem_invalidate_start(struct gmem_file *f, pgoff_t start,
>
> flush |= kvm_mmu_unmap_gfn_range(kvm, &gfn_range);
>
> + /*
> + * In PFN mode, a fault may map a block of up to PUD_ORDER
> + * pages, sized before this invalidation (see
> + * kvm_gmem_pfn_map_order()): make faults anywhere in the
> + * blocks around the range retry.
> + */
> + kvm_mmu_invalidate_range_add(kvm,
> + round_down(gfn_range.start, blk),
> + round_up(gfn_range.end, blk));
> +

Yes, we want to make the invalidation range larger, but not the range
being zapped. We can't over-zap for CoCo VMs.

Would it be better to refactor kvm_mmu_unmap_gfn_range() instead of
doing kvm_mmu_invalidate_range_add() twice?

> #ifdef CONFIG_HAVE_KVM_ARCH_GMEM_INVALIDATE
> kvm_arch_gmem_invalidate_range(kvm, &gfn_range);
> #endif
> @@ -374,7 +389,7 @@ static void kvm_gmem_invalidate_start(struct inode *inode, pgoff_t start,
> struct gmem_file *f;
>
> kvm_gmem_for_each_file(f, inode)
> - __kvm_gmem_invalidate_start(f, start, end, filter);
> + __kvm_gmem_invalidate_start(inode, f, start, end, filter);
> }
>
> static void __kvm_gmem_invalidate_end(struct gmem_file *f, pgoff_t start,
> @@ -670,7 +685,7 @@ static int kvm_gmem_release(struct inode *inode, struct file *file)
> * Zap all SPTEs pointed at by this file. Do not free the backing
> * memory, as its lifetime is associated with the inode, not the file.
> */
> - __kvm_gmem_invalidate_start(f, 0, -1ul,
> + __kvm_gmem_invalidate_start(inode, f, 0, -1ul,
> kvm_gmem_get_all_gfns_filter(inode));
> __kvm_gmem_invalidate_end(f, 0, -1ul);
>
> @@ -1535,6 +1550,44 @@ static struct folio *__kvm_gmem_get_pfn(struct file *file,
> return folio;
> }
>
> +/*
> + * The order a PFN mode block can be mapped at: the provider's order, no
> + * larger than the alignment of @gfn and @pfn to @index allows, rounded down to
> + * a page-table size, and never over pages that are not all shared or all
> + * private, since KVM maps a block with a single attribute. Called with the
> + * invalidate lock held; an invalidation of any page of the block makes the
> + * fault retry (see __kvm_gmem_invalidate_start()).
> + */
> +static int kvm_gmem_pfn_map_order(struct inode *inode, pgoff_t index,
> + gfn_t gfn, kvm_pfn_t pfn, int order)
> +{
> + u64 misalign = (gfn ^ index) | (pfn ^ index);
> + u64 attrs;
> +
> + if (WARN_ON_ONCE(order < 0))
> + return 0;
> +
> + order = min(order, PUD_ORDER);
> + if (misalign)
> + order = min_t(int, order, __ffs64(misalign));
> +
> + if (order >= PUD_ORDER)
> + order = PUD_ORDER;
> + else if (order >= PMD_ORDER)
> + order = PMD_ORDER;
> + else
> + return 0;
> +
> + attrs = kvm_gmem_get_attributes(inode,
> + mtree_load(&GMEM_I(inode)->attributes, index));
> + while (order &&
> + !kvm_gmem_range_has_attributes(inode,
> + ALIGN_DOWN(index, 1UL << order),
> + 1UL << order, attrs))
> + order = order == PUD_ORDER ? PMD_ORDER : 0;
> + return order;
> +}
> +

I'll review this function more closely again. I think the mapping order
should fit into how KVM does it. After taking guest_memfd's input on
max_order, KVM then lets these parties possibly tune the max order down:

1. lpage_info contains information about highest order due to memslot
alignment and alignment due to VM-level memory attributes
2. CoCo platform can impose a max order
3. Userspace mappings can impose a max order too.

So I think we should follow the same model:

+ The provider reports the max_order based on it's contiguity
information, which is the actual contiguity of memory and
read-only attributes
+ guest_memfd further reduces max_order due to shared/private contiguity
+ After handing it to KVM, KVM further reduces the max order based on
the above.

I think mappings for all other page tables (userspace and IOMMU) should
also follow this approach.

I guess the kvm_gmem_fault_user_mapping() equivalent of this max_order
tuning is missing from this series. :) I think we should defer order > 0
to a later series though. I think read-only-ness should be part of the
first PFN mode series.

>
> [...snip...]
>