[PATCH 0/4] KVM: arm64: Don't perform vgic-v2 lazy init on timer injection
Marc Zyngier
maz at kernel.org
Fri Apr 17 05:46:08 PDT 2026
Syzkaller reported an interesting case [1] showing vgic-v2 being
initialised via the lazy init path on injection from the timer reset
path. Yes, that's convoluted. This resulted in a splat as we could
end-up scheduling in an atomic context.
Deepanshu proposed [2] a simple fix that unconditionally init'd the
GIC on vcpu reset. While this would do the trick, this is only
papering over the real issue.
The situation is that we currently have three ways to lazily init the
vgic:
- on first run of any vcpu
- on access from userspace injecting an interrupt
- on access from the kernel injecting an interrupt
The splat is caused by this last one, and it is interesting to drill
into why we end-up with it.
All guest interrupts generated by the kernel itself are level. Which
means that they cannot be lost unless the generating device is being
interacted with. So there shouldn't be any need to initialise the vgic
for that reason, and we could defer it to the first run of a vcpu.
However, the timers are extra special. Each one has its own little
single bit cache that contains the last level set. And as long as the
level doesn't change, the timer code doesn't call into the interrupt
injection code, making it totally optimal.
A side effect of this optimisation is that the level interrupt
effectively becomes an edge (only the changes are reported). Which
means that the interrupt must be recorded in the vgic, or it be
forever lost. Hence the need to eagerly initialise the GIC at
injection time.
But frankly, there isn't much to gain by having this cache. All we
avoid is a lookup, an uncontended lock, and an early return. The other
interrupts generated by the kernel (PMU, vgic MI) don't have such
cache, and nobody has complained yet.
So let's drop this cache, and remove the vgic init from the kernel
injection. If someone shouts about a loss of performance, then let's
improve the interrupt injection itself, and not paper over it. Also
use this opportunity to repaint kvm_timer_should_fire() as
kvm_timer_pending(), something that is way less ambiguous.
Patches on top of kvmarm-7.1. The reproducer didn't trigger on my
boxes, and syzkaller is down at the moment. But nothing bad happened
in my testing...
[1] https://syzkaller.appspot.com/bug?extid=12b178b7c756664d2518
[2] https://lore.kernel.org/r/20260412080437.38782-1-kartikey406@gmail.com
Marc Zyngier (4):
KVM: arm64: timer: Repaint kvm_timer_should_fire() to
kvm_timer_pending()
KVM: arm64: timer: Kill the per-timer level cache
KVM: arm64: vgic-v2: Force vgic init on injection from userspace
KVM: arm64: vgic-v2: Don't init the vgic on in-kernel interrupt
injection
arch/arm64/kvm/arch_timer.c | 44 ++++++++++++++++++------------------
arch/arm64/kvm/arm.c | 7 ++++++
arch/arm64/kvm/vgic/vgic.c | 6 ++---
include/kvm/arm_arch_timer.h | 5 ----
4 files changed, 31 insertions(+), 31 deletions(-)
--
2.47.3
More information about the linux-arm-kernel
mailing list