KVM: arm64: vgic-v3: Make LPI disabling robust (and more)
Subsystems (5)
Marc Zyngier
maintainerv2 cover KVM: arm64: vgic-v3: Make LPI disabling robust (and more)
This is v2 of this series addressing shortcomings of LPIs being disabled on one CPU from another. It has now expanded into some more common areas.
Yuchao Zhang reported that disabling LPIs on one CPU from another could result in UAFs and other horrors.
There are two reasons for this:
- the last_lr_irq pointer does not contribute to LPI refcount, and that LPI being removed results in a dangling pointer
- LPIs can be deleted from a remote vcpu by disabling them while that vcpu is actually running, and has LPIs in its LRs.
Address the two issues in one go, by actively taking a refcount on all IRQs referenced by last_lr_irq, and making sure that disabling LPIs force all vcpus to be paused, making it safe.
Review of the initial version pointed out two more general issues:
- OUTSIDE_GUEST_MODE is published too early, when the guest state is not yet visible to other threads, resulting in the wrong state being evaluated from another CPU.
- kvm_{halt,resume}_guest() can be called without holding a global lock, and therefore can nest. This can result in a vcpu being restarted too early.
This is addressed by the first two patches.
Finally, MOVALL suffers from similar issues as LPI disabling, but also appears to be broken (the filtering on the source RD was accidentally removed a while ago). Fix the filtering and move MOVALL to a "stop the world" approach.
* From v1 [1]:
- Fix OUTSIDE_GUEST_MODE publication to occur after the saving of the guest state
- Turn vcpu->arch.pause into an atomic counter, allowing nesting
- Fix MOVALL to filter by source RD
- Make MOVALL a "stop the world" command
[1] https://lore.kernel.org/r/[email protected]
Marc Zyngier (7):
KVM: arm64: Move OUTSIDE_GUEST_MODE publication past context being
saved
KVM: arm64: Turn vcpu->arch.pause into a counter
KVM: arm64: vgic: Allow last_lr_irq to be NULL when LRs are not
overflowing
KVM: arm64: vgic: Take a refcount on IRQs referenced by last_lr_irq
KVM: arm64: vgic: Stop the VM when disabling LPIs
KVM: arm64: vgic-its: Fix MOVALL handling of source redistributor
KVM: arm64: vgic-its: Stop the VM when handling MOVALL
arch/arm64/include/asm/kvm_host.h | 7 +++---- arch/arm64/kvm/arm.c | 28 ++++++++++++++++++---------- arch/arm64/kvm/vgic/vgic-its.c | 23 +++++++++++++++++++---- arch/arm64/kvm/vgic/vgic-mmio-v3.c | 10 ++++++++++ arch/arm64/kvm/vgic/vgic-v2.c | 6 ++++-- arch/arm64/kvm/vgic/vgic-v3.c | 6 ++++-- arch/arm64/kvm/vgic/vgic.c | 21 ++++++++++++++++----- 7 files changed, 74 insertions(+), 27 deletions(-)
Signature · 1 line
2.47.3
Marc Zyngier
maintainerPATCH v2 1/7 KVM: arm64: Move OUTSIDE_GUEST_MODE publication past context being saved
OUTSIDE_GUEST_MODE indicates to the rest of KVM that the vcpu has exited. So far, this is set as soon as we return from the notional hypervisor (which with VHE is just a function call away).
However, code that uses OUTSIDE_GUEST_MODE as a synchronisation point relies on the vcpu state to have been written back to the in-memory structures. Clearly, this is not what is happening
Move the publication of OUTSIDE_GUEST_MODE to the point where the state is actually written, and give this write release semantics to ensure the correct ordering.
--- arch/arm64/kvm/arm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
arch/arm64/kvm/arm.c +6 -1
@@ -1352,7 +1352,6 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) ret =kvm_arm_vcpu_enter_exit (vcpu); - vcpu->mode =OUTSIDE_GUEST_MODE ; vcpu->stat.exits++;/* * Back from guest
@@ -1386,6 +1385,12 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)kvm_arch_vcpu_ctxsync_fp (vcpu); +/* +* All the state has been synchronised, let advertise +* we're outside of the guest. +*/ +smp_store_release (&vcpu->mode,OUTSIDE_GUEST_MODE ); +/* * We must ensure that any pending interrupts are taken before * we exit guest timing so that timer ticks are accounted as
Signature · 1 line
2.47.3
Fuad Tabba
reviewerRe: [PATCH v2 1/7] KVM: arm64: Move OUTSIDE_GUEST_MODE publication past context being saved
Hi Marc,
On Tue, 29 Sep 2026 10:35:42 +0100, Marc Zyngier <[email protected]> wrote:
[...]
Quoted 6 lines
Move the publication of
OUTSIDE_GUEST_MODEto the point where the state is actually written, and give this write release semantics to ensure the correct ordering.Signed-off-by Marc Zyngier <[email protected]>
No Fixes: tag?
[...]
Quoted 1 line
arch/arm64/kvm/arm.c +0
-0
[...]
arch/arm64/kvm/arm.c int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) · line 1392
Quoted 9 lines
arch/arm64/kvm/arm.c +5
-0@@ -1386,6 +1385,12 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)kvm_arch_vcpu_ctxsync_fp (vcpu); +/* +* All the state has been synchronised, let advertise +* we're outside of the guest. +*/ +smp_store_release (&vcpu->mode,OUTSIDE_GUEST_MODE );
Pardon my atomics :), but what does the release pair with? On the halt path, the only reader I can find is the cmpxchg() in kvm_vcpu_exiting_guest_mode(), and the LPI-disable and MOVALL halts then take ap_list_lock or irq_lock. Would WRITE_ONCE() be enough?
Should the early exit path (the kvm_vcpu_exit_request() bail-out) get the same treatment? I think that's what Sashiko is trying to say in the patch 5 review [1].
nit: "let advertise" -> "publish"?
Cheers,
/fuad
[1] https://sashiko.dev/#/patchset/20260929093548.3598547-1-maz%40kernel.org?part=5
Marc Zyngier
maintainerRe: [PATCH v2 1/7] KVM: arm64: Move OUTSIDE_GUEST_MODE publication past context being saved
Quoted 9 lines · Fuad Tabba
Hi Marc,
On Tue, 29 Sep 2026 10:35:42 +0100, Marc Zyngier <[email protected]> wrote:
[...]Quoted 4 lines
Move the publication of
OUTSIDE_GUEST_MODEto the point where the state is actually written, and give this write release semantics to ensure the correct ordering.
Quoted 5 lines
Quoted 2 lines
Signed-off-by Marc Zyngier <[email protected]>No Fixes: tag?
No. It's always been fsck'd.
Quoted 8 lines
[...]
Quoted 1 line
arch/arm64/kvm/arm.c +0
-0[...]
Quoted 4 lines
arch/arm64/kvm/arm.c +0
-0@@ -1386,6 +1385,12 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)kvm_arch_vcpu_ctxsync_fp (vcpu);
Quoted 7 lines
arch/arm64/kvm/arm.c int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) · line 1392
Quoted 5 lines
arch/arm64/kvm/arm.c +5
-0+/* +* All the state has been synchronised, let advertise +* we're outside of the guest. +*/ +smp_store_release (&vcpu->mode,OUTSIDE_GUEST_MODE );Pardon my atomics :)
This is not an atomic instruction. However, it composes with atomics.
Quoted 3 lines
, but what does the release pair with? On the halt path, the only reader I can find is the
cmpxchg()inkvm_vcpu_exiting_guest_mode()
From Documentation/atomic_t.txt:
<quote>
- RMW operations that have a return value are fully ordered;
- RMW operations that are conditional are unordered on FAILURE, otherwise the above rules apply.
</quote>
The acquire side of cmpxchg() is therefore interacting with the above release, which gives us the required ordering.
However, there is a problem if cmpxchg() fails, as there is no ordering in that case, and I'm not sure the smp_mb__before_atomic() saves the bacon in that case. It feels we'd need an acquire somewhere, a bit like this:
include/linux/kvm_host.h +7 -1
@@ -563,9 +563,15 @@ static inline int kvm_vcpu_exiting_guest_mode(struct kvm_vcpu *vcpu)* The memory barrier ensures a previous write to vcpu->requests cannot * be reordered with the read of vcpu->mode. It pairs with the general * memory barrier following the write of vcpu->mode in VCPU RUN. +* +* cmpxchg() is not ordered when failing, so make sure we perform an +* acquire in that case. */ smp_mb__before_atomic (); -return cmpxchg (&vcpu->mode,IN_GUEST_MODE ,EXITING_GUEST_MODE ); +if (cmpxchg (&vcpu->mode,IN_GUEST_MODE ,EXITING_GUEST_MODE ) !=IN_GUEST_MODE ) +return smp_load_acquire (&vcpu->mode); + +return IN_GUEST_MODE ; }/*
Quoted 2 lines
, and the LPI-disable and MOVALL halts
then takeap_list_lockorirq_lock. WouldWRITE_ONCE()be enough?
We need a release so that we know for sure that any state stored before is visible by the time we can observe OUTSIDE_GUEST_MODE, and WRITE_ONCE() doesn't provide that (it can be reordered).
I don't see what taking a lock changes to the ordering requirement.
Quoted 4 lines
Should the early exit path (the
kvm_vcpu_exit_request()bail-out) get the same treatment? I think that's what Sashiko is trying to say in the patch 5 review [1].
I don't understand what sashiko is trying to say, but this is clearly missing from the patch, see below. Not sure how I missed that one.
arch/arm64/kvm/arm.c +1 -1
@@ -1333,13 +1333,13 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)smp_store_mb (vcpu->mode,IN_GUEST_MODE );if (ret <=0 ||kvm_vcpu_exit_request (vcpu, &ret)) { - vcpu->mode =OUTSIDE_GUEST_MODE ;isb ();/* Ensure work in x_flush_hwstate is committed */ if (kvm_vcpu_has_pmu (vcpu))kvm_pmu_sync_hwstate (vcpu);if (unlikely (!irqchip_in_kernel (vcpu->kvm)))kvm_timer_sync_user (vcpu);kvm_vgic_sync_hwstate (vcpu); +smp_store_release (&vcpu->mode,OUTSIDE_GUEST_MODE );local_irq_enable ();preempt_enable ();continue ;
Thanks,
M.
Signature · 1 line
Without deviation from the norm, progress is not possible.
Fuad Tabba
reviewerRe: [PATCH v2 1/7] KVM: arm64: Move OUTSIDE_GUEST_MODE publication past context being saved
Hi Marc,
On Tue, 29 Sep 2026 15:13:28 +0100, Marc Zyngier <[email protected]> wrote:
[...]
Quoted 4 lines
However, there is a problem if
cmpxchg()fails, as there is no ordering in that case, and I'm not sure thesmp_mb__before_atomic()saves the bacon in that case. It feels we'd need an acquire somewhere, a bit like this:
It doesn't save the bacon. I ran it through herd7 with the LKMM, the fold walk in vgic_v3_fold_lr_state() against the list_del() in vgic_flush_pending_lpis(): with smp_mb__before_atomic() and a failing cmpxchg(), the walk can see the list_del(). With your smp_load_acquire() it can't.
[...]
Quoted 5 lines
We need a release so that we know for sure that any state stored before is visible by the time we can observe
OUTSIDE_GUEST_MODE, andWRITE_ONCE()doesn't provide that (it can be reordered).I don't see what taking a lock changes to the ordering requirement.
The fold walk takes no lock, so the locks on the halt side don't order it against the list_del(). herd7 agrees: WRITE_ONCE() with your acquire can see the list_del() again.
[...]
Quoted 2 lines
I don't understand what sashiko is trying to say, but this is clearly missing from the patch, see below. Not sure how I missed that one.
I don't understand Sashiko half of the time either :) That said, the diff looks right to me.
Cheers,
/fuad
Will Deacon
maintainerRe: [PATCH v2 1/7] KVM: arm64: Move OUTSIDE_GUEST_MODE publication past context being saved
In reply to Marc ZyngierHi folks,
Sorry, but this is probably an incredibly unhelpful drive-by comment but Marc was talking about vcpu->mode the other day and I couldn't resist looking at it some more. Like a moth to a flame...
See below.
Quoted 32 lines · Marc Zyngier
Quoted 7 lines · Fuad Tabba
Quoted 1 line · Marc Zyngier
arch/arm64/kvm/arm.c +0
-0[...]
Quoted 4 lines
arch/arm64/kvm/arm.c +0
-0@@ -1386,6 +1385,12 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)kvm_arch_vcpu_ctxsync_fp (vcpu);Quoted 7 lines
arch/arm64/kvm/arm.c int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) · line 1392
Quoted 5 lines
arch/arm64/kvm/arm.c +5
-0+/* +* All the state has been synchronised, let advertise +* we're outside of the guest. +*/ +smp_store_release (&vcpu->mode,OUTSIDE_GUEST_MODE );Pardon my atomics :)
This is not an atomic instruction. However, it composes with atomics.
Quoted 3 lines
, but what does the release pair with? On the halt path, the only reader I can find is the
cmpxchg()inkvm_vcpu_exiting_guest_mode()From
Documentation/atomic_t.txt:<quote>
- RMW operations that have a return value are fully ordered;- RMW operations that are conditional are unordered on FAILURE, otherwise the above rules apply.
</quote>
Quoted 7 lines
The acquire side of
cmpxchg()is therefore interacting with the above release, which gives us the required ordering.However, there is a problem if
cmpxchg()fails, as there is no ordering in that case, and I'm not sure thesmp_mb__before_atomic()saves the bacon in that case. It feels we'd need an acquire somewhere, a bit like this:
(as discussed off list, you can use smp_acquire__after_ctrl_dep() if you're feeling really brave)
Quoted 45 lines
include/linux/kvm_host.h +7
-1@@ -563,9 +563,15 @@ static inline int kvm_vcpu_exiting_guest_mode(struct kvm_vcpu *vcpu)* The memory barrier ensures a previous write to vcpu->requests cannot * be reordered with the read of vcpu->mode. It pairs with the general * memory barrier following the write of vcpu->mode in VCPU RUN. +* +* cmpxchg() is not ordered when failing, so make sure we perform an +* acquire in that case. */ smp_mb__before_atomic (); -return cmpxchg (&vcpu->mode,IN_GUEST_MODE ,EXITING_GUEST_MODE ); +if (cmpxchg (&vcpu->mode,IN_GUEST_MODE ,EXITING_GUEST_MODE ) !=IN_GUEST_MODE ) +return smp_load_acquire (&vcpu->mode); + +return IN_GUEST_MODE ; }/* Quoted 3 lines
, and the LPI-disable and MOVALL halts
then takeap_list_lockorirq_lock. WouldWRITE_ONCE()be enough?We need a release so that we know for sure that any state stored before is visible by the time we can observe
OUTSIDE_GUEST_MODE, andWRITE_ONCE()doesn't provide that (it can be reordered).I don't see what taking a lock changes to the ordering requirement.
Quoted 4 lines
Should the early exit path (the
kvm_vcpu_exit_request()bail-out) get the same treatment? I think that's what Sashiko is trying to say in the patch 5 review [1].I don't understand what sashiko is trying to say, but this is clearly missing from the patch, see below. Not sure how I missed that one.
arch/arm64/kvm/arm.c +0
-0@@ -1333,13 +1333,13 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)smp_store_mb (vcpu->mode,IN_GUEST_MODE );
arch/arm64/kvm/arm.c int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) · line 1342
Quoted 10 lines
arch/arm64/kvm/arm.c +1
-1if (ret <=0 ||kvm_vcpu_exit_request (vcpu, &ret)) { - vcpu->mode =OUTSIDE_GUEST_MODE ;isb ();/* Ensure work in x_flush_hwstate is committed */ if (kvm_vcpu_has_pmu (vcpu))kvm_pmu_sync_hwstate (vcpu);if (unlikely (!irqchip_in_kernel (vcpu->kvm)))kvm_timer_sync_user (vcpu);kvm_vgic_sync_hwstate (vcpu); +smp_store_release (&vcpu->mode,OUTSIDE_GUEST_MODE );
I'm struggling to see why a release is sufficient here, but I'm also struggling to understand the bigger picture so I'm probably just confused.
I can see why a release is necessary for the saved state to be visible to another CPU that has kicked the vCPU out of the guest and then uses vcpu->mode == OUTSIDE_GUEST_MODE as the indication that the state is safe to consume. However, don't we also need to make sure that any subsequent check for a pending request on _this_ vCPU is ordered after that write to the mode? Now that we've toggled it away from IN_GUEST_MODE, I think IPIs can be elided by the kick, so a subsequent call to e.g. kvm_request_pending() must be observed after that toggle, otherwise I think we could miss a request.
Can you see the tree I'm barking up here?
Will
Marc Zyngier
maintainerPATCH v2 2/7 KVM: arm64: Turn vcpu->arch.pause into a counter
In reply to Marc ZyngierWe have situations where we can call kvm_arm_halt_guest() without holding config_lock, which means that halt and resume can overlap in funny ways when called from separate contexts, and result in situations where a vcpu is resumed while other parts of KVM assume it is halted. Consequences are left to the imagination of the reader.
Fix this sorry situation by turning kvm_vcpu_arch::pause into an atomic counter, which makes the races described above harmless.
--- arch/arm64/include/asm/kvm_host.h | 7 +++---- arch/arm64/kvm/arm.c | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-)
arch/arm64/include/asm/kvm_host.h +3 -4
@@ -889,11 +889,10 @@ struct kvm_vcpu_arch {/* * Don't run the guest (internal implementation need). * -* Contrary to the flags above, this is set/cleared outside of -* a vcpu context, and thus cannot be mixed with the flags -* themselves (or the flag accesses need to be made atomic). +* Contrary to the flags above, this is updated outside of +* a vcpu context, and thus cannot be mixed with the flags. */ -bool pause; +atomic_t pause;/* * We maintain more than a single set of debug registers to support
arch/arm64/kvm/arm.c +12 -9
@@ -561,6 +561,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)kvm_arm_pvtime_vcpu_init (&vcpu->arch); vcpu->arch.hw_mmu = &vcpu->kvm->arch.mmu; +atomic_set (&vcpu->arch.pause,0 );/* * This vCPU may have been created after mpidr_data was initialized.
@@ -835,6 +836,11 @@ int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu,return ret; } +static bool vcpu_can_run (struct kvm_vcpu *vcpu) +{ +return !kvm_arm_vcpu_stopped (vcpu) && !atomic_read (&vcpu->arch.pause); +} +/** * kvm_arch_vcpu_runnable - determine if the vcpu can be scheduled * @v: The VCPU pointer
@@ -850,8 +856,7 @@ int kvm_arch_vcpu_runnable(struct kvm_vcpu *v) (kvm_timer_should_notify_user (v) || kvm_pmu_should_notify_user(v))); -return ((irq_lines ||kvm_vgic_vcpu_pending_irq (v)) - && !kvm_arm_vcpu_stopped (v) && !v->arch.pause); +return ((irq_lines ||kvm_vgic_vcpu_pending_irq (v)) &&vcpu_can_run (v)); }bool kvm_arch_vcpu_in_kernel (struct kvm_vcpu *vcpu)
@@ -1014,7 +1019,7 @@ void kvm_arm_halt_guest(struct kvm *kvm)struct kvm_vcpu *vcpu;kvm_for_each_vcpu (i, vcpu, kvm) - vcpu->arch.pause = true; +atomic_inc (&vcpu->arch.pause);kvm_make_all_cpus_request (kvm,KVM_REQ_SLEEP ); }
@@ -1024,8 +1029,8 @@ void kvm_arm_resume_guest(struct kvm *kvm)struct kvm_vcpu *vcpu;kvm_for_each_vcpu (i, vcpu, kvm) { - vcpu->arch.pause = false; -__kvm_vcpu_wake_up (vcpu); +if (atomic_dec_and_test (&vcpu->arch.pause)) +__kvm_vcpu_wake_up (vcpu); } }
@@ -1033,11 +1038,9 @@ static void kvm_vcpu_sleep(struct kvm_vcpu *vcpu) {struct rcuwait *wait =kvm_arch_vcpu_get_wait (vcpu); -rcuwait_wait_event (wait, - (!kvm_arm_vcpu_stopped (vcpu)) && (!vcpu->arch.pause), -TASK_INTERRUPTIBLE ); +rcuwait_wait_event (wait,vcpu_can_run (vcpu),TASK_INTERRUPTIBLE ); -if (kvm_arm_vcpu_stopped (vcpu) || vcpu->arch.pause) { +if (!vcpu_can_run (vcpu)) {/* Awaken to handle a signal, request we sleep again later. */ kvm_make_request (KVM_REQ_SLEEP , vcpu); }
Signature · 1 line
2.47.3
Re: [PATCH v2 2/7] KVM: arm64: Turn vcpu->arch.pause into a counter
Quoted 7 lines · Marc Zyngier
We have situations where we can call
kvm_arm_halt_guest()without holdingconfig_lock, which means that halt and resume can overlap in funny ways when called from separate contexts, and result in situations where a vcpu is resumed while other parts of KVM assume it is halted. Consequences are left to the imagination of the reader.
Quoted 6 lines
Fix this sorry situation by turning
kvm_vcpu_arch::pauseinto an atomic counter, which makes the races described above harmless.Fixes 3b92830ad41b2 ("KVM: arm/arm64: implement kvm_arm_[halt,resume]_guest")Signed-off-by Marc Zyngier <[email protected]>
Cheers,
/fuad
Quoted 95 lines
--- arch/arm64/include/asm/kvm_host.h | 7 +++---- arch/arm64/kvm/arm.c | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-)arch/arm64/include/asm/kvm_host.h +3
-4@@ -889,11 +889,10 @@ struct kvm_vcpu_arch {/* * Don't run the guest (internal implementation need). * -* Contrary to the flags above, this is set/cleared outside of -* a vcpu context, and thus cannot be mixed with the flags -* themselves (or the flag accesses need to be made atomic). +* Contrary to the flags above, this is updated outside of +* a vcpu context, and thus cannot be mixed with the flags. */ -bool pause; +atomic_t pause;/* * We maintain more than a single set of debug registers to support arch/arm64/kvm/arm.c +12
-10@@ -561,6 +561,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)kvm_arm_pvtime_vcpu_init (&vcpu->arch); vcpu->arch.hw_mmu = &vcpu->kvm->arch.mmu; +atomic_set (&vcpu->arch.pause,0 );/* * This vCPU may have been created after mpidr_data was initialized. @@ -835,6 +836,11 @@ int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu,return ret; } +static bool vcpu_can_run (struct kvm_vcpu *vcpu) +{ +return !kvm_arm_vcpu_stopped (vcpu) && !atomic_read (&vcpu->arch.pause); +} +/** * kvm_arch_vcpu_runnable - determine if the vcpu can be scheduled * @v: The VCPU pointer @@ -850,8 +856,7 @@ int kvm_arch_vcpu_runnable(struct kvm_vcpu *v) (kvm_timer_should_notify_user (v) || kvm_pmu_should_notify_user(v))); -return ((irq_lines ||kvm_vgic_vcpu_pending_irq (v)) - && !kvm_arm_vcpu_stopped (v) && !v->arch.pause); +return ((irq_lines ||kvm_vgic_vcpu_pending_irq (v)) &&vcpu_can_run (v)); }bool kvm_arch_vcpu_in_kernel (struct kvm_vcpu *vcpu)@@ -1014,7 +1019,7 @@ void kvm_arm_halt_guest(struct kvm *kvm)struct kvm_vcpu *vcpu;kvm_for_each_vcpu (i, vcpu, kvm) - vcpu->arch.pause = true; +atomic_inc (&vcpu->arch.pause);kvm_make_all_cpus_request (kvm,KVM_REQ_SLEEP ); }@@ -1024,8 +1029,8 @@ void kvm_arm_resume_guest(struct kvm *kvm)struct kvm_vcpu *vcpu;kvm_for_each_vcpu (i, vcpu, kvm) { - vcpu->arch.pause = false; -__kvm_vcpu_wake_up (vcpu); +if (atomic_dec_and_test (&vcpu->arch.pause)) +__kvm_vcpu_wake_up (vcpu); } }@@ -1033,11 +1038,9 @@ static void kvm_vcpu_sleep(struct kvm_vcpu *vcpu) {struct rcuwait *wait =kvm_arch_vcpu_get_wait (vcpu); -rcuwait_wait_event (wait, - (!kvm_arm_vcpu_stopped (vcpu)) && (!vcpu->arch.pause), -TASK_INTERRUPTIBLE ); +rcuwait_wait_event (wait,vcpu_can_run (vcpu),TASK_INTERRUPTIBLE ); -if (kvm_arm_vcpu_stopped (vcpu) || vcpu->arch.pause) { +if (!vcpu_can_run (vcpu)) {/* Awaken to handle a signal, request we sleep again later. */ kvm_make_request (KVM_REQ_SLEEP , vcpu); } --2.47.3
Marc Zyngier
maintainerPATCH v2 3/7 KVM: arm64: vgic: Allow last_lr_irq to be NULL when LRs are not overflowing
In reply to Marc Zyngierlast_lr_irq is always populated when there is any interrupt populated in the AP list. Not only this is not necessary (it is only useful when we completely fill the LRs), but this is in the way of further fixes.
Make sure last_lr_irq is kept to NULL when we LRs are not completely full.
--- arch/arm64/kvm/vgic/vgic-v2.c | 6 ++++-- arch/arm64/kvm/vgic/vgic-v3.c | 6 ++++-- arch/arm64/kvm/vgic/vgic.c | 8 +++----- 3 files changed, 11 insertions(+), 9 deletions(-)
arch/arm64/kvm/vgic/vgic-v2.c +4 -2
@@ -122,6 +122,10 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu)for (int lr =0 ; lr < vgic_cpu->vgic_v2.used_lrs; lr++)vgic_v2_fold_lr (vcpu, cpuif->vgic_lr[lr]); + cpuif->used_lrs =0 ; +if (!irq) +return ; +/* See the GICv3 equivalent for the EOIcount handling rationale */ list_for_each_entry_continue (irq, &vgic_cpu->ap_list_head, ap_list) {u32 lr;
@@ -144,8 +148,6 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu)vgic_v2_fold_lr (vcpu, lr); eoicount--; } - - cpuif->used_lrs =0 ; }void vgic_v2_deactivate (struct kvm_vcpu *vcpu,u32 val)
arch/arm64/kvm/vgic/vgic-v3.c +4 -2
@@ -155,6 +155,10 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu)for (int lr =0 ; lr < cpuif->used_lrs; lr++)vgic_v3_fold_lr (vcpu, cpuif->vgic_lr[lr]); + cpuif->used_lrs =0 ; +if (!irq) +return ; +/* * EOIMode=0: use EOIcount to emulate deactivation. We are * guaranteed to deactivate in reverse order of the activation, so
@@ -188,8 +192,6 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu)vgic_v3_fold_lr (vcpu, lr); eoicount--; } - - cpuif->used_lrs =0 ; }void vgic_v3_deactivate (struct kvm_vcpu *vcpu,u64 val)
arch/arm64/kvm/vgic/vgic.c +3 -5
@@ -866,9 +866,6 @@ static void vgic_fold_state(struct kvm_vcpu *vcpu)return ; } -if (!*host_data_ptr (last_lr_irq)) -return ; -if (kvm_vgic_global_state.type ==VGIC_V2 )vgic_v2_fold_lr_state (vcpu);else
@@ -1021,11 +1018,12 @@ static void vgic_flush_lr_state(struct kvm_vcpu *vcpu) scoped_guard(raw_spinlock, &irq->irq_lock) {if (likely (vgic_target_oracle (irq) == vcpu)) {vgic_populate_lr (vcpu, irq, count++); - *host_data_ptr (last_lr_irq) = irq; +if (count == kvm_vgic_global_state.nr_lr) + *host_data_ptr (last_lr_irq) = irq; } } -if (count == kvm_vgic_global_state.nr_lr) +if (*host_data_ptr (last_lr_irq))break ; }
Signature · 1 line
2.47.3
Marc Zyngier
maintainerPATCH v2 4/7 KVM: arm64: vgic: Take a refcount on IRQs referenced by last_lr_irq
In reply to Marc ZyngierReferencing the last interrupt inserted in an LR is rather fragile, as this interrupt can vanish if a concurrently unmapped LPI.
Solve this by bumping up the refcount on the interrupt when populating last_lr_irq, and drop it at vgic_prune_ap_list() time, when LPIs are being reclaimed.
--- arch/arm64/kvm/vgic/vgic.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-)
arch/arm64/kvm/vgic/vgic.c +14 -1
@@ -853,6 +853,17 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu) goto retry; } +/* +* Fix the last_lr_irq refcount which was obtained while +* populating the LRs. This can also result in the LPI being +* deleted. +*/ + irq = *host_data_ptr (last_lr_irq); +if (irq) { + deleted_lpis |=vgic_put_irq_norelease (vcpu->kvm, irq); + *host_data_ptr (last_lr_irq) =NULL ; + } +raw_spin_unlock (&vgic_cpu->ap_list_lock);if (unlikely (deleted_lpis))
@@ -1018,8 +1029,10 @@ static void vgic_flush_lr_state(struct kvm_vcpu *vcpu) scoped_guard(raw_spinlock, &irq->irq_lock) {if (likely (vgic_target_oracle (irq) == vcpu)) {vgic_populate_lr (vcpu, irq, count++); -if (count == kvm_vgic_global_state.nr_lr) +if (count == kvm_vgic_global_state.nr_lr) { +vgic_get_irq_ref (irq); *host_data_ptr (last_lr_irq) = irq; + } } }
Signature · 1 line
2.47.3
Marc Zyngier
maintainerPATCH v2 5/7 KVM: arm64: vgic: Stop the VM when disabling LPIs
In reply to Marc ZyngierDisabling LPIs is pretty nasty, as it directly messes with the AP list of the affected CPU, which could be running... This has the potential to lead to really bad behaviours, and we should not be doing that.
Take example on the way the Active state is handled and simply pause all the vcpus so that we are sure they are all in a quiescent state, and the state be safely manipulated.
Nobody has any expectation of performance for this anyway.
--- arch/arm64/kvm/vgic/vgic-mmio-v3.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
arch/arm64/kvm/vgic/vgic-mmio-v3.c +10 -0
@@ -303,9 +303,19 @@ static void vgic_mmio_write_v3r_ctlr(struct kvm_vcpu *vcpu,if (ctlr !=GICR_CTLR_ENABLE_LPIS )return ; +/* +* Yes, disabling LPIs is painful, since it can be done from +* a *remote* vcpu! So let's not take any chance, and make +* sure that everybody has written their LRs back to the irq +* structures, and release any reference they would have. +* +* If it hurts, don't do it. +*/ +kvm_arm_halt_guest (vcpu->kvm);vgic_flush_pending_lpis (vcpu);vgic_its_invalidate_all_caches (vcpu->kvm);atomic_set_release (&vgic_cpu->ctlr,0 ); +kvm_arm_resume_guest (vcpu->kvm); }else { ctlr =atomic_cmpxchg_acquire (&vgic_cpu->ctlr,0 ,GICR_CTLR_ENABLE_LPIS );
Signature · 1 line
2.47.3
Marc Zyngier
maintainerPATCH v2 6/7 KVM: arm64: vgic-its: Fix MOVALL handling of source redistributor
In reply to Marc ZyngierThe MOVALL command moves the pending state of all LPIs targeting a given redistributor to another one. However, we seem to have lost the filtering on the source RD, which means we move all LPIs to the target.
Not quite what the spec mandates.
Hack update_affinity() to take an optional source vcpu that is used as a filter when non-NULL, restoring the filtering that was performed by vgic_copy_lpi_list() back in the days.
--- arch/arm64/kvm/vgic/vgic-its.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
arch/arm64/kvm/vgic/vgic-its.c +8 -4
@@ -319,12 +319,16 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,return ret; } -static int update_affinity (struct vgic_irq *irq,struct kvm_vcpu *vcpu) +static int update_affinity (struct vgic_irq *irq, +struct kvm_vcpu *from_vcpu,struct kvm_vcpu *vcpu) {struct its_vlpi_map map;int ret;guard (raw_spinlock_irqsave)(&irq->irq_lock); +if (from_vcpu && irq->target_vcpu != from_vcpu) +return 0 ; + irq->target_vcpu = vcpu;if (!irq->hw)
@@ -362,7 +366,7 @@ static void update_affinity_ite(struct kvm *kvm, struct its_ite *ite)return ; vcpu =collection_to_vcpu (kvm, ite->collection); -update_affinity (ite->irq, vcpu); +update_affinity (ite->irq,NULL , vcpu); }/*
@@ -856,7 +860,7 @@ static int vgic_its_cmd_handle_movi(struct kvm *kvm, struct vgic_its *its,vgic_its_invalidate_cache (its); -return update_affinity (ite->irq, vcpu); +return update_affinity (ite->irq,NULL , vcpu); }static bool __is_visible_gfn_locked (struct vgic_its *its,gpa_t gpa)
@@ -1383,7 +1387,7 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its,if (!irq)continue ; -update_affinity (irq, vcpu2); +update_affinity (irq, vcpu1, vcpu2);vgic_put_irq (kvm, irq); }
Signature · 1 line
2.47.3
Re: [PATCH v2 6/7] KVM: arm64: vgic-its: Fix MOVALL handling of source redistributor
Quoted 7 lines · Marc Zyngier
The MOVALL command moves the pending state of all LPIs targeting a given redistributor to another one. However, we seem to have lost the filtering on the source RD, which means we move all LPIs to the target.
Not quite what the spec mandates.
Quoted 7 lines
Hack
update_affinity()to take an optional source vcpu that is used as a filter when non-NULL, restoring the filtering that was performed byvgic_copy_lpi_list()back in the days.Fixes 11f4f8f3e6e06 ("KVM: arm64: vgic-its: Walk LPI xarray in vgic_its_cmd_handle_movall()")Signed-off-by Marc Zyngier <[email protected]>
Cheers,
/fuad
Quoted 56 lines
--- arch/arm64/kvm/vgic/vgic-its.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)arch/arm64/kvm/vgic/vgic-its.c +8
-5@@ -319,12 +319,16 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,return ret; } -static int update_affinity (struct vgic_irq *irq,struct kvm_vcpu *vcpu) +static int update_affinity (struct vgic_irq *irq, +struct kvm_vcpu *from_vcpu,struct kvm_vcpu *vcpu) {struct its_vlpi_map map;int ret;guard (raw_spinlock_irqsave)(&irq->irq_lock); +if (from_vcpu && irq->target_vcpu != from_vcpu) +return 0 ; + irq->target_vcpu = vcpu;if (!irq->hw)@@ -362,7 +366,7 @@ static void update_affinity_ite(struct kvm *kvm, struct its_ite *ite)return ; vcpu =collection_to_vcpu (kvm, ite->collection); -update_affinity (ite->irq, vcpu); +update_affinity (ite->irq,NULL , vcpu); }/* @@ -856,7 +860,7 @@ static int vgic_its_cmd_handle_movi(struct kvm *kvm, struct vgic_its *its,vgic_its_invalidate_cache (its); -return update_affinity (ite->irq, vcpu); +return update_affinity (ite->irq,NULL , vcpu); }static bool __is_visible_gfn_locked (struct vgic_its *its,gpa_t gpa)@@ -1383,7 +1387,7 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its,if (!irq)continue ; -update_affinity (irq, vcpu2); +update_affinity (irq, vcpu1, vcpu2);vgic_put_irq (kvm, irq); } --2.47.3
Marc Zyngier
maintainerPATCH v2 7/7 KVM: arm64: vgic-its: Stop the VM when handling MOVALL
In reply to Marc ZyngierMOVALL is a royal pain, as it requires us to iterate over all the LPIs and move them around if they are on the correct source vcpu, and requiring locking for each of them.
This locking can result in contention for vcpus that enter and exit the guest, and hinders forward progress. In order to make sure such process is not contended, just stop the VM altogether.
Nobody in their right mind uses MOVALL anyway, as this is a rather braindead aspect of the GICv3 architecture.
--- arch/arm64/kvm/vgic/vgic-its.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
arch/arm64/kvm/vgic/vgic-its.c +11 -0
@@ -1382,6 +1382,15 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its,if (vcpu1 == vcpu2)return 0 ; +/* +* Bulk operations such as MOVALL are a pain, as they can clash +* badly locking-wise with other vcpus entering and exiting the +* guest, should they be affected by it. Stopping the guest is a +* safer bet to ensure uncontended access and ultimately forward +* progress. Yeah... +*/ +kvm_arm_halt_guest (kvm); +xa_for_each (&dist->lpi_xa, intid, irq) { irq =vgic_get_irq (kvm, intid);if (!irq)
@@ -1394,6 +1403,8 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its,vgic_its_invalidate_cache (its); +kvm_arm_resume_guest (kvm); +return 0 ; }
Signature · 1 line
2.47.3
Re: [PATCH v2 7/7] KVM: arm64: vgic-its: Stop the VM when handling MOVALL
Quoted 5 lines · Marc Zyngier
MOVALL is a royal pain, as it requires us to iterate over all the LPIs and move them around if they are on the correct source vcpu, and requiring locking for each of them.
Quoted 8 lines
This locking can result in contention for vcpus that enter and exit the guest, and hinders forward progress. In order to make sure such process is not contended, just stop the VM altogether.
Nobody in their right mind uses MOVALL anyway, as this is a rather braindead aspect of the GICv3 architecture.
Signed-off-by Marc Zyngier <[email protected]>
This is the only patch in the series without Cc: stable. Was that deliberate?
Cheers,
/fuad
Quoted 36 lines
--- arch/arm64/kvm/vgic/vgic-its.c | 11 +++++++++++ 1 file changed, 11 insertions(+)arch/arm64/kvm/vgic/vgic-its.c +11
-1@@ -1382,6 +1382,15 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its,if (vcpu1 == vcpu2)return 0 ; +/* +* Bulk operations such as MOVALL are a pain, as they can clash +* badly locking-wise with other vcpus entering and exiting the +* guest, should they be affected by it. Stopping the guest is a +* safer bet to ensure uncontended access and ultimately forward +* progress. Yeah... +*/ +kvm_arm_halt_guest (kvm); +xa_for_each (&dist->lpi_xa, intid, irq) { irq =vgic_get_irq (kvm, intid);if (!irq)@@ -1394,6 +1403,8 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its,vgic_its_invalidate_cache (its); +kvm_arm_resume_guest (kvm); +return 0 ; } --2.47.3
Fuad Tabba
reviewercover KVM: arm64: selftests: Cover the ITS MOVALL command
In reply to Marc ZyngierHi Marc,
I wrote this to test your series [1]. It fails without patch 6 and passes with the whole series, on QEMU with pKVM, VHE and nVHE hosts. On an Apple M4 host, the series boots and runs protected and non-protected guests. The M4's GIC has no ITS, so the test doesn't run there.
Feel free to take it with your series if you think it's useful.
Based on kvmarm/next (5c49bb52faf38).
Cheers,
/fuad
[1] https://lore.kernel.org/all/[email protected]/
Fuad Tabba (2):
KVM: arm64: selftests: Add a MOVALL command to the ITS library
KVM: arm64: selftests: Add an ITS MOVALL test
tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/vgic_its_movall.c | 341 ++++++++++++++++++ .../selftests/kvm/include/arm64/gic_v3_its.h | 1 + .../selftests/kvm/lib/arm64/gic_v3_its.c | 16 + 4 files changed, 359 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/vgic_its_movall.c
base-commit: 5c49bb52faf38ddf9c3383807b1d814ef8769b80
Signature · 1 line
2.39.5
Fuad Tabba
reviewerPATCH 1/2 KVM: arm64: selftests: Add a MOVALL command to the ITS library
Add its_send_movall_cmd() for a MOVALL test. Both redistributors are encoded as processor numbers, as its_send_mapc_cmd() does.
--- .../selftests/kvm/include/arm64/gic_v3_its.h | 1 + .../testing/selftests/kvm/lib/arm64/gic_v3_its.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+)
tools/testing/selftests/kvm/include/arm64/gic_v3_its.h +1 -0
@@ -14,6 +14,7 @@ void its_send_mapc_cmd(void *cmdq_base, u32 vcpu_id, u32 collection_id, bool valvoid its_send_mapti_cmd (void *cmdq_base,u32 device_id,u32 event_id,u32 collection_id,u32 intid);void its_send_invall_cmd (void *cmdq_base,u32 collection_id); +void its_send_movall_cmd (void *cmdq_base,u32 from_vcpu,u32 to_vcpu);void its_send_sync_cmd (void *cmdq_base,u32 vcpu_id);#endif // __SELFTESTS_GIC_V3_ITS_H__
tools/testing/selftests/kvm/lib/arm64/gic_v3_its.c +16 -0
@@ -159,6 +159,11 @@ static void its_encode_target(struct its_cmd_block *cmd, u64 target_addr)its_mask_encode (&cmd->raw_cmd[2 ], target_addr >>16 ,51 ,16 ); } +static void its_encode_target2 (struct its_cmd_block *cmd,u64 target_addr) +{ +its_mask_encode (&cmd->raw_cmd[3 ], target_addr >>16 ,51 ,16 ); +} +static void its_encode_collection (struct its_cmd_block *cmd,u16 col) {its_mask_encode (&cmd->raw_cmd[2 ], col,15 ,0 );
@@ -253,6 +258,17 @@ void its_send_invall_cmd(void *cmdq_base, u32 collection_id)its_send_cmd (cmdq_base, &cmd); } +void its_send_movall_cmd (void *cmdq_base,u32 from_vcpu,u32 to_vcpu) +{ +struct its_cmd_block cmd = {}; + +its_encode_cmd (&cmd,GITS_CMD_MOVALL ); +its_encode_target (&cmd,procnum_to_rdbase (from_vcpu)); +its_encode_target2 (&cmd,procnum_to_rdbase (to_vcpu)); + +its_send_cmd (cmdq_base, &cmd); +} +void its_send_sync_cmd (void *cmdq_base,u32 vcpu_id) {struct its_cmd_block cmd = {};
Signature · 1 line
2.39.5
Fuad Tabba
reviewerPATCH 2/2 KVM: arm64: selftests: Add an ITS MOVALL test
In reply to Fuad TabbaMap LPI A to vCPU0's collection and LPI B to vCPU1's, remap vCPU0's collection to vCPU2 and MOVALL vCPU0's LPIs there, then inject both. A must be taken on vCPU2 and B on vCPU1. KVM's MAPC handling already moves A to vCPU2, so only MOVALL handling can move B: without "KVM: arm64:
vgic-its: Fix MOVALL handling of source redistributor", it moves B to vCPU2 as well.
--- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/vgic_its_movall.c | 341 ++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/vgic_its_movall.c
tools/testing/selftests/kvm/Makefile.kvm +1 -0
@@ -190,6 +190,7 @@ TEST_GEN_PROGS_arm64 += arm64/stage2_block_transitions TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config TEST_GEN_PROGS_arm64 += arm64/vgic_init TEST_GEN_PROGS_arm64 += arm64/vgic_irq +TEST_GEN_PROGS_arm64 += arm64/vgic_its_movall TEST_GEN_PROGS_arm64 += arm64/vgic_its_save TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress TEST_GEN_PROGS_arm64 += arm64/vgic_v5
tools/testing/selftests/kvm/arm64/vgic_its_movall.c new file +341 -0
341 lines not on this page · raw message
Signature · 1 line
2.39.5
Marc Zyngier
maintainerRe: [PATCH v1 2/2] KVM: arm64: selftests: Add an ITS MOVALL test
Quoted 7 lines · Fuad Tabba
Map LPI A to vCPU0's collection and LPI B to vCPU1's, remap vCPU0's collection to vCPU2 and MOVALL vCPU0's LPIs there, then inject both. A must be taken on vCPU2 and B on vCPU1. KVM's MAPC handling already moves A to vCPU2, so only MOVALL handling can move B: without "KVM: arm64:
vgic-its: Fix MOVALL handling of source redistributor", it moves B to vCPU2 as well.
MOVALL is about moving the pending bits (see 5.3.13 in the IHI0069H.b spec). Doing MOVALL first and only then injecting the interrupts doesn't quite check the MOVALL requirement. That doesn't impact KVM itself (we don't use pending tables at all), but you probably don't want to hardcode implementation specific behaviours here.
Quoted 77 lines
Assisted-by LLMSigned-off-by Fuad Tabba <[email protected]>--- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/vgic_its_movall.c | 341 ++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/vgic_its_movall.ctools/testing/selftests/kvm/Makefile.kvm +1
-0@@ -190,6 +190,7 @@ TEST_GEN_PROGS_arm64 += arm64/stage2_block_transitions TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config TEST_GEN_PROGS_arm64 += arm64/vgic_init TEST_GEN_PROGS_arm64 += arm64/vgic_irq +TEST_GEN_PROGS_arm64 += arm64/vgic_its_movall TEST_GEN_PROGS_arm64 += arm64/vgic_its_save TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress TEST_GEN_PROGS_arm64 += arm64/vgic_v5tools/testing/selftests/kvm/arm64/vgic_its_movall.c new file +50
-0@@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* vgic_its_movall - MOVALL does not move the LPIs of other redistributors. +* +* Two LPIs, A and B, target two different redistributors. A's collection is +* remapped to a third redistributor with MAPC, followed by MOVALL from the +* first redistributor to the third. Both LPIs are then injected. A must be +* taken on the third redistributor, and B must stay on its own, which is not +* MOVALL's source. +* +* Copyright (c) 2026 Google LLC +* Author: Fuad Tabba <[email protected]> +*/ + +#include <linux/sizes.h> +#include <pthread.h> +#include <stdatomic.h> + +#include "kvm_util.h" +#include "gic.h" +#include "gic_v3.h" +#include "gic_v3_its.h" +#include "processor.h" +#include "ucall.h" +#include "vgic.h" + +#define TEST_MEMSLOT_INDEX 1 + +#define GIC_LPI_OFFSET 8192 +#define NR_VCPUS 3 +#define NR_LPIS 2 +#define DEVICE_ID 0 + +/* LPI A (event 0) starts on vCPU0 and moves to vCPU2; B (event 1) stays on vCPU1 */ +#define LPI_A_COLL 0 +#define LPI_B_COLL 1 +#define MOVALL_FROM 0 +#define MOVALL_TO 2 +#define LPI_B_VCPU 1 + +#define LPI_PROP_DEFAULT_PRIO 0xa0 + +static gpa_t gpa_base; + +static struct kvm_vm *vm; +static struct kvm_vcpu *vcpus[NR_VCPUS ]; +static int its_fd; + +static struct test_data { + bool request_vcpus_stop;
tools/testing/selftests/kvm/arm64/vgic_its_movall.c line 60
Quoted 10 lines
tools/testing/selftests/kvm/arm64/vgic_its_movall.c new file +10
-0+ +gpa_t device_table; +gpa_t collection_table; +gpa_t cmdq_base; +void *cmdq_base_va; +gpa_t itt_table; + +gpa_t lpi_prop_table; +gpa_t lpi_pend_tables; +} test_data;
There seem to be a lot of commonality with the existing vgic_lpi_stress test. I'd rather we make this test the container for most ITS-related tests, instead of coming up with new individual tests.
Thanks,
N,
Signature · 1 line
Without deviation from the norm, progress is not possible.
Fuad Tabba
reviewerRe: [PATCH v1 2/2] KVM: arm64: selftests: Add an ITS MOVALL test
Hi Marc,
Quoted 11 lines · Marc Zyngier
Quoted 7 lines · Fuad Tabba
Map LPI A to vCPU0's collection and LPI B to vCPU1's, remap vCPU0's collection to vCPU2 and MOVALL vCPU0's LPIs there, then inject both. A must be taken on vCPU2 and B on vCPU1. KVM's MAPC handling already moves A to vCPU2, so only MOVALL handling can move B: without "KVM: arm64:
vgic-its: Fix MOVALL handling of source redistributor", it moves B to vCPU2 as well.
Quoted 5 lines
MOVALL is about moving the pending bits (see 5.3.13 in the IHI0069H.b spec). Doing MOVALL first and only then injecting the interrupts doesn't quite check the MOVALL requirement. That doesn't impact KVM itself (we don't use pending tables at all), but you probably don't want to hardcode implementation specific behaviours here.
Ack.
[...]
Quoted 5 lines
There seem to be a lot of commonality with the existing
vgic_lpi_stresstest. I'd rather we make this test the container for most ITS-related tests, instead of coming up with new individual tests.
If you think this test is worth having, I'll respin v2 that way.
Cheers,
/fuad
Quoted 6 lines
Thanks,
N,--
Without deviation from the norm, progress is not possible.
Re: (subset) [PATCH v2 0/7] KVM: arm64: vgic-v3: Make LPI disabling robust (and more)
In reply to Marc ZyngierQuoted 8 lines · Marc Zyngier
This is v2 of this series addressing shortcomings of LPIs being disabled on one CPU from another. It has now expanded into some more common areas.
Yuchao Zhang reported that disabling LPIs on one CPU from another could result in UAFs and other horrors.
[...]
Applied to fixes, thanks!
[3/7] KVM: arm64: vgic: Allow last_lr_irq to be NULL when LRs are not overflowing
https://git.kernel.org/kvmarm/kvmarm/c/e079ef0b1c40 [4/7] KVM: arm64: vgic: Take a refcount on IRQs referenced by last_lr_irq https://git.kernel.org/kvmarm/kvmarm/c/19bc5d79ece6 [6/7] KVM: arm64: vgic-its: Fix MOVALL handling of source redistributor https://git.kernel.org/kvmarm/kvmarm/c/751f4641560b
--
Best,
Oliver
Fuad Tabba
reviewerHi Marc,
On Tue, 29 Sep 2026 10:35:41 +0100, Marc Zyngier <[email protected]> wrote:
[...]
Quoted 3 lines
Address the two issues in one go, by actively taking a refcount on all IRQs referenced by
last_lr_irq, and making sure that disabling LPIs force all vcpus to be paused, making it safe.
I'm triaging Sashiko's pre-existing bug database, and I ran into something that I think this series doesn't cover.
vgic_its_inject_cached_translation() doesn't check vgic_lpis_enabled() (the slow path, vgic_its_resolve_lpi(), does), and pausing the vCPUs doesn't stop an MSI coming in through irqfd or KVM_SIGNAL_MSI. So an MSI that hits the translation cache between vgic_flush_pending_lpis() and vgic_its_invalidate_all_caches() can still end up on the ap_list of a vCPU whose LPIs are being disabled, after the flush.
Could the cached path check vgic_lpis_enabled() on the target vCPU, or am I missing something?
Cheers,
/fuad
Oliver Upton
maintainerHi Fuad,
Quoted 18 lines · Fuad Tabba
Hi Marc,
On Tue, 29 Sep 2026 10:35:41 +0100, Marc Zyngier <[email protected]> wrote:
[...]Quoted 3 lines
Address the two issues in one go, by actively taking a refcount on all IRQs referenced by
last_lr_irq, and making sure that disabling LPIs force all vcpus to be paused, making it safe.I'm triaging Sashiko's pre-existing bug database, and I ran into something that I think this series doesn't cover.
vgic_its_inject_cached_translation()doesn't checkvgic_lpis_enabled()(the slow path,vgic_its_resolve_lpi(), does), and pausing the vCPUs doesn't stop an MSI coming in through irqfd orKVM_SIGNAL_MSI. So an MSI that hits the translation cache betweenvgic_flush_pending_lpis()andvgic_its_invalidate_all_caches()can still end up on theap_listof a vCPU whose LPIs are being disabled, after the flush.
Quoted 2 lines
Could the cached path check
vgic_lpis_enabled()on the target vCPU, or am I missing something?
Hmm, since there's no parent lock between disabling LPIs and translation cache fills I believe there's still a chance for this to race. We could have vgic_target_oracle() return NULL if LPIs are disabled at the redistributor, then the rest of the AP list machinery will "just work" for injections that slip between the cracks.
If only Arm went a bit further than "strongly recommends" on migrating LPIs _before_ flipping the bit...
Thanks,
Oliver
Fuad Tabba
reviewerHi Oliver,
[...]
Quoted 1 line
We could have
vgic_target_oracle()return NULL if LPIs are disabled at the redistributor, then the rest of the AP list machinery will "just work" for injections that slip between the cracks.
That would also cover a related one Sashiko raised:vgic_its_resolve_lpi() can cache a translation after vgic_its_invalidate_all_caches() has run, since the disable path doesn't take its_lock, but any MSI that hits that entry would then be dropped by the oracle. I'll write it up and send a patch on top of Marc's series.
Cheers,
/fuad