* KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT
@ 2026-08-27 21:13 David Woodhouse
2026-08-27 21:47 ` Sean Christopherson
0 siblings, 1 reply; 7+ messages in thread
From: David Woodhouse @ 2026-08-27 21:13 UTC (permalink / raw)
To: Sean Christopherson, James Houghton
Cc: Paolo Bonzini, Sebastian Andrzej Siewior, kvm, linux-rt-devel,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1519 bytes --]
While soaking unrelated KVM changes on a PREEMPT_RT + lockdep kernel
I hit this, which I don't believe has been reported before:
WARNING: arch/x86/kvm/mmu/mmu.c:920 at __kvm_rmap_lock+0x1a7/0x1e0 [kvm], CPU#16: vmx_apic_update/3708
CPU: 16 UID: 0 PID: 3708 Comm: vmx_apic_update Not tainted 7.2.0-rc7 #52 PREEMPT_{RT,LAZY}
RIP: 0010:__kvm_rmap_lock+0x1a7/0x1e0 [kvm]
Call Trace:
pte_list_add+0x67/0x4d0 [kvm]
__link_shadow_page+0x249/0x480 [kvm]
ept_fetch+0x4d5/0x1220 [kvm]
ept_page_fault+0x60b/0x850 [kvm]
kvm_mmu_do_page_fault+0x252/0x690 [kvm]
That's the lockdep_assert_preemption_disabled() in __kvm_rmap_lock(),
from commit 4834eaded91e ("KVM: x86/mmu: Add infrastructure to allow
walking rmaps outside of mmu_lock").
I don't think this one is just lockdep vs. PREEMPT_RT causing false
positives — the rmap lock is a hand-crafted bit-spinlock, and if a lock
holder is preempted that leaves every other walker of that rmap
spinning and waiting for it (with no tracked owner for PI to boost).
On PREEMPT_RT we genuinely get here without preemption disabled,
because kvm->mmu_lock is a sleeping lock now. Any shadow-MMU fault
on an RT kernel should trip it — this one is the !TDP nested EPT path
(ept_fetch()), and it fires within seconds of running a nested guest
with lockdep enabled.
I guess we fix it by turning the assertion into a preempt_disable() of
its own? Not sufficiently confident in that conclusion to send it in
'diff -up' form though...
[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 6179 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT 2026-08-27 21:13 KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT David Woodhouse @ 2026-08-27 21:47 ` Sean Christopherson 2026-08-27 21:58 ` David Woodhouse 2026-08-27 22:17 ` Sean Christopherson 0 siblings, 2 replies; 7+ messages in thread From: Sean Christopherson @ 2026-08-27 21:47 UTC (permalink / raw) To: David Woodhouse Cc: James Houghton, Paolo Bonzini, Sebastian Andrzej Siewior, kvm, linux-rt-devel, linux-kernel On Thu, Aug 27, 2026, David Woodhouse wrote: > While soaking unrelated KVM changes on a PREEMPT_RT + lockdep kernel > I hit this, which I don't believe has been reported before: Heh, not on-list. I've hit it the few times I've run PREEMPT_RT, but I (obviously) haven't cared enough to fix it, especially given the lack of external reports. Which makes sense; I know people run KVM with PREEMPT_RT, but running nested, memory-overcommitted VMs with PREEMPT_RT seems beyond crazy. > WARNING: arch/x86/kvm/mmu/mmu.c:920 at __kvm_rmap_lock+0x1a7/0x1e0 [kvm], CPU#16: vmx_apic_update/3708 > CPU: 16 UID: 0 PID: 3708 Comm: vmx_apic_update Not tainted 7.2.0-rc7 #52 PREEMPT_{RT,LAZY} > RIP: 0010:__kvm_rmap_lock+0x1a7/0x1e0 [kvm] > Call Trace: > pte_list_add+0x67/0x4d0 [kvm] > __link_shadow_page+0x249/0x480 [kvm] > ept_fetch+0x4d5/0x1220 [kvm] > ept_page_fault+0x60b/0x850 [kvm] > kvm_mmu_do_page_fault+0x252/0x690 [kvm] > > That's the lockdep_assert_preemption_disabled() in __kvm_rmap_lock(), > from commit 4834eaded91e ("KVM: x86/mmu: Add infrastructure to allow > walking rmaps outside of mmu_lock"). > > I don't think this one is just lockdep vs. PREEMPT_RT causing false > positives — the rmap lock is a hand-crafted bit-spinlock, and if a lock > holder is preempted that leaves every other walker of that rmap > spinning and waiting for it (with no tracked owner for PI to boost). > > On PREEMPT_RT we genuinely get here without preemption disabled, > because kvm->mmu_lock is a sleeping lock now. Any shadow-MMU fault > on an RT kernel should trip it — this one is the !TDP nested EPT path > (ept_fetch()), and it fires within seconds of running a nested guest > with lockdep enabled. Yep. > I guess we fix it by turning the assertion into a preempt_disable() of > its own? Not sufficiently confident in that conclusion to send it in > 'diff -up' form though... Or in the spirit of PREEMPT_RT, make the aging code acquire mmu_lock? As above, I have a hard time believing anyone cares about aging throughput of nested VMs when running PREEMPT_RT, certainly not enough to want to disable preemption for any amount of time. Not the prettiest code, but it does seem like the right thing to do for PREEMPT_RT. Compile-tested only at this point. I'll take it for a spin, unless someone has a better idea. diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 81c30e2c74f3..31a372d0697c 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -913,11 +913,15 @@ static struct kvm_memory_slot *gfn_to_memslot_dirty_bitmap(struct kvm_vcpu *vcpu */ #define KVM_RMAP_LOCKED BIT(1) -static unsigned long __kvm_rmap_lock(struct kvm_rmap_head *rmap_head) +static unsigned long __kvm_rmap_lock(struct kvm *kvm, + struct kvm_rmap_head *rmap_head) { unsigned long old_val, new_val; - lockdep_assert_preemption_disabled(); + if (IS_ENABLED(CONFIG_PREEMPT_RT)) + lockdep_assert_preemption_disabled(); + else + lockdep_assert_held(&kvm->mmu_lock); /* * Elide the lock if the rmap is empty, as lockless walkers (read-only @@ -973,7 +977,7 @@ static unsigned long kvm_rmap_lock(struct kvm *kvm, { lockdep_assert_held_write(&kvm->mmu_lock); - return __kvm_rmap_lock(rmap_head); + return __kvm_rmap_lock(kvm, rmap_head); } static void __kvm_rmap_unlock(struct kvm_rmap_head *rmap_head, @@ -1008,14 +1012,17 @@ 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; - preempt_disable(); - rmap_val = __kvm_rmap_lock(rmap_head); + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) + preempt_disable(); - if (!rmap_val) + rmap_val = __kvm_rmap_lock(kvm, rmap_head); + + if (!IS_ENABLED(CONFIG_PREEMPT_RT) && !rmap_val) preempt_enable(); return rmap_val; @@ -1030,7 +1037,9 @@ static void kvm_rmap_unlock_readonly(struct kvm_rmap_head *rmap_head, KVM_MMU_WARN_ON(old_val != kvm_rmap_get(rmap_head)); __kvm_rmap_unlock(rmap_head, old_val); - preempt_enable(); + + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) + preempt_enable(); } /* @@ -1745,11 +1754,15 @@ static bool kvm_rmap_age_gfn_range(struct kvm *kvm, gfn_t gfn; int level; +#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)) ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT 2026-08-27 21:47 ` Sean Christopherson @ 2026-08-27 21:58 ` David Woodhouse 2026-08-27 22:17 ` Sean Christopherson 1 sibling, 0 replies; 7+ messages in thread From: David Woodhouse @ 2026-08-27 21:58 UTC (permalink / raw) To: Sean Christopherson Cc: James Houghton, Paolo Bonzini, Sebastian Andrzej Siewior, kvm, linux-rt-devel, linux-kernel [-- Attachment #1: Type: text/plain, Size: 421 bytes --] On Thu, 2026-08-27 at 14:47 -0700, Sean Christopherson wrote: > I'll take it for a spin, unless someone has a better idea. Heh, no pun intended? > - lockdep_assert_preemption_disabled(); > + if (IS_ENABLED(CONFIG_PREEMPT_RT)) > + lockdep_assert_preemption_disabled(); > + else > + lockdep_assert_held(&kvm->mmu_lock); Other way round? I'll fix that and throw it on top of my GPC/RT/RCU torture tests. [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT 2026-08-27 21:47 ` Sean Christopherson 2026-08-27 21:58 ` David Woodhouse @ 2026-08-27 22:17 ` Sean Christopherson 2026-08-28 10:45 ` Sebastian Andrzej Siewior 1 sibling, 1 reply; 7+ messages in thread From: Sean Christopherson @ 2026-08-27 22:17 UTC (permalink / raw) To: David Woodhouse Cc: James Houghton, Paolo Bonzini, Sebastian Andrzej Siewior, kvm, linux-rt-devel, linux-kernel On Thu, Aug 27, 2026, Sean Christopherson wrote: > On Thu, Aug 27, 2026, David Woodhouse wrote: > diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c > index 81c30e2c74f3..31a372d0697c 100644 > --- a/arch/x86/kvm/mmu/mmu.c > +++ b/arch/x86/kvm/mmu/mmu.c > @@ -913,11 +913,15 @@ static struct kvm_memory_slot *gfn_to_memslot_dirty_bitmap(struct kvm_vcpu *vcpu > */ > #define KVM_RMAP_LOCKED BIT(1) > > -static unsigned long __kvm_rmap_lock(struct kvm_rmap_head *rmap_head) > +static unsigned long __kvm_rmap_lock(struct kvm *kvm, > + struct kvm_rmap_head *rmap_head) > { > unsigned long old_val, new_val; > > - lockdep_assert_preemption_disabled(); > + if (IS_ENABLED(CONFIG_PREEMPT_RT)) > + lockdep_assert_preemption_disabled(); > + else > + lockdep_assert_held(&kvm->mmu_lock); ... > @@ -1745,11 +1754,15 @@ static bool kvm_rmap_age_gfn_range(struct kvm *kvm, > gfn_t gfn; > int level; > > +#ifdef CONFIG_PREEMPT_RT > + guard(read_lock)(&kvm->mmu_lock); > +#endif And now I remember why I swept this under the rug. This really should take mmu_lock for write, otherwise concurrent aging threads could theoretically get stuck competing for KVM_RMAP_LOCKED. Which is silly, because they don't actually need to take a lock of any kind. I.e. it's not super trivial? Wait, duh. It is trivial if mmu_lock is held, because then KVM can operate on rmaps without any KVM_RMAP_LOCKED shenanigans. I wouldn't test this because it might break horribly, but I think this? diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 81c30e2c74f3..2b157b390a40 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -895,6 +895,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 +1009,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 +1034,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 +1776,15 @@ static bool kvm_rmap_age_gfn_range(struct kvm *kvm, gfn_t gfn; int level; +#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)) ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT 2026-08-27 22:17 ` Sean Christopherson @ 2026-08-28 10:45 ` Sebastian Andrzej Siewior 2026-08-28 11:16 ` David Woodhouse 0 siblings, 1 reply; 7+ messages in thread From: Sebastian Andrzej Siewior @ 2026-08-28 10:45 UTC (permalink / raw) To: Sean Christopherson Cc: David Woodhouse, James Houghton, Paolo Bonzini, kvm, linux-rt-devel, linux-kernel On 2026-08-27 15:17:57 [-0700], Sean Christopherson wrote: > > @@ -1745,11 +1754,15 @@ static bool kvm_rmap_age_gfn_range(struct kvm *kvm, > > gfn_t gfn; > > int level; > > > > +#ifdef CONFIG_PREEMPT_RT > > + guard(read_lock)(&kvm->mmu_lock); > > +#endif > > And now I remember why I swept this under the rug. This really should take > mmu_lock for write, otherwise concurrent aging threads could theoretically get > stuck competing for KVM_RMAP_LOCKED. Which is silly, because they don't actually > need to take a lock of any kind. I.e. it's not super trivial? > > Wait, duh. It is trivial if mmu_lock is held, because then KVM can operate on > rmaps without any KVM_RMAP_LOCKED shenanigans. I wouldn't test this because it > might break horribly, but I think this? So you drop the bit spinlock and rely on kvm->mmu_lock instead? No sure why it should break horribly but it looks reasonable. Sebastian ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT 2026-08-28 10:45 ` Sebastian Andrzej Siewior @ 2026-08-28 11:16 ` David Woodhouse 2026-08-28 14:23 ` Sean Christopherson 0 siblings, 1 reply; 7+ messages in thread From: David Woodhouse @ 2026-08-28 11:16 UTC (permalink / raw) To: Sebastian Andrzej Siewior, Sean Christopherson Cc: James Houghton, Paolo Bonzini, kvm, linux-rt-devel, linux-kernel [-- Attachment #1: Type: text/plain, Size: 1112 bytes --] On Fri, 2026-08-28 at 12:45 +0200, Sebastian Andrzej Siewior wrote: > On 2026-08-27 15:17:57 [-0700], Sean Christopherson wrote: > > > @@ -1745,11 +1754,15 @@ static bool kvm_rmap_age_gfn_range(struct kvm *kvm, > > > gfn_t gfn; > > > int level; > > > > > > +#ifdef CONFIG_PREEMPT_RT > > > + guard(read_lock)(&kvm->mmu_lock); > > > +#endif > > > > And now I remember why I swept this under the rug. This really should take > > mmu_lock for write, otherwise concurrent aging threads could theoretically get > > stuck competing for KVM_RMAP_LOCKED. Which is silly, because they don't actually > > need to take a lock of any kind. I.e. it's not super trivial? > > > > Wait, duh. It is trivial if mmu_lock is held, because then KVM can operate on > > rmaps without any KVM_RMAP_LOCKED shenanigans. I wouldn't test this because it > > might break horribly, but I think this? > > So you drop the bit spinlock and rely on kvm->mmu_lock instead? No sure > why it should break horribly but it looks reasonable. FWIW it survived the night in my GPC invalidation torture tests. [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT 2026-08-28 11:16 ` David Woodhouse @ 2026-08-28 14:23 ` Sean Christopherson 0 siblings, 0 replies; 7+ messages in thread From: Sean Christopherson @ 2026-08-28 14:23 UTC (permalink / raw) To: David Woodhouse Cc: Sebastian Andrzej Siewior, James Houghton, Paolo Bonzini, kvm, linux-rt-devel, linux-kernel On Fri, Aug 28, 2026, David Woodhouse wrote: > On Fri, 2026-08-28 at 12:45 +0200, Sebastian Andrzej Siewior wrote: > > On 2026-08-27 15:17:57 [-0700], Sean Christopherson wrote: > > > > @@ -1745,11 +1754,15 @@ static bool kvm_rmap_age_gfn_range(struct kvm *kvm, > > > > gfn_t gfn; > > > > int level; > > > > > > > > +#ifdef CONFIG_PREEMPT_RT > > > > + guard(read_lock)(&kvm->mmu_lock); > > > > +#endif > > > > > > And now I remember why I swept this under the rug. This really should take > > > mmu_lock for write, otherwise concurrent aging threads could theoretically get > > > stuck competing for KVM_RMAP_LOCKED. Which is silly, because they don't actually > > > need to take a lock of any kind. I.e. it's not super trivial? > > > > > > Wait, duh. It is trivial if mmu_lock is held, because then KVM can operate on > > > rmaps without any KVM_RMAP_LOCKED shenanigans. I wouldn't test this because it > > > might break horribly, but I think this? > > > > So you drop the bit spinlock and rely on kvm->mmu_lock instead? Ya, it's basically a revert of the per-rmap locking, but just for PREEMPT_RT. > > No sure why it should break horribly but it looks reasonable. I wasn't worried about the locking, I was worried I was forgetting an rmap detail, e.g. I almost forgot to update rmap_head->val in the "unlock" path. > FWIW it survived the night in my GPC invalidation torture tests. Awesome! I'll throw a PREEMPT_RT kernel through the normal testing and hopefully get a patch posted today. ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-28 14:23 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-27 21:13 KVM: x86/mmu: __kvm_rmap_lock() preemption assert trips on PREEMPT_RT David Woodhouse 2026-08-27 21:47 ` Sean Christopherson 2026-08-27 21:58 ` David Woodhouse 2026-08-27 22:17 ` Sean Christopherson 2026-08-28 10:45 ` Sebastian Andrzej Siewior 2026-08-28 11:16 ` David Woodhouse 2026-08-28 14:23 ` Sean Christopherson
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox