summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2026-10-16 09:51:00 +0200
committerPaolo Bonzini <pbonzini@redhat.com>2026-10-16 09:51:00 +0200
commit7930afcf146df456d9ba17cf11d8269955c694cf (patch)
tree4af3f47f87283b898fd0a3e6123787a8992a1927
parent7213f4e515354c398276689e82cbac011e678646 (diff)
parent31adfe155db60a58b61e5b3d472cf85336ff3daf (diff)
Merge tag 'kvm-x86-mmu-7.4' of https://github.com/kvm-x86/linux into HEAD
KVM x86 MMU changes for 7.4 - Grab KVM's SRCU "lock" in kvm_zap_gfn_range() to fix a recurring bug where callers fail to do so. - Fix a variety of issues in the pre-fault code that are mostly benign in the current code base, but will show up in big ways if/when the pre-faulting logic is used to implementing prefetch-on-fault for the TDP MMU. - Guard rmaps with mmu_lock for PREEMPT_RT=y kernels to fix a longstanding issue introducing by "lockless aging" where KVM would take its effective raw bit-spinlock with preemption enabled (because mmu_lock gets turned into a sleepable lock). - Prepare a memory_fault exit (paired with -EFAULT) when KVM hits an error PFN, e.g. so that userspace can resolve transient issues instead of killing the guest. Convert a variety of WARN()+EFAULT paths into KVM_BUG_ON()+EIO so that KVM can hard commit to filling memory_fault on -EFAULTs that occur in the page fault path. - Fix a NX hugepage un-accounting bug where KVM would always uncharge the shadow MMU, even when handling TDP MMU pages.
-rw-r--r--arch/x86/kvm/mmu/mmu.c124
-rw-r--r--arch/x86/kvm/mmu/paging_tmpl.h4
-rw-r--r--arch/x86/kvm/x86.c2
3 files changed, 97 insertions, 33 deletions
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 205dfe00111b..5a2cc6a17a9b 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -864,11 +864,12 @@ void untrack_possible_nx_huge_page(struct kvm *kvm, struct kvm_mmu_page *sp,
list_del_init(&sp->possible_nx_huge_page_link);
}
-static void unaccount_nx_huge_page(struct kvm *kvm, struct kvm_mmu_page *sp)
+static void unaccount_nx_huge_page(struct kvm *kvm, struct kvm_mmu_page *sp,
+ enum kvm_mmu_type mmu_type)
{
sp->nx_huge_page_disallowed = false;
- untrack_possible_nx_huge_page(kvm, sp, KVM_SHADOW_MMU);
+ untrack_possible_nx_huge_page(kvm, sp, mmu_type);
}
static struct kvm_memory_slot *gfn_to_memslot_dirty_bitmap(struct kvm_vcpu *vcpu,
@@ -895,6 +896,7 @@ static struct kvm_memory_slot *gfn_to_memslot_dirty_bitmap(struct kvm_vcpu *vcpu
*/
#define KVM_RMAP_MANY BIT(0)
+#ifndef CONFIG_PREEMPT_RT
/*
* rmaps and PTE lists are mostly protected by mmu_lock (the shadow MMU always
* operates with mmu_lock held for write), but rmaps can be walked without
@@ -1008,7 +1010,8 @@ static unsigned long kvm_rmap_get(struct kvm_rmap_head *rmap_head)
* actual locking is the same, but the caller is disallowed from modifying the
* rmap, and so the unlock flow is a nop if the rmap is/was empty.
*/
-static unsigned long kvm_rmap_lock_readonly(struct kvm_rmap_head *rmap_head)
+static unsigned long kvm_rmap_lock_readonly(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head)
{
unsigned long rmap_val;
@@ -1032,6 +1035,35 @@ static void kvm_rmap_unlock_readonly(struct kvm_rmap_head *rmap_head,
__kvm_rmap_unlock(rmap_head, old_val);
preempt_enable();
}
+#else
+static unsigned long kvm_rmap_get(struct kvm_rmap_head *rmap_head)
+{
+ return atomic_long_read(&rmap_head->val);
+}
+static unsigned long kvm_rmap_lock(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head)
+{
+ lockdep_assert_held_write(&kvm->mmu_lock);
+ return kvm_rmap_get(rmap_head);
+}
+
+static void kvm_rmap_unlock(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head,
+ unsigned long new_val)
+{
+ atomic_long_set_release(&rmap_head->val, new_val);
+}
+
+static unsigned long kvm_rmap_lock_readonly(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head)
+{
+ lockdep_assert_held_read(&kvm->mmu_lock);
+ return kvm_rmap_get(rmap_head);
+}
+
+static void kvm_rmap_unlock_readonly(struct kvm_rmap_head *rmap_head,
+ unsigned long old_val) { }
+#endif
/*
* Returns the number of pointers in the rmap chain, not counting the new one.
@@ -1745,11 +1777,24 @@ static bool kvm_rmap_age_gfn_range(struct kvm *kvm,
gfn_t gfn;
int level;
+ BUILD_BUG_ON(!IS_ENABLED(CONFIG_KVM_MMU_LOCKLESS_AGING));
+
+ /*
+ * For realtime kernels, do aging under mmu_lock (per-rmap locking is
+ * compiled out), as realtime deployments are unlikely to benefit from
+ * increased aging throughput and reduced jitter for memory-overcommitted
+ * nested VMs, whereas keeping preemption enabled is extremely valuable
+ * (mmu_lock becomes a sleepable lock on realtime kernels).
+ */
+#ifdef CONFIG_PREEMPT_RT
+ guard(read_lock)(&kvm->mmu_lock);
+#endif
+
for (level = PG_LEVEL_4K; level <= KVM_MAX_HUGEPAGE_LEVEL; level++) {
for (gfn = range->start; gfn < range->end;
gfn += KVM_PAGES_PER_HPAGE(level)) {
rmap_head = gfn_to_rmap(gfn, level, range->slot);
- rmap_val = kvm_rmap_lock_readonly(rmap_head);
+ rmap_val = kvm_rmap_lock_readonly(kvm, rmap_head);
for_each_rmap_spte_lockless(rmap_val, &iter, sptep, old_spte) {
if (!is_accessed_spte(old_spte))
@@ -2791,7 +2836,7 @@ static bool __kvm_mmu_prepare_zap_page(struct kvm *kvm,
}
if (sp->nx_huge_page_disallowed)
- unaccount_nx_huge_page(kvm, sp);
+ unaccount_nx_huge_page(kvm, sp, KVM_SHADOW_MMU);
sp->role.invalid = 1;
@@ -3027,6 +3072,9 @@ int mmu_try_to_unsync_pages(struct kvm *kvm, const struct kvm_memory_slot *slot,
if (prefetch)
return -EEXIST;
+ if (KVM_BUG_ON(sp->role.level != PG_LEVEL_4K, kvm))
+ continue;
+
/*
* TDP MMU page faults require an additional spinlock as they
* run with mmu_lock held for read, not write, and the unsync
@@ -3050,7 +3098,6 @@ int mmu_try_to_unsync_pages(struct kvm *kvm, const struct kvm_memory_slot *slot,
continue;
}
- WARN_ON_ONCE(sp->role.level != PG_LEVEL_4K);
kvm_unsync_page(kvm, sp);
}
if (locked)
@@ -3537,8 +3584,8 @@ static int direct_map(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
fault->req_level >= it.level);
}
- if (WARN_ON_ONCE(it.level != fault->goal_level))
- return -EFAULT;
+ if (KVM_BUG_ON(it.level != fault->goal_level, vcpu->kvm))
+ return -EIO;
ret = mmu_set_spte(vcpu, fault->slot, it.sptep, access,
base_gfn, fault->pfn, fault);
@@ -3576,6 +3623,7 @@ static int kvm_handle_error_pfn(struct kvm_vcpu *vcpu, struct kvm_page_fault *fa
return RET_PF_RETRY;
}
+ kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
return -EFAULT;
}
@@ -4901,8 +4949,8 @@ int kvm_handle_page_fault(struct kvm_vcpu *vcpu, u64 error_code,
#ifndef CONFIG_X86_64
/* A 64-bit CR2 should be impossible on 32-bit KVM. */
- if (WARN_ON_ONCE(fault_address >> 32))
- return -EFAULT;
+ if (KVM_BUG_ON(fault_address >> 32, vcpu->kvm))
+ return -EIO;
#endif
/*
* Legacy #PF exception only have a 32-bit error code. Simply drop the
@@ -5067,6 +5115,10 @@ static int kvm_tdp_page_prefault(struct kvm_vcpu *vcpu, gpa_t gpa,
if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu))
return -EIO;
+ r = kvm_mmu_reload(vcpu);
+ if (r)
+ return r;
+
cond_resched();
r = kvm_mmu_do_page_fault(vcpu, gpa, error_code, true, NULL, level);
} while (r == RET_PF_RETRY);
@@ -5112,14 +5164,6 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
if (kvm_is_gfn_alias(vcpu->kvm, gpa_to_gfn(range->gpa)))
return -EINVAL;
- /*
- * reload is efficient when called repeatedly, so we can do it on
- * every iteration.
- */
- r = kvm_mmu_reload(vcpu);
- if (r)
- return r;
-
direct_bits = 0;
if (kvm_arch_has_private_mem(vcpu->kvm) &&
kvm_is_private_gfn(vcpu->kvm, gpa_to_gfn(range->gpa)))
@@ -5224,14 +5268,6 @@ int kvm_tdp_mmu_map_private_pfn(struct kvm_vcpu *vcpu, gfn_t gfn, kvm_pfn_t pfn)
if (kvm_gfn_is_write_tracked(kvm, fault.slot, fault.gfn))
return -EPERM;
- r = kvm_mmu_reload(vcpu);
- if (r)
- return r;
-
- r = mmu_topup_memory_caches(vcpu, false);
- if (r)
- return r;
-
do {
if (signal_pending(current))
return -EINTR;
@@ -5239,10 +5275,38 @@ int kvm_tdp_mmu_map_private_pfn(struct kvm_vcpu *vcpu, gfn_t gfn, kvm_pfn_t pfn)
if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu))
return -EIO;
+ r = kvm_mmu_reload(vcpu);
+ if (r)
+ return r;
+
+ r = mmu_topup_memory_caches(vcpu, false);
+ if (r)
+ return r;
+
cond_resched();
guard(read_lock)(&kvm->mmu_lock);
+ /*
+ * Because slots_lock is held, it should be impossible for *any*
+ * roots to be invalidated after the initial MMU reload. WARN,
+ * but continue on; the above MMU reload will do the right thing
+ * if the current root is actually invalid.
+ */
+ WARN_ON_ONCE(kvm_test_request(KVM_REQ_MMU_FREE_OBSOLETE_ROOTS, vcpu));
+
+ /*
+ * Snapshot the invalidation sequence counter after acquiring
+ * mmu_lock, as guest_memfd guarantees the validity of the pfn,
+ * i.e. any concurrent invalidations are guaranteed to be
+ * irrelevant.
+ */
+ fault.mmu_seq = vcpu->kvm->mmu_invalidate_seq;
+ if (is_page_fault_stale(vcpu, &fault)) {
+ r = RET_PF_RETRY;
+ continue;
+ }
+
r = kvm_tdp_mmu_map(vcpu, &fault);
} while (r == RET_PF_RETRY);
@@ -6606,8 +6670,8 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
r = RET_PF_INVALID;
if (unlikely(error_code & PFERR_RSVD_MASK)) {
- if (WARN_ON_ONCE(error_code & PFERR_PRIVATE_ACCESS))
- return -EFAULT;
+ if (KVM_BUG_ON(error_code & PFERR_PRIVATE_ACCESS, vcpu->kvm))
+ return -EIO;
r = handle_mmio_page_fault(vcpu, cr2_or_gpa, direct);
if (r == RET_PF_EMULATE)
@@ -7137,6 +7201,8 @@ void kvm_zap_gfn_range(struct kvm *kvm, gfn_t gfn_start, gfn_t gfn_end)
if (WARN_ON_ONCE(gfn_end <= gfn_start))
return;
+ guard(srcu)(&kvm->srcu);
+
write_lock(&kvm->mmu_lock);
kvm_mmu_invalidate_start(kvm);
@@ -8005,7 +8071,7 @@ static void kvm_recover_nx_huge_pages(struct kvm *kvm,
WARN_ON_ONCE(!sp->nx_huge_page_disallowed);
WARN_ON_ONCE(!sp->role.direct);
- unaccount_nx_huge_page(kvm, sp);
+ unaccount_nx_huge_page(kvm, sp, mmu_type);
if (is_tdp_mmu)
spin_unlock(tdp_mmu_pages_lock);
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index c8ec47b09264..8e350095508c 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -778,8 +778,8 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault,
fault->req_level >= it.level);
}
- if (WARN_ON_ONCE(it.level != fault->goal_level))
- return -EFAULT;
+ if (KVM_BUG_ON(it.level != fault->goal_level, vcpu->kvm))
+ return -EIO;
ret = mmu_set_spte(vcpu, fault->slot, it.sptep, gw->pte_access,
base_gfn, fault->pfn, fault);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 269d9886248e..50efebf156d5 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7938,10 +7938,8 @@ void __kvm_set_or_clear_apicv_inhibit(struct kvm *kvm,
kvm->arch.apicv_inhibit_reasons = new;
if (new) {
unsigned long gfn = gpa_to_gfn(APIC_DEFAULT_PHYS_BASE);
- int idx = srcu_read_lock(&kvm->srcu);
kvm_zap_gfn_range(kvm, gfn, gfn+1);
- srcu_read_unlock(&kvm->srcu, idx);
}
} else {
kvm->arch.apicv_inhibit_reasons = new;