[PATCH 2/2] dmaengine: dw-edma: Add virtual IRQ for interrupt-emulation doorbells

Koichiro Den posted 2 patches 1 month, 2 weeks ago
[PATCH 2/2] dmaengine: dw-edma: Add virtual IRQ for interrupt-emulation doorbells
Posted by Koichiro Den 1 month, 2 weeks ago
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>
---
 drivers/dma/dw-edma/dw-edma-core.c | 127 +++++++++++++++++++++++++++--
 include/linux/dma/edma.h           |   6 ++
 2 files changed, 128 insertions(+), 5 deletions(-)

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 8e5f7defa6b6..51c1ea99c584 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -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);
diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h
index 270b5458aecf..9da53c75e49b 100644
--- a/include/linux/dma/edma.h
+++ b/include/linux/dma/edma.h
@@ -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;
-- 
2.51.0
Re: [PATCH 2/2] dmaengine: dw-edma: Add virtual IRQ for interrupt-emulation doorbells
Posted by Frank Li 1 month, 2 weeks ago
On Mon, Feb 16, 2026 at 12:22:16AM +0900, Koichiro Den wrote:
> 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>
> ---

I think it is good. Add Thomas Gleixner for irq part to do double check.

Reviewed-by: Frank Li <Frank.Li@nxp.com>
>  drivers/dma/dw-edma/dw-edma-core.c | 127 +++++++++++++++++++++++++++--
>  include/linux/dma/edma.h           |   6 ++
>  2 files changed, 128 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 8e5f7defa6b6..51c1ea99c584 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -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);
> diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h
> index 270b5458aecf..9da53c75e49b 100644
> --- a/include/linux/dma/edma.h
> +++ b/include/linux/dma/edma.h
> @@ -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;
> --
> 2.51.0
>