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