Re: [PATCH v4 18/18] KVM: guest_memfd: Combine .gmem_prepare()+.gmem_invalidate() into .gmem_convert()
From: Sean Christopherson
Date: Mon Jul 13 2026 - 15:57:02 EST
On Mon, Jul 13, 2026, Fuad Tabba wrote:
> On Mon, 13 Jul 2026 at 16:41, Ackerley Tng <ackerleytng@xxxxxxxxxx> wrote:
> >
> > Fuad Tabba <fuad.tabba@xxxxxxxxx> writes:
> >
> > >
> > > [...snip...]
> > >
> >
> > Thanks for bringing [1] up, I didn't make the link when this change was
> > being discussed.
> >
> > pKVM definitely needs a callback when the folio is released from
> > guest_memfd's ownership.
> >
> > >
> > > free_folio is the only to_private=false caller today, but the cover
> > > letter sketches the other one, a private->shared conversion through
> > > kvm_gmem_make_shared(inode, start, end). Once that lands both reach
> > > the hook with to_private=false, and as far as I can tell, the only
> > > thing separating them is kvm == NULL / INVALID_GPA. That isn't
> >
> > On the folio teardown path kvm has to be non-NULL so that SNP can find
> > out which VM to unassign from, so you're right that INVALID_GPA won't be
> > sufficient to separate conversion to private and teardown, for pKVM,
> > from the arch perspective.
> >
> > > documented as a contract, and nothing in-tree reads it on that branch
> > > (the kvm/gpa check is under if (to_private), and
> > > sev_gmem_make_shared() doesn't take a kvm at all), so nothing would
> > > catch it regressing. The sketch is also inode-scoped, so I'm not sure
> > > the conversion path passes a non-NULL kvm at all; if it doesn't, the
> > > two aren't distinguishable.
> > >
> > >>
> > >> [...snip...]
> > >>
> > >
> > > The Kconfig now couples them too: an arch can't take teardown without also
> > > taking conversion. pKVM wants teardown and not conversion, so that's a problem
> > > for us today, not only once the second caller lands.
> > >
> > > This is the same ground as v8 15/46, which ended with the two split under
> > > separate Kconfigs. Suzuki confirmed CCA disables both; pKVM wants teardown only,
> > > since our conversions are guest-initiated and EL2 has already done the
> > > transition by the time the host runs the generic path [1].
> > >
> > > I'd rather keep the free path on its own callback. If you want a single hook,
> > > passing the caller explicitly instead of leaving it implied by kvm would work
> > > too. Merging the two conversion directions is a genuine cleanup, it's the free
> > > path I'd like to keep separate.
> > >
> >
> > Is it okay if later, we add a .gmem_teardown callback with a new CONFIG
> > flag that pKVM can use?
>
> That works for me, and I agree it's nicer to introduce the callback along
> with its first user.
>
> Sean, is that the plan you have in mind as well? Two things that matter for
> pKVM when it lands: the new CONFIG needs to be independent of the conversion
> one, so that free_folio fires for an arch that selects only teardown (pKVM
> wants teardown and not conversion), and the teardown callback stays distinct
> from a private->shared conversion. Good point on teardown needing a non-NULL
> kvm for SNP,
Converting back to SHARED doesn't require a non-NULL @kvm pointer. SNP needs
the ASID and thus the VM when assigning memory to a VM, but converting memory
back to SHARED, i.e. returning to the host, doesn't need that information.
> incidentally, that's another reason keying off the sentinel wouldn't have
> worked.
It can work, but it would be all kinds of gross, and arguably wrong. To pass
a non-NULL @kvm for the true convert-to-shared (not reclaim), guest_memfd would
need to iterate over all bindings to get at the @kvm instances. That's "fine",
but wasteful and confusing because the SNP conversion is a platform-level thing
(as above, unassigning from a VM doesn't require any information about the VM).
> If that's where this is heading, I'm happy.
Yeah, this is roughly what the total diff will look like.
diff --git arch/x86/include/asm/kvm-x86-ops.h arch/x86/include/asm/kvm-x86-ops.h
index 6091987860bd..1764bad03b17 100644
--- arch/x86/include/asm/kvm-x86-ops.h
+++ arch/x86/include/asm/kvm-x86-ops.h
@@ -146,7 +146,7 @@ KVM_X86_OP(vcpu_deliver_sipi_vector)
KVM_X86_OP_OPTIONAL_RET0(vcpu_get_apicv_inhibit_reasons);
KVM_X86_OP_OPTIONAL(get_untagged_addr)
KVM_X86_OP_OPTIONAL(alloc_apic_backing_page)
-#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
+#if defined(CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT) || defined(CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM)
KVM_X86_OP_OPTIONAL_RET0(gmem_convert)
#endif
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_INVALIDATE
diff --git arch/x86/include/asm/kvm_host.h arch/x86/include/asm/kvm_host.h
index a70e734af122..08c4af487fa1 100644
--- arch/x86/include/asm/kvm_host.h
+++ arch/x86/include/asm/kvm_host.h
@@ -1724,7 +1724,7 @@ struct kvm_x86_ops {
gva_t (*get_untagged_addr)(struct kvm_vcpu *vcpu, gva_t gva, unsigned int flags);
void *(*alloc_apic_backing_page)(struct kvm_vcpu *vcpu);
-#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
+#if defined(CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT) || defined(CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM)
int (*gmem_convert)(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
kvm_pfn_t nr_pages, int max_order, bool to_private);
#endif
diff --git arch/x86/kvm/Kconfig arch/x86/kvm/Kconfig
index 52e75d83a4d2..538ed1e80332 100644
--- arch/x86/kvm/Kconfig
+++ arch/x86/kvm/Kconfig
@@ -161,6 +161,7 @@ config KVM_AMD_SEV
select ARCH_HAS_CC_PLATFORM
select KVM_GENERIC_MEMORY_ATTRIBUTES
select HAVE_KVM_ARCH_GMEM_CONVERT
+ select HAVE_KVM_ARCH_GMEM_RECLAIM
select HAVE_KVM_ARCH_GMEM_INVALIDATE
select HAVE_KVM_ARCH_GMEM_POPULATE
help
diff --git arch/x86/kvm/x86.c arch/x86/kvm/x86.c
index f0c49a92d041..11017f49b94a 100644
--- arch/x86/kvm/x86.c
+++ arch/x86/kvm/x86.c
@@ -10625,6 +10625,16 @@ int kvm_arch_gmem_convert(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
{
return kvm_x86_call(gmem_convert)(kvm, gfn, pfn, nr_pages, max_order, to_private);
}
+#endif
+
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
+void kvm_arch_gmem_reclaim(kvm_pfn_t pfn, kvm_pfn_t nr_pages, int max_order)
+{
+ WARN_ON_ONCE(kvm_x86_call(gmem_convert)(NULL, -1ull, pfn, nr_pages, max_order, false));
+}
+#endif
+
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_INVALIDATE
void kvm_arch_gmem_invalidate_range(struct kvm *kvm, struct kvm_gfn_range *range)
{
kvm_x86_call(gmem_invalidate_range)(kvm, range);
diff --git include/linux/kvm_host.h include/linux/kvm_host.h
index 9391ffcc90ff..fdbd697d0337 100644
--- include/linux/kvm_host.h
+++ include/linux/kvm_host.h
@@ -2614,6 +2614,10 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src,
kvm_gmem_populate_cb post_populate, void *opaque);
#endif
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
+void kvm_arch_gmem_reclaim(kvm_pfn_t pfn, kvm_pfn_t nr_pages, int order);
+#endif
+
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_INVALIDATE
void kvm_arch_gmem_invalidate_range(struct kvm *kvm, struct kvm_gfn_range *range);
#endif
diff --git virt/kvm/Kconfig virt/kvm/Kconfig
index 53803c5777a4..c3c0ee253fc7 100644
--- virt/kvm/Kconfig
+++ virt/kvm/Kconfig
@@ -111,6 +111,10 @@ config HAVE_KVM_ARCH_GMEM_CONVERT
bool
depends on KVM_GUEST_MEMFD
+config HAVE_KVM_ARCH_GMEM_RECLAIM
+ bool
+ depends on KVM_GUEST_MEMFD
+
config HAVE_KVM_ARCH_GMEM_INVALIDATE
bool
depends on KVM_GUEST_MEMFD
diff --git virt/kvm/guest_memfd.c virt/kvm/guest_memfd.c
index aeb765831c32..173a7ed008d1 100644
--- virt/kvm/guest_memfd.c
+++ virt/kvm/guest_memfd.c
@@ -516,13 +516,11 @@ static int kvm_gmem_error_folio(struct address_space *mapping, struct folio *fol
return MF_DELAYED;
}
-#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
static void kvm_gmem_free_folio(struct folio *folio)
{
- WARN_ON_ONCE(kvm_arch_gmem_convert(NULL, -1ull,
- folio_file_pfn(folio, 0),
- folio_nr_pages(folio),
- folio_order(folio), false));
+ kvm_arch_gmem_reclaim(folio_file_pfn(folio, 0), folio_nr_pages(folio),
+ folio_order(folio));
}
#endif
@@ -530,7 +528,7 @@ static const struct address_space_operations kvm_gmem_aops = {
.dirty_folio = noop_dirty_folio,
.migrate_folio = kvm_gmem_migrate_folio,
.error_remove_folio = kvm_gmem_error_folio,
-#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
+#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_RECLAIM
.free_folio = kvm_gmem_free_folio,
#endif
};