[PATCH v2 01/16] KVM: arm64: vgic: Split out common LPI allocation
Sascha Bischoff
sascha.bischoff at arm.com
Mon Nov 2 11:44:39 PST 2026
The GICv3 ITS code currently owns vgic_add_lpi(), even though the
first half of the helper is not ITS specific. It allocates or finds
the VM-wide vgic_irq object in lpi_xa and initialises the common LPI
state before the GICv3 ITS code reads the property table and pending
state.
GICv5 needs to reuse the same LPI object lifetime and refcounting, but
without reading the GICv3 property table. Move the allocation and
xarray insertion path into vgic_alloc_lpi() in vgic.c. Both
vgic_add_lpi() and the GICv3 ITS wrapper perform an initial lookup,
then call this helper on a miss. The ITS wrapper continues to load the
property and pending state, while GICv5 can use the common allocator
without GICv3 setup.
Keeping the lookup outside vgic_alloc_lpi() preserves GICv3 behaviour
when another caller inserts the LPI during allocation: the ITS wrapper
still performs its setup on the returned LPI. A second lookup inside
the allocator could instead treat it as already initialised and skip
that setup.
Initialise LPIs as Group 1 before publishing them in lpi_xa. GICv3
LPIs are Group 1, and another vCPU can find a new LPI as soon as it is
published. GICv5 does not use the group field.
This preserves the intended GICv3 behaviour while making the common
LPI storage usable by other VGIC users.
Signed-off-by: Sascha Bischoff <sascha.bischoff at arm.com>
---
arch/arm64/kvm/vgic/vgic-its.c | 75 ++++---------------------------
arch/arm64/kvm/vgic/vgic.c | 82 ++++++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 4 ++
3 files changed, 95 insertions(+), 66 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
index 7e9b265b1f521..34c1c89bfbfe1 100644
--- a/arch/arm64/kvm/vgic/vgic-its.c
+++ b/arch/arm64/kvm/vgic/vgic-its.c
@@ -66,76 +66,19 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq,
__ret; \
})
-/*
- * Creates a new (reference to a) struct vgic_irq for a given LPI.
- * If this LPI is already mapped on another ITS, we increase its refcount
- * and return a pointer to the existing structure.
- * If this is a "new" LPI, we allocate and initialize a new struct vgic_irq.
- * This function returns a pointer to the _unlocked_ structure.
- */
-static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
- struct kvm_vcpu *vcpu)
+static struct vgic_irq *vgic_its_add_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu)
{
- struct vgic_dist *dist = &kvm->arch.vgic;
- struct vgic_irq *irq = vgic_get_irq(kvm, intid), *oldirq;
- unsigned long flags;
+ struct vgic_irq *irq;
int ret;
- /* In this case there is no put, since we keep the reference. */
+ irq = vgic_get_irq(kvm, intid);
if (irq)
return irq;
- irq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);
- if (!irq)
- return ERR_PTR(-ENOMEM);
-
- ret = xa_reserve_irq(&dist->lpi_xa, intid, GFP_KERNEL_ACCOUNT);
- if (ret) {
- kfree(irq);
- return ERR_PTR(ret);
- }
-
- INIT_LIST_HEAD(&irq->ap_list);
- raw_spin_lock_init(&irq->irq_lock);
-
- irq->config = VGIC_CONFIG_EDGE;
- refcount_set(&irq->refcount, 1);
- irq->intid = intid;
- irq->target_vcpu = vcpu;
- irq->group = 1;
-
- xa_lock_irqsave(&dist->lpi_xa, flags);
-
- /*
- * There could be a race with another vgic_add_lpi(), so we need to
- * check that we don't add a second list entry with the same LPI.
- */
- oldirq = xa_load(&dist->lpi_xa, intid);
- if (vgic_try_get_irq_ref(oldirq)) {
- /* Someone was faster with adding this LPI, lets use that. */
- kfree(irq);
- irq = oldirq;
- } else {
- /*
- * The entry is either empty or contains a dead LPI (refcount=0)
- * from the deferred release path, pending cleanup by
- * vgic_release_deleted_lpis(). Evict and free it if present.
- */
- oldirq = __xa_store(&dist->lpi_xa, intid, irq,
- GFP_NOWAIT | __GFP_ACCOUNT);
- ret = xa_err(oldirq);
- if (ret) {
- xa_unlock_irqrestore(&dist->lpi_xa, flags);
- kfree(irq);
-
- return ERR_PTR(ret);
- }
-
- if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount)))
- kfree_rcu(oldirq, rcu);
- }
-
- xa_unlock_irqrestore(&dist->lpi_xa, flags);
+ irq = vgic_alloc_lpi(kvm, intid, vcpu);
+ if (IS_ERR(irq))
+ return irq;
/*
* We "cache" the configuration table entries in our struct vgic_irq's.
@@ -1133,7 +1076,7 @@ static int vgic_its_cmd_handle_mapi(struct kvm *kvm, struct vgic_its *its,
if (its_is_collection_mapped(collection))
vcpu = collection_to_vcpu(kvm, collection);
- irq = vgic_add_lpi(kvm, lpi_nr, vcpu);
+ irq = vgic_its_add_lpi(kvm, lpi_nr, vcpu);
if (!IS_ERR(irq) && !vgic_its_map_lpi(kvm, ite, irq, vcpu))
irq = ERR_PTR(-EEXIST);
if (IS_ERR(irq)) {
@@ -2269,7 +2212,7 @@ static int vgic_its_restore_ite(struct vgic_its *its, u32 event_id,
if (its_is_collection_mapped(collection))
vcpu = kvm_get_vcpu_by_id(kvm, collection->target_addr);
- irq = vgic_add_lpi(kvm, lpi_id, vcpu);
+ irq = vgic_its_add_lpi(kvm, lpi_id, vcpu);
if (IS_ERR(irq)) {
its_free_ite(kvm, ite);
return PTR_ERR(irq);
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index bea848c93a990..d1a1a2cea386e 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -79,6 +79,88 @@ static struct vgic_irq *vgic_get_lpi(struct kvm *kvm, u32 intid)
return irq;
}
+/* Allocate an LPI or acquire one inserted by a concurrent caller. */
+struct vgic_irq *vgic_alloc_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu)
+{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ struct vgic_irq *irq, *oldirq;
+ unsigned long flags;
+ int ret;
+
+ irq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);
+ if (!irq)
+ return ERR_PTR(-ENOMEM);
+
+ ret = xa_reserve_irq(&dist->lpi_xa, intid, GFP_KERNEL_ACCOUNT);
+ if (ret) {
+ kfree(irq);
+ return ERR_PTR(ret);
+ }
+
+ INIT_LIST_HEAD(&irq->ap_list);
+ raw_spin_lock_init(&irq->irq_lock);
+
+ irq->config = VGIC_CONFIG_EDGE;
+ refcount_set(&irq->refcount, 1);
+ irq->intid = intid;
+ irq->target_vcpu = vcpu;
+ irq->group = 1;
+
+ xa_lock_irqsave(&dist->lpi_xa, flags);
+
+ /*
+ * There could be a race with another vgic_add_lpi(), so we need to
+ * check that we don't add a second list entry with the same LPI.
+ */
+ oldirq = xa_load(&dist->lpi_xa, intid);
+ if (vgic_try_get_irq_ref(oldirq)) {
+ /* Someone was faster with adding this LPI, lets use that. */
+ kfree(irq);
+ irq = oldirq;
+ } else {
+ /*
+ * The entry is either empty or contains a dead LPI (refcount=0)
+ * from the deferred release path, pending cleanup by
+ * vgic_release_deleted_lpis(). Evict and free it if present.
+ */
+ oldirq = __xa_store(&dist->lpi_xa, intid, irq,
+ GFP_NOWAIT | __GFP_ACCOUNT);
+ ret = xa_err(oldirq);
+ if (ret) {
+ xa_unlock_irqrestore(&dist->lpi_xa, flags);
+ kfree(irq);
+
+ return ERR_PTR(ret);
+ }
+
+ if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount)))
+ kfree_rcu(oldirq, rcu);
+ }
+
+ xa_unlock_irqrestore(&dist->lpi_xa, flags);
+
+ return irq;
+}
+
+/*
+ * Creates a new (reference to a) struct vgic_irq for a given LPI.
+ * If this LPI already exists, we increase its refcount and return it.
+ * If this is a "new" LPI, we allocate and initialize a new struct vgic_irq.
+ * This function returns a pointer to the _unlocked_ structure.
+ */
+struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu)
+{
+ struct vgic_irq *irq = vgic_get_irq(kvm, intid);
+
+ /* In this case there is no put, since we keep the reference. */
+ if (irq)
+ return irq;
+
+ return vgic_alloc_lpi(kvm, intid, vcpu);
+}
+
/*
* This looks up the virtual interrupt ID to get the corresponding
* struct vgic_irq. It also increases the refcount, so any caller is expected
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 97fc3bdf520e5..8792953057bb7 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -280,6 +280,10 @@ vgic_get_mmio_region(struct kvm_vcpu *vcpu, struct vgic_io_device *iodev,
gpa_t addr, int len);
struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid);
struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid);
+struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu);
+struct vgic_irq *vgic_alloc_lpi(struct kvm *kvm, u32 intid,
+ struct kvm_vcpu *vcpu);
void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq);
struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq);
bool vgic_get_phys_line_level(struct vgic_irq *irq);
--
2.34.1
More information about the linux-arm-kernel
mailing list