Skip to content

Commit cec29ee

Browse files
sean-jcbonzini
authored andcommitted
KVM: Add a dedicated mmu_notifier flag for reclaiming freed memory
Handle AMD SEV's kvm_arch_guest_memory_reclaimed() hook by having __kvm_handle_hva_range() return whether or not an overlapping memslot was found, i.e. mmu_lock was acquired. Using the .on_unlock() hook works, but kvm_arch_guest_memory_reclaimed() needs to run after dropping mmu_lock, which makes .on_lock() and .on_unlock() asymmetrical. Use a small struct to return the tuple of the notifier-specific return, plus whether or not overlap was found. Because the iteration helpers are __always_inlined, practically speaking, the struct will never actually be returned from a function call (not to mention the size of the struct will be two bytes in practice). Signed-off-by: Sean Christopherson <seanjc@google.com> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Fuad Tabba <tabba@google.com> Tested-by: Fuad Tabba <tabba@google.com> Message-Id: <20231027182217.3615211-11-seanjc@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 16f95f3 commit cec29ee

1 file changed

Lines changed: 37 additions & 16 deletions

File tree

virt/kvm/kvm_main.c

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,19 @@ struct kvm_mmu_notifier_range {
561561
bool may_block;
562562
};
563563

564+
/*
565+
* The inner-most helper returns a tuple containing the return value from the
566+
* arch- and action-specific handler, plus a flag indicating whether or not at
567+
* least one memslot was found, i.e. if the handler found guest memory.
568+
*
569+
* Note, most notifiers are averse to booleans, so even though KVM tracks the
570+
* return from arch code as a bool, outer helpers will cast it to an int. :-(
571+
*/
572+
typedef struct kvm_mmu_notifier_return {
573+
bool ret;
574+
bool found_memslot;
575+
} kvm_mn_ret_t;
576+
564577
/*
565578
* Use a dedicated stub instead of NULL to indicate that there is no callback
566579
* function/handler. The compiler technically can't guarantee that a real
@@ -582,22 +595,25 @@ static const union kvm_mmu_notifier_arg KVM_MMU_NOTIFIER_NO_ARG;
582595
node; \
583596
node = interval_tree_iter_next(node, start, last)) \
584597

585-
static __always_inline int __kvm_handle_hva_range(struct kvm *kvm,
586-
const struct kvm_mmu_notifier_range *range)
598+
static __always_inline kvm_mn_ret_t __kvm_handle_hva_range(struct kvm *kvm,
599+
const struct kvm_mmu_notifier_range *range)
587600
{
588-
bool ret = false, locked = false;
601+
struct kvm_mmu_notifier_return r = {
602+
.ret = false,
603+
.found_memslot = false,
604+
};
589605
struct kvm_gfn_range gfn_range;
590606
struct kvm_memory_slot *slot;
591607
struct kvm_memslots *slots;
592608
int i, idx;
593609

594610
if (WARN_ON_ONCE(range->end <= range->start))
595-
return 0;
611+
return r;
596612

597613
/* A null handler is allowed if and only if on_lock() is provided. */
598614
if (WARN_ON_ONCE(IS_KVM_NULL_FN(range->on_lock) &&
599615
IS_KVM_NULL_FN(range->handler)))
600-
return 0;
616+
return r;
601617

602618
idx = srcu_read_lock(&kvm->srcu);
603619

@@ -631,32 +647,31 @@ static __always_inline int __kvm_handle_hva_range(struct kvm *kvm,
631647
gfn_range.end = hva_to_gfn_memslot(hva_end + PAGE_SIZE - 1, slot);
632648
gfn_range.slot = slot;
633649

634-
if (!locked) {
635-
locked = true;
650+
if (!r.found_memslot) {
651+
r.found_memslot = true;
636652
KVM_MMU_LOCK(kvm);
637653
if (!IS_KVM_NULL_FN(range->on_lock))
638654
range->on_lock(kvm);
639655

640656
if (IS_KVM_NULL_FN(range->handler))
641657
break;
642658
}
643-
ret |= range->handler(kvm, &gfn_range);
659+
r.ret |= range->handler(kvm, &gfn_range);
644660
}
645661
}
646662

647-
if (range->flush_on_ret && ret)
663+
if (range->flush_on_ret && r.ret)
648664
kvm_flush_remote_tlbs(kvm);
649665

650-
if (locked) {
666+
if (r.found_memslot) {
651667
KVM_MMU_UNLOCK(kvm);
652668
if (!IS_KVM_NULL_FN(range->on_unlock))
653669
range->on_unlock(kvm);
654670
}
655671

656672
srcu_read_unlock(&kvm->srcu, idx);
657673

658-
/* The notifiers are averse to booleans. :-( */
659-
return (int)ret;
674+
return r;
660675
}
661676

662677
static __always_inline int kvm_handle_hva_range(struct mmu_notifier *mn,
@@ -677,7 +692,7 @@ static __always_inline int kvm_handle_hva_range(struct mmu_notifier *mn,
677692
.may_block = false,
678693
};
679694

680-
return __kvm_handle_hva_range(kvm, &range);
695+
return __kvm_handle_hva_range(kvm, &range).ret;
681696
}
682697

683698
static __always_inline int kvm_handle_hva_range_no_flush(struct mmu_notifier *mn,
@@ -696,7 +711,7 @@ static __always_inline int kvm_handle_hva_range_no_flush(struct mmu_notifier *mn
696711
.may_block = false,
697712
};
698713

699-
return __kvm_handle_hva_range(kvm, &range);
714+
return __kvm_handle_hva_range(kvm, &range).ret;
700715
}
701716

702717
static bool kvm_change_spte_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
@@ -798,7 +813,7 @@ static int kvm_mmu_notifier_invalidate_range_start(struct mmu_notifier *mn,
798813
.end = range->end,
799814
.handler = kvm_mmu_unmap_gfn_range,
800815
.on_lock = kvm_mmu_invalidate_begin,
801-
.on_unlock = kvm_arch_guest_memory_reclaimed,
816+
.on_unlock = (void *)kvm_null_fn,
802817
.flush_on_ret = true,
803818
.may_block = mmu_notifier_range_blockable(range),
804819
};
@@ -830,7 +845,13 @@ static int kvm_mmu_notifier_invalidate_range_start(struct mmu_notifier *mn,
830845
gfn_to_pfn_cache_invalidate_start(kvm, range->start, range->end,
831846
hva_range.may_block);
832847

833-
__kvm_handle_hva_range(kvm, &hva_range);
848+
/*
849+
* If one or more memslots were found and thus zapped, notify arch code
850+
* that guest memory has been reclaimed. This needs to be done *after*
851+
* dropping mmu_lock, as x86's reclaim path is slooooow.
852+
*/
853+
if (__kvm_handle_hva_range(kvm, &hva_range).found_memslot)
854+
kvm_arch_guest_memory_reclaimed(kvm);
834855

835856
return 0;
836857
}

0 commit comments

Comments
 (0)