From: David Woodhouse <[email protected]> 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) <[email protected]> --- 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

