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

From: Frederic Griffoul

Date: Thu Oct 29 2026 - 13:08:02 EST


On Thu, Oct 29, 2026 at 06:25:49AM -0700, Ackerley Tng wrote:
> Fred Griffoul <griffoul@xxxxxxxxx> writes:
>
> > + 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?

Good point. As far as I can see, kvm_mmu_unmap_gfn_range() is only
kvm_mmu_invalidate_range_add() plus kvm_unmap_gfn_range(), so
guest_memfd could do the two steps itself: add the blocks around the
range, then unmap the exact range. The zap stays exact and the range is
added once. Or am I missing something about kvm_mmu_unmap_gfn_range()
that a refactor would need to keep?

> 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.

Agreed. guest_memfd should then only reduce the order for shared and
private pages, and drop the gfn alignment and the rounding to a
page-table size, which KVM does. For the provider, "contiguity" would
mean a naturally aligned block of contiguous frames with the same
attributes, the frames aligned to the block too, so a mapping of that
size is valid as is.

One gap: KVM does not cover a memslot whose gfn and guest_memfd offset
are not aligned to each other. On x86, kvm_alloc_memslot_metadata()
disallows large pages when base_gfn and userspace_addr are misaligned,
but nothing compares base_gfn with gmem.pgoff, and kvm_gmem_bind()
takes any page-aligned offset. A gmem.pgoff check next to the
userspace_addr one would keep that in KVM too.

The export lookup in the series already works this way: it reports the
provider's block, reduced for shared and private pages. iommufd maps
PAGE_SIZE IOPTEs in this series, and will pick larger IOPTEs from that
block later.

> 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.

Right, userspace mappings are PAGE_SIZE for now.

Deferring order > 0 for KVM and userspace is fine with me. I'd like to
keep the order in the export lookup, though: large IOPTEs matter for
device performance, and I'm preparing iommupt changes to split and merge
them in place.

Fred

On Thu, Oct 29, 2026 at 1:25 PM Ackerley Tng <ackerleytng@xxxxxxxxxx> wrote:
>
> 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...]
> >