Hi Marc,
Some time ago, you suggested that we should communicate a change
of the IRQ state via vgic_queue_irq_unlock [1], which needs to
include dropping the IRQ from the VCPU's ap_list if the IRQ is
not pending or enabled but on the ap_list. And I additionally
add a case where the IRQ has to be migrated to another ap_list.
(maybe you forget this...)
Does this patch match your thought at the time?
[1] https://lore.kernel.org/patchwork/patch/1371884/
Signed-off-by: Shenming Lu <[email protected]>
---
arch/arm64/kvm/vgic/vgic.c | 116 ++++++++++++++++++++++++-------------
1 file changed, 75 insertions(+), 41 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 15b666200f0b..9b88d49aa439 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -326,8 +326,9 @@ static bool vgic_validate_injection(struct vgic_irq *irq, bool level, void *owne
/*
* Check whether an IRQ needs to (and can) be queued to a VCPU's ap list.
- * Do the queuing if necessary, taking the right locks in the right order.
- * Returns true when the IRQ was queued, false otherwise.
+ * Do the queuing, dropping or migrating if necessary, taking the right
+ * locks in the right order. Returns true when the IRQ was queued, false
+ * otherwise.
*
* Needs to be entered with the IRQ lock already held, but will return
* with all locks dropped.
@@ -335,49 +336,38 @@ static bool vgic_validate_injection(struct vgic_irq *irq, bool level, void *owne
bool vgic_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
unsigned long flags)
{
+ struct kvm_vcpu *target_vcpu;
struct kvm_vcpu *vcpu;
+ bool ret = false;
lockdep_assert_held(&irq->irq_lock);
retry:
- vcpu = vgic_target_oracle(irq);
- if (irq->vcpu || !vcpu) {
+ target_vcpu = vgic_target_oracle(irq);
+ vcpu = irq->vcpu;
+ if (target_vcpu == vcpu) {
/*
- * If this IRQ is already on a VCPU's ap_list, then it
- * cannot be moved or modified and there is no more work for
+ * If this IRQ's state is consistent with whether on
+ * the right ap_lsit or not, there is no more work for
* us to do.
- *
- * Otherwise, if the irq is not pending and enabled, it does
- * not need to be inserted into an ap_list and there is also
- * no more work for us to do.
*/
raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
-
- /*
- * We have to kick the VCPU here, because we could be
- * queueing an edge-triggered interrupt for which we
- * get no EOI maintenance interrupt. In that case,
- * while the IRQ is already on the VCPU's AP list, the
- * VCPU could have EOI'ed the original interrupt and
- * won't see this one until it exits for some other
- * reason.
- */
- if (vcpu) {
- kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
- kvm_vcpu_kick(vcpu);
- }
- return false;
+ goto out;
}
/*
* We must unlock the irq lock to take the ap_list_lock where
- * we are going to insert this new pending interrupt.
+ * we are going to insert/drop this IRQ.
*/
raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
/* someone can do stuff here, which we re-check below */
- raw_spin_lock_irqsave(&vcpu->arch.vgic_cpu.ap_list_lock, flags);
+ if (target_vcpu)
+ raw_spin_lock_irqsave(&target_vcpu->arch.vgic_cpu.ap_list_lock,
+ flags);
+ if (vcpu)
+ raw_spin_lock_irqsave(&vcpu->arch.vgic_cpu.ap_list_lock, flags);
raw_spin_lock(&irq->irq_lock);
/*
@@ -392,30 +382,74 @@ bool vgic_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
* In both cases, drop the locks and retry.
*/
- if (unlikely(irq->vcpu || vcpu != vgic_target_oracle(irq))) {
+ if (unlikely(target_vcpu != vgic_target_oracle(irq) ||
+ vcpu != irq->vcpu)) {
raw_spin_unlock(&irq->irq_lock);
- raw_spin_unlock_irqrestore(&vcpu->arch.vgic_cpu.ap_list_lock,
- flags);
+ if (target_vcpu)
+ raw_spin_unlock_irqrestore(&target_vcpu->arch.vgic_cpu.ap_list_lock,
+ flags);
+ if (vcpu)
+ raw_spin_unlock_irqrestore(&vcpu->arch.vgic_cpu.ap_list_lock,
+ flags);
raw_spin_lock_irqsave(&irq->irq_lock, flags);
goto retry;
}
- /*
- * Grab a reference to the irq to reflect the fact that it is
- * now in the ap_list.
- */
- vgic_get_irq_kref(irq);
- list_add_tail(&irq->ap_list, &vcpu->arch.vgic_cpu.ap_list_head);
- irq->vcpu = vcpu;
+ if (!vcpu && target_vcpu) {
+ /*
+ * Insert this new pending interrupt.
+ * Grab a reference to the irq to reflect the fact that
+ * it is now in the ap_list.
+ */
+ vgic_get_irq_kref(irq);
+ list_add_tail(&irq->ap_list,
+ &target_vcpu->arch.vgic_cpu.ap_list_head);
+ irq->vcpu = target_vcpu;
+ ret = true;
+ } else if (vcpu && !target_vcpu) {
+ /*
+ * This IRQ is not pending or enabled but on the ap_list,
+ * drop it from the ap_list.
+ */
+ list_del(&irq->ap_list);
+ irq->vcpu = NULL;
+ raw_spin_unlock(&irq->irq_lock);
+ vgic_put_irq(vcpu->kvm, irq);
+ raw_spin_unlock_irqrestore(&vcpu->arch.vgic_cpu.ap_list_lock,
+ flags);
+ goto out;
+ } else {
+ /* This IRQ looks like it has to be migrated. */
+ list_del(&irq->ap_list);
+ list_add_tail(&irq->ap_list,
+ &target_vcpu->arch.vgic_cpu.ap_list_head);
+ irq->vcpu = target_vcpu;
+ }
raw_spin_unlock(&irq->irq_lock);
- raw_spin_unlock_irqrestore(&vcpu->arch.vgic_cpu.ap_list_lock, flags);
+ if (target_vcpu)
+ raw_spin_unlock_irqrestore(&target_vcpu->arch.vgic_cpu.ap_list_lock,
+ flags);
+ if (vcpu)
+ raw_spin_unlock_irqrestore(&vcpu->arch.vgic_cpu.ap_list_lock, flags);
- kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
- kvm_vcpu_kick(vcpu);
+out:
+ /*
+ * Even for the already queuing rightly case we have
+ * to kick the VCPU, because we could be queueing an
+ * edge-triggered interrupt for which we get no EOI
+ * maintenance interrupt. In that case, while the IRQ
+ * is already on the VCPU's AP list, the VCPU could
+ * have EOI'ed the original interrupt and won't see
+ * this one until it exits for some other reason.
+ */
+ if (target_vcpu) {
+ kvm_make_request(KVM_REQ_IRQ_PENDING, target_vcpu);
+ kvm_vcpu_kick(target_vcpu);
+ }
- return true;
+ return ret;
}
/**
--
2.19.1