[tip: irq/drivers] irqchip/al-fic: Support per-child affinity via interrupt redirection

From: tip-bot2 for Eliav Farber

Date: Thu Oct 15 2026 - 08:37:21 EST


The following commit has been merged into the irq/drivers branch of tip:

Commit-ID: 9376df3496753563e3d7f7cb5d91525f9a6e9497
Gitweb: https://git.kernel.org/tip/9376df3496753563e3d7f7cb5d91525f9a6e9497
Author: Eliav Farber <farbere@xxxxxxxxxx>
AuthorDate: Sun, 11 Oct 2026 05:04:48
Committer: Thomas Gleixner <tglx@xxxxxxxxxx>
CommitterDate: Thu, 15 Oct 2026 14:25:07 +02:00

irqchip/al-fic: Support per-child affinity via interrupt redirection

The FIC multiplexes up to 32 sources into one parent GIC SPI, and a
child's handler runs in the parent's context, so a child has no affinity
control of its own. irq_set_affinity() therefore fails with -EINVAL, and
drivers that treat that as fatal cannot probe when their interrupt
arrives through a FIC group. The parent routing cannot be changed on
behalf of one child either, because several FIC groups may share one SPI
and all of them would move with it.

Use the interrupt redirection infrastructure. Dispatch children with
generic_handle_demux_domain_irq() and set irq_set_affinity to
irq_chip_redirect_set_affinity(), so a child whose affinity excludes the
CPU taking the parent interrupt runs on its target CPU in IRQ work
context. The interrupt is still taken on the parent's CPU; only the
child's handler body moves.

Redirection defers the whole flow handler, so the cause bit is cleared in
al_fic_irq_pre_redirect(), which runs in parent context on both the
inline and the redirected path. A level configured group is masked there
too, because its cause bit re-latches while the input stays asserted; the
deferred handle_level_irq() masks again and unmasks when the handler is
done. An edge configured group is only acked, since handle_edge_irq() has
no unconditional unmask. irq_ack is a noop because irq_pre_redirect runs
on every dispatch and the ack must not happen twice.

Signed-off-by: Eliav Farber <farbere@xxxxxxxxxx>
Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxx>
Reviewed-by: Radu Rendec <radu@xxxxxxxxxx>
Link: https://patch.msgid.link/20261011050449.41903-10-farbere@xxxxxxxxxx
---
drivers/irqchip/irq-al-fic.c | 49 ++++++++++++++++++++++++++++++++---
1 file changed, 46 insertions(+), 3 deletions(-)

diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c
index eb74c52..e6068e0 100644
--- a/drivers/irqchip/irq-al-fic.c
+++ b/drivers/irqchip/irq-al-fic.c
@@ -140,8 +140,15 @@ static irqreturn_t al_fic_irq_handler(int irq, void *data)
pending = readl_relaxed(fic->base + AL_FIC_CAUSE);
pending &= ~gc->mask_cache;

- for_each_set_bit(hwirq, &pending, NR_FIC_IRQS)
- generic_handle_domain_irq(domain, hwirq);
+ for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) {
+ /*
+ * Redirect a child whose affinity excludes this CPU to its
+ * target CPU via irq_work. The ack moves to
+ * al_fic_irq_pre_redirect(), because the whole flow handler is
+ * deferred when that happens.
+ */
+ generic_handle_demux_domain_irq(domain, hwirq);
+ }

return pending ? IRQ_HANDLED : IRQ_NONE;
}
@@ -156,6 +163,32 @@ static int al_fic_irq_retrigger(struct irq_data *data)
return 1;
}

+#ifdef CONFIG_SMP
+/* The ack happens in al_fic_irq_pre_redirect() on both paths instead. */
+static void al_fic_ack_noop(struct irq_data *data) { }
+
+/*
+ * Runs in the parent interrupt context on every dispatch, redirected or not.
+ * Clear the cause bit here: once the flow handler is deferred, the FIC keeps
+ * driving its output until the irq_work runs, and a level parent SPI re-fires.
+ *
+ * A level configured group is masked too, because its cause bit re-latches
+ * while the input stays asserted; the deferred handle_level_irq() unmasks
+ * when the handler is done. An edge configured group is only acked, since
+ * handle_edge_irq() never unmasks unconditionally and the mask would stay.
+ */
+static void al_fic_irq_pre_redirect(struct irq_data *data)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
+ struct al_fic *fic = gc->private;
+
+ if (fic->state == AL_FIC_CONFIGURED_LEVEL)
+ irq_gc_mask_set_bit(data);
+
+ irq_gc_ack_clr_bit(data);
+}
+#endif /* CONFIG_SMP */
+
static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al_fic_id fic_id)
{
struct irq_chip_generic *gc;
@@ -188,9 +221,19 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al
gc->chip_types->regs.ack = AL_FIC_CAUSE;
gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit;
gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit;
- gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
gc->chip_types->chip.irq_set_type = al_fic_irq_set_type;
gc->chip_types->chip.irq_retrigger = al_fic_irq_retrigger;
+#ifdef CONFIG_SMP
+ /*
+ * The FIC has no hardware affinity control, so give each child real
+ * affinity by redirecting its handler to the target CPU.
+ */
+ gc->chip_types->chip.irq_ack = al_fic_ack_noop;
+ gc->chip_types->chip.irq_pre_redirect = al_fic_irq_pre_redirect;
+ gc->chip_types->chip.irq_set_affinity = irq_chip_redirect_set_affinity;
+#else
+ gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
+#endif
gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE;
gc->private = fic;