[RFC PATCH] KVM: arm/arm64: GICv4: Support shared VLPI

From: Kunkun Jiang
Date: Thu Nov 02 2023 - 10:35:23 EST


In some scenarios, the guest virtio-pci driver will request two MSI-X,
one vector for config, one shared for queues. However, the host driver
(vDPA or VFIO) will request a vector for each queue.

In the current implementation of GICv4/4.1 direct injection of vLPI,
pINTID and vINTID have one-to-one correspondence. Therefore, the
above scenario cannot be handled correctly. The host kernel will
execute its_map_vlpi multiple times but only execute its_unmap_vlpi
once. This may cause guest hang[1].

| WARN_ON(!(irq->hw && irq->host_irq == virq));
| if (irq->hw) {
| atomic_dec(&irq->target_vcpu->arch.vgic_cpu.vgic_v3.its_vpe.vlpi_count);
| irq->hw = false;
| ret = its_unmap_vlpi(virq);
| }

Add a list to struct vgic_irq to record all host irqs mapped to the vlpi.
When performing an action on the vlpi, traverse the list and perform this
action on all host irqs.

Link: https://lore.kernel.org/all/0d9fdf42-76b1-afc6-85a9-159c5490bbd4@xxxxxxxxxx/#t

Signed-off-by: Kunkun Jiang <jiangkunkun@xxxxxxxxxx>
---
arch/arm64/kvm/vgic/vgic-its.c | 74 ++++++++++++++++++++++++----------
arch/arm64/kvm/vgic/vgic-v4.c | 37 ++++++++++++++---
include/kvm/arm_vgic.h | 7 ++++
3 files changed, 91 insertions(+), 27 deletions(-)

diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
index 5fe2365a629f..c4b453155fcf 100644
--- a/arch/arm64/kvm/vgic/vgic-its.c
+++ b/arch/arm64/kvm/vgic/vgic-its.c
@@ -54,6 +54,7 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,

INIT_LIST_HEAD(&irq->lpi_list);
INIT_LIST_HEAD(&irq->ap_list);
+ INIT_LIST_HEAD(&irq->host_irq_head);
raw_spin_lock_init(&irq->irq_lock);

irq->config = VGIC_CONFIG_EDGE;
@@ -61,6 +62,7 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
irq->intid = intid;
irq->target_vcpu = vcpu;
irq->group = 1;
+ atomic_set(&irq->map_count, 0);

raw_spin_lock_irqsave(&dist->lpi_list_lock, flags);

@@ -284,6 +286,7 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,
u8 prop;
int ret;
unsigned long flags;
+ struct vlpi_host_irq *vlpi_hirq;

ret = kvm_read_guest_lock(kvm, propbase + irq->intid - GIC_LPI_OFFSET,
&prop, 1);
@@ -305,8 +308,13 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,

raw_spin_unlock_irqrestore(&irq->irq_lock, flags);

- if (irq->hw)
- return its_prop_update_vlpi(irq->host_irq, prop, needs_inv);
+ if (irq->hw) {
+ list_for_each_entry(vlpi_hirq, &irq->host_irq_head, host_irq_list) {
+ ret = its_prop_update_vlpi(vlpi_hirq->host_irq, prop, needs_inv);
+ if (ret)
+ return ret;
+ }
+ }

return 0;
}
@@ -354,25 +362,31 @@ int vgic_copy_lpi_list(struct kvm *kvm, struct kvm_vcpu *vcpu, u32 **intid_ptr)
static int update_affinity(struct vgic_irq *irq, struct kvm_vcpu *vcpu)
{
int ret = 0;
- unsigned long flags;
+ unsigned long flags, counts;
+ struct vlpi_host_irq *vlpi_hirq;

raw_spin_lock_irqsave(&irq->irq_lock, flags);
irq->target_vcpu = vcpu;
raw_spin_unlock_irqrestore(&irq->irq_lock, flags);

if (irq->hw) {
- struct its_vlpi_map map;
+ counts = atomic_read(&irq->map_count);
+ list_for_each_entry(vlpi_hirq, &irq->host_irq_head, host_irq_list) {
+ struct its_vlpi_map map;

- ret = its_get_vlpi(irq->host_irq, &map);
- if (ret)
- return ret;
+ ret = its_get_vlpi(vlpi_hirq->host_irq, &map);
+ if (ret)
+ return ret;

- if (map.vpe)
- atomic_dec(&map.vpe->vlpi_count);
- map.vpe = &vcpu->arch.vgic_cpu.vgic_v3.its_vpe;
- atomic_inc(&map.vpe->vlpi_count);
+ counts--;
+ if (map.vpe && !counts)
+ atomic_dec(&map.vpe->vlpi_count);
+ map.vpe = &vcpu->arch.vgic_cpu.vgic_v3.its_vpe;
+ if (!counts)
+ atomic_inc(&map.vpe->vlpi_count);

- ret = its_map_vlpi(irq->host_irq, &map);
+ ret = its_map_vlpi(vlpi_hirq->host_irq, &map);
+ }
}

return ret;
@@ -731,6 +745,7 @@ static int vgic_its_trigger_msi(struct kvm *kvm, struct vgic_its *its,
u32 devid, u32 eventid)
{
struct vgic_irq *irq = NULL;
+ struct vlpi_host_irq *vlpi_hirq;
unsigned long flags;
int err;

@@ -738,9 +753,15 @@ static int vgic_its_trigger_msi(struct kvm *kvm, struct vgic_its *its,
if (err)
return err;

- if (irq->hw)
- return irq_set_irqchip_state(irq->host_irq,
- IRQCHIP_STATE_PENDING, true);
+ if (irq->hw) {
+ list_for_each_entry(vlpi_hirq, &irq->host_irq_head, host_irq_list) {
+ err = irq_set_irqchip_state(vlpi_hirq->host_irq,
+ IRQCHIP_STATE_PENDING, true);
+ if (err)
+ return err;
+ return 0;
+ }
+ }

raw_spin_lock_irqsave(&irq->irq_lock, flags);
irq->pending_latch = true;
@@ -806,12 +827,17 @@ int vgic_its_inject_msi(struct kvm *kvm, struct kvm_msi *msi)
/* Requires the its_lock to be held. */
static void its_free_ite(struct kvm *kvm, struct its_ite *ite)
{
+ struct vlpi_host_irq *vlpi_hirq;
+
list_del(&ite->ite_list);

/* This put matches the get in vgic_add_lpi. */
if (ite->irq) {
- if (ite->irq->hw)
- WARN_ON(its_unmap_vlpi(ite->irq->host_irq));
+ if (ite->irq->hw) {
+ list_for_each_entry(vlpi_hirq, &ite->irq->host_irq_head, host_irq_list) {
+ WARN_ON(its_unmap_vlpi(vlpi_hirq->host_irq));
+ }
+ }

vgic_put_irq(kvm, ite->irq);
}
@@ -1290,7 +1316,8 @@ static int vgic_its_cmd_handle_clear(struct kvm *kvm, struct vgic_its *its,
u32 device_id = its_cmd_get_deviceid(its_cmd);
u32 event_id = its_cmd_get_id(its_cmd);
struct its_ite *ite;
-
+ struct vlpi_host_irq *vlpi_hirq;
+ int ret;

ite = find_ite(its, device_id, event_id);
if (!ite)
@@ -1298,9 +1325,14 @@ static int vgic_its_cmd_handle_clear(struct kvm *kvm, struct vgic_its *its,

ite->irq->pending_latch = false;

- if (ite->irq->hw)
- return irq_set_irqchip_state(ite->irq->host_irq,
- IRQCHIP_STATE_PENDING, false);
+ if (ite->irq->hw) {
+ list_for_each_entry(vlpi_hirq, &ite->irq->host_irq_head, host_irq_list) {
+ ret = irq_set_irqchip_state(vlpi_hirq->host_irq,
+ IRQCHIP_STATE_PENDING, false);
+ if (ret)
+ return ret;
+ }
+ }

return 0;
}
diff --git a/arch/arm64/kvm/vgic/vgic-v4.c b/arch/arm64/kvm/vgic/vgic-v4.c
index 339a55194b2c..d634ba3dd225 100644
--- a/arch/arm64/kvm/vgic/vgic-v4.c
+++ b/arch/arm64/kvm/vgic/vgic-v4.c
@@ -413,6 +413,7 @@ int kvm_vgic_v4_set_forwarding(struct kvm *kvm, int virq,
{
struct vgic_its *its;
struct vgic_irq *irq;
+ struct vlpi_host_irq *vlpi_hirq;
struct its_vlpi_map map;
unsigned long flags;
int ret;
@@ -456,9 +457,19 @@ int kvm_vgic_v4_set_forwarding(struct kvm *kvm, int virq,
if (ret)
goto out;

- irq->hw = true;
- irq->host_irq = virq;
- atomic_inc(&map.vpe->vlpi_count);
+ vlpi_hirq = kzalloc(sizeof(struct vlpi_host_irq), GFP_KERNEL_ACCOUNT);
+ if (!vlpi_hirq)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&vlpi_hirq->host_irq_list);
+ vlpi_hirq->host_irq = virq;
+ list_add_tail(&vlpi_hirq->host_irq_list, &irq->host_irq_head);
+
+ if (!atomic_read(&irq->map_count)) {
+ irq->hw = true;
+ atomic_inc(&map.vpe->vlpi_count);
+ }
+ atomic_inc(&irq->map_count);

/* Transfer pending state */
raw_spin_lock_irqsave(&irq->irq_lock, flags);
@@ -488,6 +499,8 @@ int kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int virq,
{
struct vgic_its *its;
struct vgic_irq *irq;
+ struct vlpi_host_irq *vlpi_hirq;
+ struct its_vpe *vpe;
int ret;

if (!vgic_supports_direct_msis(kvm))
@@ -508,10 +521,22 @@ int kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int virq,
if (ret)
goto out;

- WARN_ON(!(irq->hw && irq->host_irq == virq));
+ WARN_ON(!(irq->hw));
if (irq->hw) {
- atomic_dec(&irq->target_vcpu->arch.vgic_cpu.vgic_v3.its_vpe.vlpi_count);
- irq->hw = false;
+ list_for_each_entry(vlpi_hirq, &irq->host_irq_head, host_irq_list) {
+ if (vlpi_hirq->host_irq == virq) {
+ list_del(&vlpi_hirq->host_irq_list);
+ kfree(vlpi_hirq);
+ break;
+ }
+ }
+
+ atomic_dec(&irq->map_count);
+ if (!atomic_read(&irq->map_count)) {
+ vpe = &irq->target_vcpu->arch.vgic_cpu.vgic_v3.its_vpe;
+ atomic_dec(&vpe->vlpi_count);
+ irq->hw = false;
+ }
ret = its_unmap_vlpi(virq);
}

diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 5b27f94d4fad..2b8f25d1eff2 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -114,6 +114,11 @@ struct irq_ops {
bool (*get_input_level)(int vintid);
};

+struct vlpi_host_irq {
+ struct list_head host_irq_list;
+ unsigned int host_irq;
+};
+
struct vgic_irq {
raw_spinlock_t irq_lock; /* Protects the content of the struct */
struct list_head lpi_list; /* Used to link all LPIs together */
@@ -138,6 +143,8 @@ struct vgic_irq {
bool active; /* not used for LPIs */
bool enabled;
bool hw; /* Tied to HW IRQ */
+ atomic_t map_count; /* record vLPI map times */
+ struct list_head host_irq_head; /* record host irqs list of a vLPI */
struct kref refcount; /* Used for LPIs */
u32 hwintid; /* HW INTID number */
unsigned int host_irq; /* linux irq corresponding to hwintid */
--
2.33.0