Commit eb6f6d52 authored by Sascha Bischoff's avatar Sascha Bischoff Committed by Thomas Gleixner
Browse files

irqchip/gic-v5: Support range allocation for LPIs



The per-IPI parent allocation loop returns immediately on failure and leaks
any parent interrupts allocated by earlier iterations.

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, which cures the leakage problem.

Fixes: 0f010132 ("irqchip/gic-v5: Add GICv5 LPI/IPI support")
Signed-off-by: default avatarSascha Bischoff <sascha.bischoff@arm.com>
Signed-off-by: default avatarThomas Gleixner <tglx@kernel.org>
Reviewed-by: default avatarMarc Zyngier <maz@kernel.org>
Reviewed-by: default avatarLorenzo Pieralisi <lpieralisi@kernel.org>
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260506093634.382062-3-sascha.bischoff@arm.com
parent dec85d2f
Loading
Loading
Loading
Loading
+42 −35
Original line number Diff line number Diff line
@@ -801,9 +801,7 @@ 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;

	for (unsigned int i = 0; i < nr_irqs; i++, virq++) {
		d = irq_domain_get_irq_data(domain, virq);

		release_lpi(d->hwirq);
@@ -811,38 +809,46 @@ static void gicv5_irq_lpi_domain_free(struct irq_domain *domain, unsigned int vi
		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;
	unsigned int i;
	int ret;

	if (WARN_ON_ONCE(nr_irqs != 1))
		return -EINVAL;

	for (i = 0; i < nr_irqs; i++) {
		ret = alloc_lpi();
		if (ret < 0)
		return ret;
			goto out_free_lpis;
		hwirq = ret;

	irqd = irq_domain_get_irq_data(domain, virq);

	irq_domain_set_info(domain, virq, 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) {
			/* Undo partial state first, then clean up the rest */
			release_lpi(hwirq);
		return ret;
			goto out_free_lpis;
		}

		irqd = irq_domain_get_irq_data(domain, virq + i);

		irq_domain_set_info(domain, virq + i, hwirq, &gicv5_lpi_irq_chip,
				    NULL, handle_fasteoi_irq, NULL, NULL);
		irqd_set_single_target(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);
	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,
		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 = {