[tip: irq/drivers] irqchip/ath79-cpu: Flush every write buffer of a shared interrupt

From: tip-bot2 for Rosen Penev

Date: Wed Nov 11 2026 - 15:23:07 EST


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

Commit-ID: 4e1d12a78596b4201303a953c229bd40fb506a15
Gitweb: https://git.kernel.org/tip/4e1d12a78596b4201303a953c229bd40fb506a15
Author: Rosen Penev <rosenp@xxxxxxxxx>
AuthorDate: Sun, 08 Nov 2026 13:57:37 -08:00
Committer: Thomas Gleixner <tglx@xxxxxxxxxx>
CommitterDate: Wed, 11 Nov 2026 00:56:19 +01:00

irqchip/ath79-cpu: Flush every write buffer of a shared interrupt

On AR934x and QCA953x, the WMAC and the PCIe root complex share CPU
interrupt 2, but each writes to DDR through its own write buffer, and
both have to be flushed before their handlers can see the data. The
driver keeps one write buffer per interrupt, so the last one listed in
the device tree wins.

Keep a mask of write buffers per interrupt instead, and flush all of
them. A flush is a few accesses to the DDR controller, which costs much
less than splitting the interrupt by source, as OpenWrt does with a
cascaded interrupt controller.

Document the interrupt wiring, the write buffers and why a shared line
flushes all of them.

Signed-off-by: Rosen Penev <rosenp@xxxxxxxxx>
Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxx>
Link: https://patch.msgid.link/20261108215738.113582-3-rosenp@xxxxxxxxx
---
drivers/irqchip/irq-ath79-cpu.c | 51 +++++++++++++++++++++-----------
1 file changed, 34 insertions(+), 17 deletions(-)

diff --git a/drivers/irqchip/irq-ath79-cpu.c b/drivers/irqchip/irq-ath79-cpu.c
index 9b7273a..ec39c37 100644
--- a/drivers/irqchip/irq-ath79-cpu.c
+++ b/drivers/irqchip/irq-ath79-cpu.c
@@ -10,6 +10,7 @@
* Parts of this file are based on Atheros' 2.6.15/2.6.31 BSP
*/

+#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqchip.h>
#include <linux/of.h>
@@ -18,23 +19,33 @@
#include <asm/mach-ath79/ath79.h>

/*
- * The IP2/IP3 lines are tied to a PCI/WMAC/USB device. Drivers for
- * these devices typically allocate coherent DMA memory, however the
- * DMA controller may still have some unsynchronized data in the FIFO.
- * Issue a flush in the handlers to ensure that the driver sees
- * the update.
+ * The MIPS core has 8 interrupt lines: IP0/IP1 are software interrupts,
+ * IP7 is the CP0 timer, and IP2-IP6 are wired by the SoC to its devices.
+ * The wiring is fixed in hardware. Several devices can share a line, like
+ * the WMAC and the PCIe root complex on IP2 of AR934x, and their drivers
+ * then request the interrupt with IRQF_SHARED.
*
- * This array map the interrupt lines to the DDR write buffer channels.
+ * Each DMA master writes to DDR through its own write buffer in the DDR
+ * controller, and each write buffer has its own flush register. When a
+ * device raises its interrupt, its last DMA writes may still sit in its
+ * write buffer, so the handler could read stale descriptors or data.
+ *
+ * The CPU interrupt says nothing about which device on a shared line
+ * raised it. So before running the handlers of a line, flush the write
+ * buffers of every device wired to it. Each handler then sees its
+ * device's data, checks its own status, and returns IRQ_NONE if it was
+ * not the source.
+ *
+ * irq_wb_chans maps each CPU interrupt line to the mask of the write
+ * buffer channels to flush, from the qca,ddr-wb-channel-interrupts and
+ * qca,ddr-wb-channels properties.
*/
-
-static unsigned irq_wb_chan[8] = {
- -1, -1, -1, -1, -1, -1, -1, -1,
-};
+static u32 irq_wb_chans[8];

asmlinkage void plat_irq_dispatch(void)
{
- unsigned long pending;
- int irq;
+ unsigned long pending, chans;
+ int irq, chan;

pending = read_c0_status() & read_c0_cause() & ST0_IM;

@@ -46,8 +57,11 @@ asmlinkage void plat_irq_dispatch(void)
pending >>= CAUSEB_IP;
while (pending) {
irq = fls(pending) - 1;
- if (irq < ARRAY_SIZE(irq_wb_chan) && irq_wb_chan[irq] != -1)
- ath79_ddr_wb_flush(irq_wb_chan[irq]);
+ if (irq < ARRAY_SIZE(irq_wb_chans)) {
+ chans = irq_wb_chans[irq];
+ for_each_set_bit(chan, &chans, 32)
+ ath79_ddr_wb_flush(chan);
+ }
do_IRQ(MIPS_CPU_IRQ_BASE + irq);
pending &= ~BIT(irq);
}
@@ -58,7 +72,7 @@ static int __init ar79_cpu_intc_of_init(
{
int err, i, count;

- /* Fill the irq_wb_chan table */
+ /* Fill the irq_wb_chans table */
count = of_count_phandle_with_args(
node, "qca,ddr-wb-channels", "#qca,ddr-wb-channel-cells");

@@ -68,7 +82,7 @@ static int __init ar79_cpu_intc_of_init(

of_property_read_u32_index(
node, "qca,ddr-wb-channel-interrupts", i, &irq);
- if (irq >= ARRAY_SIZE(irq_wb_chan))
+ if (irq >= ARRAY_SIZE(irq_wb_chans))
continue;

err = of_parse_phandle_with_args(
@@ -78,7 +92,10 @@ static int __init ar79_cpu_intc_of_init(
if (err)
return err;

- irq_wb_chan[irq] = args.args[0];
+ if (args.args[0] >= 32)
+ return -EINVAL;
+
+ irq_wb_chans[irq] |= BIT(args.args[0]);
}

return mips_cpu_irq_of_init(node, parent);