[PATCH v2 4/5] dmaengine: ti: omap-dma: fix interrupt handling in remove
From: Rosen Penev
Date: Sun May 31 2026 - 20:40:03 EST
The remove path had several pre-existing bugs:
1. Interrupts are enabled via IRQENABLE_L1 in probe and alloc_chan_resources,
but the remove path writes to IRQENABLE_L0, which has no effect on the L1
interrupt line. The DMA engine can continue asserting its IRQ during
removal. Write to IRQENABLE_L1 instead.
2. devm_free_irq() was called before disabling hardware interrupts. With
IRQF_SHARED, the hardware may still assert the IRQ line after the handler
is freed, causing unhandled interrupts that can lead to the kernel
permanently disabling the shared IRQ line. Disable interrupts first.
3. platform_get_irq() return value was not checked before devm_free_irq().
If it returns an error code (<= 0), passing it to devm_free_irq() is
incorrect. Add a guard.
4. Clearing od->irq_enable_mask and writing to IRQENABLE_L1 raced with the
interrupt handler, which reads irq_enable_mask under the spinlock.
Hold irq_lock around the disable.
5. The posted write to IRQENABLE_L1 used _relaxed accessors with no
readback to drain the write buffer. Add a readback flush before
devm_free_irq() to ensure the hardware has actually disabled the
interrupt line.
6. omap_dma_free() unconditionally freed all channel memory without
checking whether clients still held references. A sysfs unbind of the
DMA controller does not synchronously unbind consumers, so active
clients could access freed channel memory. Skip freeing channels
that still have active clients.
Fixes: 2e1136acf8a8 ("dmaengine: omap-dma: fix dma_pool resource leak in error paths")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Opencode:BigPickle
Signed-off-by: Rosen Penev <rosenp@xxxxxxxxx>
---
drivers/dma/ti/omap-dma.c | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/drivers/dma/ti/omap-dma.c b/drivers/dma/ti/omap-dma.c
index dde270646bb9..8c32b7ab50f6 100644
--- a/drivers/dma/ti/omap-dma.c
+++ b/drivers/dma/ti/omap-dma.c
@@ -1516,13 +1516,21 @@ static int omap_dma_chan_init(struct omap_dmadev *od)
static void omap_dma_free(struct omap_dmadev *od)
{
+ struct omap_chan *c;
+
while (!list_empty(&od->ddev.channels)) {
- struct omap_chan *c = list_first_entry(&od->ddev.channels,
- struct omap_chan, vc.chan.device_node);
+ c = list_first_entry(&od->ddev.channels,
+ struct omap_chan, vc.chan.device_node);
list_del(&c->vc.chan.device_node);
tasklet_kill(&c->vc.task);
vchan_free_chan_resources(&c->vc);
+ if (c->vc.chan.client_count) {
+ dev_warn(od->ddev.dev,
+ "chan%d freed with %u client(s)\n",
+ c->dma_ch, c->vc.chan.client_count);
+ continue;
+ }
kfree(c);
}
}
@@ -1870,16 +1878,20 @@ static void omap_dma_remove(struct platform_device *pdev)
if (pdev->dev.of_node)
of_dma_controller_free(pdev->dev.of_node);
- irq = platform_get_irq(pdev, 1);
- devm_free_irq(&pdev->dev, irq, od);
-
dma_async_device_unregister(&od->ddev);
if (!omap_dma_legacy(od)) {
- /* Disable all interrupts */
- omap_dma_glbl_write(od, IRQENABLE_L0, 0);
+ spin_lock_irq(&od->irq_lock);
+ od->irq_enable_mask = 0;
+ omap_dma_glbl_write(od, IRQENABLE_L1, 0);
+ spin_unlock_irq(&od->irq_lock);
+ omap_dma_glbl_read(od, IRQENABLE_L1);
}
+ irq = platform_get_irq(pdev, 1);
+ if (irq > 0)
+ devm_free_irq(&pdev->dev, irq, od);
+
omap_dma_free(od);
if (od->ll123_supported)
--
2.54.0