[RFC PATCH 07/11] KVM: guest_memfd: Add ops-driven page revocation
From: David Woodhouse
Date: Thu Jul 16 2026 - 11:54:11 EST
From: David Woodhouse <dwmw@xxxxxxxxxxxx>
Add kvm_gmem_invalidate_range(): a guest_memfd backing calls it to zap
the guest secondary-MMU mapping for a gfn range so the next access
re-faults through ops->get_pfn(). Mirrors the existing internal
__kvm_gmem_invalidate_start() for a single bound slot, bracketing the
unmap with the mmu_invalidate window so a racing vCPU fault retries
rather than installing a stale mapping.
The existing ops->invalidate callback is KVM -> implementation
(cleanup); this new symbol is the implementation -> KVM path, useful
for an overcommit manager or any subsystem that reclaims pages from a
running guest. Export it for module use and add it to the KVM
external-export allowlist.
Signed-off-by: David Woodhouse (Kiro) <dwmw@xxxxxxxxxxxx>
---
arch/x86/kvm/Makefile | 3 ++-
include/linux/kvm_host.h | 9 +++++++++
virt/kvm/guest_memfd.c | 39 +++++++++++++++++++++++++++++++++++++++
3 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile
index 77337c37324b..cb963d6ecaab 100644
--- a/arch/x86/kvm/Makefile
+++ b/arch/x86/kvm/Makefile
@@ -63,7 +63,8 @@ exports_grep_trailer := --include='*.[ch]' -nrw $(srctree)/virt/kvm $(srctree)/a
-e kvm_write_track_remove_gfn \
-e kvm_get_kvm \
-e kvm_get_kvm_safe \
- -e kvm_put_kvm
+ -e kvm_put_kvm \
+ -e kvm_gmem_invalidate_range
# Force grep to emit a goofy group separator that can in turn be replaced with
# the above newline macro (newlines in Make are a nightmare). Note, grep only
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index b33d2f475042..04fa0cb126f6 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -701,6 +701,15 @@ static inline bool is_kvm_gmem_file(struct file *file)
return file && file->f_op == &kvm_gmem_fops;
}
+/*
+ * Outbound (implementation -> KVM) revocation: zap the guest's secondary MMU
+ * (NPT/EPT) mapping for a gfn range so the next guest access re-faults
+ * through the ops' get_pfn(). This is the reverse of ->invalidate (which is
+ * KVM -> implementation) and is what a backing that manages its own memory
+ * (e.g. an overcommit manager) uses to reclaim a page from a running guest.
+ */
+void kvm_gmem_invalidate_range(struct kvm *kvm, gfn_t start, gfn_t end);
+
#endif /* CONFIG_KVM_GUEST_MEMFD */
static inline bool kvm_slot_dirty_track_enabled(const struct kvm_memory_slot *slot)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 2b277468a12f..6c13742de068 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -738,6 +738,45 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
return __kvm_gmem_create(kvm, size, flags);
}
+/*
+ * Outbound (implementation -> KVM) revocation: an implementation zaps the
+ * guest's secondary MMU mapping for a gfn range so the next guest access
+ * re-faults through ops->get_pfn(). Mirrors __kvm_gmem_invalidate_start()
+ * for a single bound slot, bracketing the unmap with the mmu_invalidate
+ * window so a racing vCPU fault retries rather than installing a stale
+ * mapping.
+ */
+void kvm_gmem_invalidate_range(struct kvm *kvm, gfn_t start, gfn_t end)
+{
+ struct kvm_memory_slot *slot;
+ bool flush = false;
+ int idx;
+
+ idx = srcu_read_lock(&kvm->srcu);
+
+ slot = gfn_to_memslot(kvm, start);
+ if (slot) {
+ struct kvm_gfn_range gfn_range = {
+ .slot = slot,
+ .start = start,
+ .end = min(end, slot->base_gfn + slot->npages),
+ .may_block = true,
+ .attr_filter = KVM_FILTER_SHARED | KVM_FILTER_PRIVATE,
+ };
+
+ KVM_MMU_LOCK(kvm);
+ kvm_mmu_invalidate_start(kvm);
+ flush = kvm_mmu_unmap_gfn_range(kvm, &gfn_range);
+ if (flush)
+ kvm_flush_remote_tlbs(kvm);
+ kvm_mmu_invalidate_end(kvm);
+ KVM_MMU_UNLOCK(kvm);
+ }
+
+ srcu_read_unlock(&kvm->srcu, idx);
+}
+EXPORT_SYMBOL_GPL(kvm_gmem_invalidate_range);
+
static const struct kvm_gmem_ops *kvm_gmem_get_ops(struct file *file)
{
if (!is_kvm_gmem_file(file))
--
2.54.0