]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
dmaengine: dw-edma: Add virtual IRQ for interrupt-emulation doorbells
authorKoichiro Den <den@valinux.co.jp>
Sun, 15 Feb 2026 15:22:16 +0000 (00:22 +0900)
committerVinod Koul <vkoul@kernel.org>
Wed, 25 Feb 2026 09:54:19 +0000 (15:24 +0530)
Interrupt emulation can assert the dw-edma IRQ line without updating the
DONE/ABORT bits. With the shared read/write/common IRQ handlers, the
driver cannot reliably distinguish such an emulated interrupt from a
real one and leaving a level IRQ asserted may wedge the line.

Allocate a dedicated, requestable Linux virtual IRQ (db_irq) for
interrupt emulation and attach an irq_chip whose .irq_ack runs the
core-specific deassert sequence (.ack_emulated_irq()). The physical
dw-edma interrupt handlers raise this virtual IRQ via
generic_handle_irq(), ensuring emulated IRQs are always deasserted.

Export the virtual IRQ number (db_irq) and the doorbell register offset
(db_offset) via struct dw_edma_chip so platform users can expose
interrupt emulation as a doorbell.

Without this, a single interrupt-emulation write can leave the level IRQ
line asserted and cause the generic IRQ layer to disable it.

Signed-off-by: Koichiro Den <den@valinux.co.jp>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://patch.msgid.link/20260215152216.3393561-3-den@valinux.co.jp
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/dma/dw-edma/dw-edma-core.c
include/linux/dma/edma.h

index e7d698b352d37186f0615d7073d4322e357a2f55..cd34a3ea602d2f7cf2e2f8c90d552fd404188bdb 100644 (file)
@@ -663,7 +663,96 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
        chan->status = EDMA_ST_IDLE;
 }
 
-static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
+static void dw_edma_emul_irq_ack(struct irq_data *d)
+{
+       struct dw_edma *dw = irq_data_get_irq_chip_data(d);
+
+       dw_edma_core_ack_emulated_irq(dw);
+}
+
+/*
+ * irq_chip implementation for interrupt-emulation doorbells.
+ *
+ * The emulated source has no mask/unmask mechanism. With handle_level_irq(),
+ * the flow is therefore:
+ *   1) .irq_ack() deasserts the source
+ *   2) registered handlers (if any) are dispatched
+ * Since deassertion is already done in .irq_ack(), handlers do not need to take
+ * care of it, hence IRQCHIP_ONESHOT_SAFE.
+ */
+static struct irq_chip dw_edma_emul_irqchip = {
+       .name           = "dw-edma-emul",
+       .irq_ack        = dw_edma_emul_irq_ack,
+       .flags          = IRQCHIP_ONESHOT_SAFE | IRQCHIP_SKIP_SET_WAKE,
+};
+
+static int dw_edma_emul_irq_alloc(struct dw_edma *dw)
+{
+       struct dw_edma_chip *chip = dw->chip;
+       int virq;
+
+       chip->db_irq = 0;
+       chip->db_offset = ~0;
+
+       /*
+        * Only meaningful when the core provides the deassert sequence
+        * for interrupt emulation.
+        */
+       if (!dw->core->ack_emulated_irq)
+               return 0;
+
+       /*
+        * Allocate a single, requestable Linux virtual IRQ number.
+        * Use >= 1 so that 0 can remain a "not available" sentinel.
+        */
+       virq = irq_alloc_desc(NUMA_NO_NODE);
+       if (virq < 0)
+               return virq;
+
+       irq_set_chip_and_handler(virq, &dw_edma_emul_irqchip, handle_level_irq);
+       irq_set_chip_data(virq, dw);
+       irq_set_noprobe(virq);
+
+       chip->db_irq = virq;
+       chip->db_offset = dw_edma_core_db_offset(dw);
+
+       return 0;
+}
+
+static void dw_edma_emul_irq_free(struct dw_edma *dw)
+{
+       struct dw_edma_chip *chip = dw->chip;
+
+       if (!chip)
+               return;
+       if (chip->db_irq <= 0)
+               return;
+
+       irq_free_descs(chip->db_irq, 1);
+       chip->db_irq = 0;
+       chip->db_offset = ~0;
+}
+
+static inline irqreturn_t dw_edma_interrupt_emulated(void *data)
+{
+       struct dw_edma_irq *dw_irq = data;
+       struct dw_edma *dw = dw_irq->dw;
+       int db_irq = dw->chip->db_irq;
+
+       if (db_irq > 0) {
+               /*
+                * Interrupt emulation may assert the IRQ line without updating the
+                * normal DONE/ABORT status bits. With a shared IRQ handler we
+                * cannot reliably detect such events by status registers alone, so
+                * always perform the core-specific deassert sequence.
+                */
+               generic_handle_irq(db_irq);
+               return IRQ_HANDLED;
+       }
+       return IRQ_NONE;
+}
+
+static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data)
 {
        struct dw_edma_irq *dw_irq = data;
 
@@ -672,7 +761,7 @@ static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
                                       dw_edma_abort_interrupt);
 }
 
-static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
+static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
 {
        struct dw_edma_irq *dw_irq = data;
 
@@ -681,12 +770,33 @@ static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
                                       dw_edma_abort_interrupt);
 }
 
-static irqreturn_t dw_edma_interrupt_common(int irq, void *data)
+static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
+{
+       irqreturn_t ret = IRQ_NONE;
+
+       ret |= dw_edma_interrupt_write_inner(irq, data);
+       ret |= dw_edma_interrupt_emulated(data);
+
+       return ret;
+}
+
+static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
 {
        irqreturn_t ret = IRQ_NONE;
 
-       ret |= dw_edma_interrupt_write(irq, data);
-       ret |= dw_edma_interrupt_read(irq, data);
+       ret |= dw_edma_interrupt_read_inner(irq, data);
+       ret |= dw_edma_interrupt_emulated(data);
+
+       return ret;
+}
+
+static inline irqreturn_t dw_edma_interrupt_common(int irq, void *data)
+{
+       irqreturn_t ret = IRQ_NONE;
+
+       ret |= dw_edma_interrupt_write_inner(irq, data);
+       ret |= dw_edma_interrupt_read_inner(irq, data);
+       ret |= dw_edma_interrupt_emulated(data);
 
        return ret;
 }
@@ -973,6 +1083,11 @@ int dw_edma_probe(struct dw_edma_chip *chip)
        if (err)
                return err;
 
+       /* Allocate a dedicated virtual IRQ for interrupt-emulation doorbells */
+       err = dw_edma_emul_irq_alloc(dw);
+       if (err)
+               dev_warn(dev, "Failed to allocate emulation IRQ: %d\n", err);
+
        /* Setup write/read channels */
        err = dw_edma_channel_setup(dw, wr_alloc, rd_alloc);
        if (err)
@@ -988,6 +1103,7 @@ int dw_edma_probe(struct dw_edma_chip *chip)
 err_irq_free:
        for (i = (dw->nr_irqs - 1); i >= 0; i--)
                free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
+       dw_edma_emul_irq_free(dw);
 
        return err;
 }
@@ -1010,6 +1126,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
        /* Free irqs */
        for (i = (dw->nr_irqs - 1); i >= 0; i--)
                free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
+       dw_edma_emul_irq_free(dw);
 
        /* Deregister eDMA device */
        dma_async_device_unregister(&dw->dma);
index 270b5458aecf3a4a7b4e99eea170be57af15f044..9da53c75e49b9daeb75b0619b5e88ce1146744d5 100644 (file)
@@ -73,6 +73,8 @@ enum dw_edma_chip_flags {
  * @ll_region_rd:       DMA descriptor link list memory for read channel
  * @dt_region_wr:       DMA data memory for write channel
  * @dt_region_rd:       DMA data memory for read channel
+ * @db_irq:             Virtual IRQ dedicated to interrupt emulation
+ * @db_offset:          Offset from DMA register base
  * @mf:                         DMA register map format
  * @dw:                         struct dw_edma that is filled by dw_edma_probe()
  */
@@ -94,6 +96,10 @@ struct dw_edma_chip {
        struct dw_edma_region   dt_region_wr[EDMA_MAX_WR_CH];
        struct dw_edma_region   dt_region_rd[EDMA_MAX_RD_CH];
 
+       /* interrupt emulation */
+       int                     db_irq;
+       resource_size_t         db_offset;
+
        enum dw_edma_map_format mf;
 
        struct dw_edma          *dw;