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

