From 885537fa80ed6349fc5ea0b0c7433f31241d1337 Mon Sep 17 00:00:00 2001 From: Bowen Pang Date: Thu, 16 Jul 2026 16:24:34 +0800 Subject: [PATCH] irqchip/sifive-plic: fix stuck in-progress IRQ on affinity migration community inclusion category: bugfix bugzilla: https://github.com/RVCK-Project/rvck-olk/issues/246 Reference: https://github.com/RVCK-Project/rvck-olk/pull/247 -------------------------------- The PLIC silently drops a completion whose source is not enabled for the target hart, and until it receives a completion it will not re-deliver the source, so the device stops receiving interrupts. During affinity migration plic_set_affinity() clears the old hart's enable bit; if that lands between claim and completion, the completion is dropped. For a direct PLIC IRQ, plic_irq_eoi() and plic_set_affinity() are both under desc->lock and cannot race, but a PLIC IRQ backing a chained irqchip issues the completion from chained_irq_exit() and reaches plic_set_affinity() via the chained chip's ->irq_set_affinity() callback, neither holding desc->lock. Take the per-context enable_lock (already held by plic_toggle() when clearing the enable bit) across plic_irq_eoi() so the affinity clear cannot overlap the EOI; test the enable bit inside the critical section and re-enable around the completion if it is clear. enable_lock nests inside desc->lock, so no new lock ordering is introduced. Signed-off-by: Bowen Pang --- drivers/irqchip/irq-sifive-plic.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 613cb4fbed90..537f93ca46e0 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -157,19 +157,25 @@ static void plic_irq_disable(struct irq_data *d) static void plic_irq_eoi(struct irq_data *d) { struct plic_handler *handler = this_cpu_ptr(&plic_handlers); - u32 __iomem *reg; - bool enabled; - - reg = handler->enable_base + (d->hwirq / 32) * sizeof(u32); - enabled = readl(reg) & BIT(d->hwirq % 32); + unsigned long flags; + void __iomem *reg = handler->enable_base + (d->hwirq / 32) * sizeof(u32); + u32 hwirq_mask = 1 << (d->hwirq % 32); - if (unlikely(!enabled)) { - plic_toggle(handler, d->hwirq, 1); + /* + * PLIC drops a completion unless the source is enabled for this hart; a + * dropped one leaves the interrupt stuck in-progress. Serialize against + * plic_set_affinity() clearing the enable bit by holding enable_lock + * across the EOI; re-enable around it if the bit was clear. + */ + raw_spin_lock_irqsave(&handler->enable_lock, flags); + if (unlikely(!(readl(reg) & hwirq_mask))) { + __plic_toggle(handler->enable_base, d->hwirq, 1); writel(d->hwirq, handler->hart_base + CONTEXT_CLAIM); - plic_toggle(handler, d->hwirq, 0); + __plic_toggle(handler->enable_base, d->hwirq, 0); } else { writel(d->hwirq, handler->hart_base + CONTEXT_CLAIM); } + raw_spin_unlock_irqrestore(&handler->enable_lock, flags); } #ifdef CONFIG_SMP