[RFC PATCH v2 07/11] KVM: guest_memfd: Add ops-driven page revocation
From: David Woodhouse
Date: Mon Jul 20 2026 - 07:16:07 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>
---
include/linux/kvm_host.h | 9 +++++++
virt/kvm/guest_memfd.c | 52 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
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 057f90eb835a..1da22da0a6ec 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -738,6 +738,58 @@ 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;
+ struct kvm_memslot_iter iter;
+ bool flush = false;
+ int as_id;
+ int idx;
+
+ idx = srcu_read_lock(&kvm->srcu);
+
+ /*
+ * Walk every address space (SMM has its own on x86) and every
+ * intersecting memslot. A single invalidation may span several
+ * slots and holes: skip the holes, clamp to each slot's range.
+ */
+ KVM_MMU_LOCK(kvm);
+ kvm_mmu_invalidate_start(kvm);
+
+ for (as_id = 0; as_id < kvm_arch_nr_memslot_as_ids(kvm); as_id++) {
+ struct kvm_memslots *slots = __kvm_memslots(kvm, as_id);
+
+ kvm_for_each_memslot_in_gfn_range(&iter, slots, start, end) {
+ struct kvm_gfn_range gfn_range;
+
+ slot = iter.slot;
+ gfn_range.slot = slot;
+ gfn_range.start = max(start, slot->base_gfn);
+ gfn_range.end = min(end, slot->base_gfn + slot->npages);
+ gfn_range.may_block = true;
+ gfn_range.attr_filter = KVM_FILTER_SHARED | KVM_FILTER_PRIVATE;
+
+ 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.55.0