* [PATCH v2 1/3] irqchip/gic-v5: Move LPI allocation into the LPI domain
2026-05-06 9:36 [PATCH v2 0/3] irqchip/gic-v5: Tidy up LPI allocation Sascha Bischoff
@ 2026-05-06 9:37 ` Sascha Bischoff
2026-05-06 9:37 ` [PATCH v2 2/3] irqchip/gic-v5: Support range allocation for LPIs Sascha Bischoff
2026-05-06 9:37 ` [PATCH v2 3/3] irqchip/gic-v5: Allocate ITS parent LPIs as a range Sascha Bischoff
2 siblings, 0 replies; 4+ messages in thread
From: Sascha Bischoff @ 2026-05-06 9:37 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Cc: nd, Lorenzo Pieralisi, Marc Zyngier, Thomas Gleixner
The IPI and ITS MSI domains currently allocate and release LPIs
directly, then pass the selected LPI ID to the parent LPI domain. This
leaks the LPI domain's allocation policy into its child domains and
forces each child to duplicate part of the parent domain's teardown.
Make the LPI domain allocate LPIs in its .alloc() callback and release
them in a matching .free() callback. Child domains can then request a
parent interrupt without passing an implementation-specific LPI ID,
and the LPI lifetime is tied to the domain that owns the LPI
namespace.
Remove the gicv5_alloc_lpi() and gicv5_free_lpi() wrappers now that no
external caller needs to manage LPIs directly.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Marc Zyngier <maz@kernel.org>
Reviewed-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
---
drivers/irqchip/irq-gic-v5-its.c | 14 ++------
drivers/irqchip/irq-gic-v5.c | 53 +++++++++++++++---------------
include/linux/irqchip/arm-gic-v5.h | 3 --
3 files changed, 28 insertions(+), 42 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
index 36a8d1368f0e4..36d03f82ef684 100644
--- a/drivers/irqchip/irq-gic-v5-its.c
+++ b/drivers/irqchip/irq-gic-v5-its.c
@@ -929,8 +929,8 @@ static void gicv5_its_free_eventid(struct gicv5_its_dev *its_dev, u32 event_id_b
static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
- u32 device_id, event_id_base, lpi;
struct gicv5_its_dev *its_dev;
+ u32 device_id, event_id_base;
msi_alloc_info_t *info = arg;
irq_hw_number_t hwirq;
struct irq_data *irqd;
@@ -949,16 +949,8 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi
device_id = its_dev->device_id;
for (i = 0; i < nr_irqs; i++) {
- ret = gicv5_alloc_lpi();
- if (ret < 0) {
- pr_debug("Failed to find free LPI!\n");
- goto out_free_irqs;
- }
- lpi = ret;
-
- ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
+ ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, NULL);
if (ret) {
- gicv5_free_lpi(lpi);
goto out_free_irqs;
}
@@ -983,7 +975,6 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi
out_free_irqs:
while (--i >= 0) {
irqd = irq_domain_get_irq_data(domain, virq + i);
- gicv5_free_lpi(irqd->parent_data->hwirq);
irq_domain_reset_irq_data(irqd);
irq_domain_free_irqs_parent(domain, virq + i, 1);
}
@@ -1013,7 +1004,6 @@ static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int vi
for (i = 0; i < nr_irqs; i++) {
d = irq_domain_get_irq_data(domain, virq + i);
- gicv5_free_lpi(d->parent_data->hwirq);
irq_domain_reset_irq_data(d);
irq_domain_free_irqs_parent(domain, virq + i, 1);
}
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index 6b0903be8ebfd..15a2a04398d25 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -59,16 +59,6 @@ static void release_lpi(u32 lpi)
ida_free(&lpi_ida, lpi);
}
-int gicv5_alloc_lpi(void)
-{
- return alloc_lpi();
-}
-
-void gicv5_free_lpi(u32 lpi)
-{
- release_lpi(lpi);
-}
-
static void gicv5_ppi_priority_init(void)
{
write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR0_EL1);
@@ -806,18 +796,36 @@ static void gicv5_lpi_config_reset(struct irq_data *d)
gicv5_lpi_irq_write_pending_state(d, false);
}
+static void gicv5_irq_lpi_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return;
+
+ d = irq_domain_get_irq_data(domain, virq);
+
+ release_lpi(d->hwirq);
+
+ irq_set_handler(virq, NULL);
+ irq_domain_reset_irq_data(d);
+}
+
static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *arg)
{
irq_hw_number_t hwirq;
struct irq_data *irqd;
- u32 *lpi = arg;
int ret;
if (WARN_ON_ONCE(nr_irqs != 1))
return -EINVAL;
- hwirq = *lpi;
+ ret = alloc_lpi();
+ if (ret < 0)
+ return ret;
+ hwirq = ret;
irqd = irq_domain_get_irq_data(domain, virq);
@@ -826,8 +834,10 @@ static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int vi
irqd_set_single_target(irqd);
ret = gicv5_irs_iste_alloc(hwirq);
- if (ret < 0)
+ if (ret < 0) {
+ release_lpi(hwirq);
return ret;
+ }
gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI);
gicv5_lpi_config_reset(irqd);
@@ -837,7 +847,7 @@ static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int vi
static const struct irq_domain_ops gicv5_irq_lpi_domain_ops = {
.alloc = gicv5_irq_lpi_domain_alloc,
- .free = gicv5_irq_domain_free,
+ .free = gicv5_irq_lpi_domain_free,
};
void __init gicv5_init_lpi_domain(void)
@@ -859,21 +869,12 @@ static int gicv5_irq_ipi_domain_alloc(struct irq_domain *domain, unsigned int vi
{
struct irq_data *irqd;
int ret, i;
- u32 lpi;
for (i = 0; i < nr_irqs; i++) {
- ret = gicv5_alloc_lpi();
- if (ret < 0)
+ ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, NULL);
+ if (ret)
return ret;
- lpi = ret;
-
- ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
- if (ret) {
- gicv5_free_lpi(lpi);
- return ret;
- }
-
irqd = irq_domain_get_irq_data(domain, virq + i);
irq_domain_set_hwirq_and_chip(domain, virq + i, i,
@@ -899,8 +900,6 @@ static void gicv5_irq_ipi_domain_free(struct irq_domain *domain, unsigned int vi
if (!d)
return;
- gicv5_free_lpi(d->parent_data->hwirq);
-
irq_set_handler(virq + i, NULL);
irq_domain_reset_irq_data(d);
irq_domain_free_irqs_parent(domain, virq + i, 1);
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 40d2fce682940..f78787e654f4c 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -425,9 +425,6 @@ struct gicv5_its_itt_cfg {
void gicv5_init_lpis(u32 max);
void gicv5_deinit_lpis(void);
-int gicv5_alloc_lpi(void);
-void gicv5_free_lpi(u32 lpi);
-
void __init gicv5_its_of_probe(struct device_node *parent);
void __init gicv5_its_acpi_probe(void);
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v2 2/3] irqchip/gic-v5: Support range allocation for LPIs
2026-05-06 9:36 [PATCH v2 0/3] irqchip/gic-v5: Tidy up LPI allocation Sascha Bischoff
2026-05-06 9:37 ` [PATCH v2 1/3] irqchip/gic-v5: Move LPI allocation into the LPI domain Sascha Bischoff
@ 2026-05-06 9:37 ` Sascha Bischoff
2026-05-06 9:37 ` [PATCH v2 3/3] irqchip/gic-v5: Allocate ITS parent LPIs as a range Sascha Bischoff
2 siblings, 0 replies; 4+ messages in thread
From: Sascha Bischoff @ 2026-05-06 9:37 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Cc: nd, Lorenzo Pieralisi, Marc Zyngier, Thomas Gleixner
The GICv5 LPI domain now owns LPI allocation and teardown internally,
but its irq_domain callbacks still reject requests where nr_irqs is
greater than one. This forces child domains to allocate and free LPIs
one at a time even when the interrupt core requests a contiguous
range.
Handle multi-interrupt allocation and teardown in the LPI domain by
iterating over the requested range and unwinding any partially
allocated state on failure.
Allocate the parent LPIs for the IPI domain with a single range
request as well. The previous per-IPI parent allocation loop returned
immediately on failure and leaked any parent IRQs allocated by earlier
iterations.
Fixes: 0f0101325876 ("irqchip/gic-v5: Add GICv5 LPI/IPI support")
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Marc Zyngier <maz@kernel.org>
Reviewed-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
---
drivers/irqchip/irq-gic-v5.c | 77 ++++++++++++++++++++----------------
1 file changed, 42 insertions(+), 35 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index 15a2a04398d25..c1af07083ceff 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -801,15 +801,14 @@ static void gicv5_irq_lpi_domain_free(struct irq_domain *domain, unsigned int vi
{
struct irq_data *d;
- if (WARN_ON_ONCE(nr_irqs != 1))
- return;
-
- d = irq_domain_get_irq_data(domain, virq);
+ for (unsigned int i = 0; i < nr_irqs; i++, virq++) {
+ d = irq_domain_get_irq_data(domain, virq);
- release_lpi(d->hwirq);
+ release_lpi(d->hwirq);
- irq_set_handler(virq, NULL);
- irq_domain_reset_irq_data(d);
+ irq_set_handler(virq, NULL);
+ irq_domain_reset_irq_data(d);
+ }
}
static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int virq,
@@ -817,32 +816,39 @@ static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int vi
{
irq_hw_number_t hwirq;
struct irq_data *irqd;
+ unsigned int i;
int ret;
- if (WARN_ON_ONCE(nr_irqs != 1))
- return -EINVAL;
-
- ret = alloc_lpi();
- if (ret < 0)
- return ret;
- hwirq = ret;
+ for (i = 0; i < nr_irqs; i++) {
+ ret = alloc_lpi();
+ if (ret < 0)
+ goto out_free_lpis;
+ hwirq = ret;
+
+ ret = gicv5_irs_iste_alloc(hwirq);
+ if (ret < 0) {
+ /* Undo partial state first, then clean up the rest */
+ release_lpi(hwirq);
+ goto out_free_lpis;
+ }
- irqd = irq_domain_get_irq_data(domain, virq);
+ irqd = irq_domain_get_irq_data(domain, virq + i);
- irq_domain_set_info(domain, virq, hwirq, &gicv5_lpi_irq_chip, NULL,
- handle_fasteoi_irq, NULL, NULL);
- irqd_set_single_target(irqd);
+ irq_domain_set_info(domain, virq + i, hwirq, &gicv5_lpi_irq_chip,
+ NULL, handle_fasteoi_irq, NULL, NULL);
+ irqd_set_single_target(irqd);
- ret = gicv5_irs_iste_alloc(hwirq);
- if (ret < 0) {
- release_lpi(hwirq);
- return ret;
+ gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI);
+ gicv5_lpi_config_reset(irqd);
}
- gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI);
- gicv5_lpi_config_reset(irqd);
-
return 0;
+
+out_free_lpis:
+ if (i)
+ gicv5_irq_lpi_domain_free(domain, virq, i);
+
+ return ret;
}
static const struct irq_domain_ops gicv5_irq_lpi_domain_ops = {
@@ -868,21 +874,21 @@ static int gicv5_irq_ipi_domain_alloc(struct irq_domain *domain, unsigned int vi
unsigned int nr_irqs, void *arg)
{
struct irq_data *irqd;
- int ret, i;
+ int ret;
- for (i = 0; i < nr_irqs; i++) {
- ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, NULL);
- if (ret)
- return ret;
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
+ if (ret)
+ return ret;
- irqd = irq_domain_get_irq_data(domain, virq + i);
+ for (unsigned int i = 0; i < nr_irqs; i++, virq++) {
+ irqd = irq_domain_get_irq_data(domain, virq);
- irq_domain_set_hwirq_and_chip(domain, virq + i, i,
- &gicv5_ipi_irq_chip, NULL);
+ irq_domain_set_hwirq_and_chip(domain, virq, i,
+ &gicv5_ipi_irq_chip, NULL);
irqd_set_single_target(irqd);
- irq_set_handler(virq + i, handle_percpu_irq);
+ irq_set_handler(virq, handle_percpu_irq);
}
return 0;
@@ -902,8 +908,9 @@ static void gicv5_irq_ipi_domain_free(struct irq_domain *domain, unsigned int vi
irq_set_handler(virq + i, NULL);
irq_domain_reset_irq_data(d);
- irq_domain_free_irqs_parent(domain, virq + i, 1);
}
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
}
static const struct irq_domain_ops gicv5_irq_ipi_domain_ops = {
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v2 3/3] irqchip/gic-v5: Allocate ITS parent LPIs as a range
2026-05-06 9:36 [PATCH v2 0/3] irqchip/gic-v5: Tidy up LPI allocation Sascha Bischoff
2026-05-06 9:37 ` [PATCH v2 1/3] irqchip/gic-v5: Move LPI allocation into the LPI domain Sascha Bischoff
2026-05-06 9:37 ` [PATCH v2 2/3] irqchip/gic-v5: Support range allocation for LPIs Sascha Bischoff
@ 2026-05-06 9:37 ` Sascha Bischoff
2 siblings, 0 replies; 4+ messages in thread
From: Sascha Bischoff @ 2026-05-06 9:37 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Cc: nd, Lorenzo Pieralisi, Marc Zyngier, Thomas Gleixner
The ITS MSI domain no longer manages LPI allocation directly. LPIs are
allocated and freed by the parent LPI domain, which can now handle a
full range of interrupts and unwind partial allocations internally.
Make the ITS domain request and release the parent IRQs as a single
range instead of iterating over each interrupt. The ITS allocation
path then only needs to reserve EventIDs, allocate the parent range,
and fill in the ITS irq_data for each MSI. Since no operation in the
per-MSI loop can fail, the partial parent-free unwind becomes
unnecessary.
On teardown, reset the ITS irq_data for the range and then release the
parent range in one call, leaving LPI teardown to the LPI domain.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Reviewed-by: Marc Zyngier <maz@kernel.org>
Reviewed-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
---
drivers/irqchip/irq-gic-v5-its.c | 22 +++++++---------------
1 file changed, 7 insertions(+), 15 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
index 36d03f82ef684..28e39b065de0e 100644
--- a/drivers/irqchip/irq-gic-v5-its.c
+++ b/drivers/irqchip/irq-gic-v5-its.c
@@ -937,6 +937,7 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi
int ret, i;
its_dev = info->scratchpad[0].ptr;
+ device_id = its_dev->device_id;
ret = gicv5_its_alloc_eventid(its_dev, info, nr_irqs, &event_id_base);
if (ret)
@@ -946,14 +947,11 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi
if (ret)
goto out_eventid;
- device_id = its_dev->device_id;
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, NULL);
+ if (ret)
+ goto out_eventid;
for (i = 0; i < nr_irqs; i++) {
- ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, NULL);
- if (ret) {
- goto out_free_irqs;
- }
-
/*
* Store eventid and deviceid into the hwirq for later use.
*
@@ -972,12 +970,6 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi
return 0;
-out_free_irqs:
- while (--i >= 0) {
- irqd = irq_domain_get_irq_data(domain, virq + i);
- irq_domain_reset_irq_data(irqd);
- irq_domain_free_irqs_parent(domain, virq + i, 1);
- }
out_eventid:
gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs);
return ret;
@@ -1000,14 +992,14 @@ static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int vi
bitmap_release_region(its_dev->event_map, event_id_base,
get_count_order(nr_irqs));
- /* Hierarchically free irq data */
for (i = 0; i < nr_irqs; i++) {
d = irq_domain_get_irq_data(domain, virq + i);
-
irq_domain_reset_irq_data(d);
- irq_domain_free_irqs_parent(domain, virq + i, 1);
}
+ /* Hierarchically free irq data */
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+
gicv5_its_syncr(its, its_dev);
gicv5_irs_syncr();
}
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread