[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260204145440.950609-6-den@valinux.co.jp>
Date: Wed, 4 Feb 2026 23:54:33 +0900
From: Koichiro Den <den@...inux.co.jp>
To: vkoul@...nel.org,
mani@...nel.org,
Frank.Li@....com,
jingoohan1@...il.com,
lpieralisi@...nel.org,
kwilczynski@...nel.org,
robh@...nel.org,
bhelgaas@...gle.com
Cc: dmaengine@...r.kernel.org,
linux-pci@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [PATCH v3 05/11] dmaengine: dw-edma: Implement dmaengine selfirq callbacks using interrupt emulation
DesignWare eDMA can generate an interrupt when software writes to the
WR_DONE_INT_STATUS / RD_DONE_INT_STATUS registers without setting the
normal DONE/ABORT status bits. This behavior can be used as a
lightweight doorbell for remote DMA use cases.
Implement the dmaengine selfirq registration callbacks for dw-edma, and
ACK emulated interrupts in the eDMA v0 core by writing 0 to INT_CLEAR.
Because interrupt emulation does not set any DONE/ABORT status bits,
dw-edma cannot reliably tell whether an IRQ was raised solely due to
normal DMA completion or whether an emulated selfirq was also raised
around the same time. As a result, selfirq callbacks are invoked on
every IRQ.
Note that dw-hdma-v0 does not implement the ACK path yet due to lack of
hardware access.
Signed-off-by: Koichiro Den <den@...inux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 118 ++++++++++++++++++++++++--
drivers/dma/dw-edma/dw-edma-core.h | 17 ++++
drivers/dma/dw-edma/dw-edma-v0-core.c | 11 +++
3 files changed, 141 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index b4cb02d545bd..398328b0a753 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -15,6 +15,7 @@
#include <linux/irq.h>
#include <linux/dma/edma.h>
#include <linux/dma-mapping.h>
+#include <linux/rculist.h>
#include <linux/string_choices.h>
#include "dw-edma-core.h"
@@ -687,7 +688,30 @@ 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 inline irqreturn_t dw_edma_interrupt_emulated(void *data)
+{
+ struct dw_edma_irq *dw_irq = data;
+ struct dw_edma *dw = dw_irq->dw;
+ struct dw_edma_selfirq *h;
+
+ /*
+ * eDMA interrupt emulation does not set DONE/ABORT status bits, so
+ * a shared IRQ handler cannot reliably tell whether or not the
+ * emulated interrupt has been raised when the status bits are
+ * non-zero. Invoke selfirq callbacks on every IRQ and always claim
+ * the interrupt.
+ */
+ dw_edma_core_ack_selfirq(dw);
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(h, &dw->selfirq_handlers, node)
+ h->fn(&dw->dma, h->data);
+ rcu_read_unlock();
+
+ return IRQ_HANDLED;
+}
+
+static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data)
{
struct dw_edma_irq *dw_irq = data;
@@ -696,7 +720,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;
@@ -705,12 +729,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_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(irq, data);
- ret |= dw_edma_interrupt_read(irq, data);
+ ret |= dw_edma_interrupt_write_inner(irq, data);
+ ret |= dw_edma_interrupt_read_inner(irq, data);
+ ret |= dw_edma_interrupt_emulated(data);
return ret;
}
@@ -742,6 +787,63 @@ static void dw_edma_free_chan_resources(struct dma_chan *dchan)
}
}
+static inline struct dw_edma *to_dw_edma(struct dma_device *ddev)
+{
+ return container_of(ddev, struct dw_edma, dma);
+}
+
+static int dw_edma_register_selfirq(struct dma_device *ddev, dma_selfirq_fn fn,
+ void *data)
+{
+ struct dw_edma *dw = to_dw_edma(ddev);
+ struct dw_edma_selfirq *h, *iter;
+ unsigned long flags;
+
+ if (!dw || !fn)
+ return -EINVAL;
+
+ h = kzalloc(sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return -ENOMEM;
+ h->fn = fn;
+ h->data = data;
+
+ spin_lock_irqsave(&dw->selfirq_lock, flags);
+ list_for_each_entry(iter, &dw->selfirq_handlers, node) {
+ if (iter->fn == fn && iter->data == data) {
+ spin_unlock_irqrestore(&dw->selfirq_lock, flags);
+ kfree(h);
+ return -EEXIST;
+ }
+ }
+ list_add_tail_rcu(&h->node, &dw->selfirq_handlers);
+ spin_unlock_irqrestore(&dw->selfirq_lock, flags);
+ return 0;
+}
+
+static void dw_edma_unregister_selfirq(struct dma_device *ddev, dma_selfirq_fn fn,
+ void *data)
+{
+ struct dw_edma *dw = to_dw_edma(ddev);
+ struct dw_edma_selfirq *h;
+ unsigned long flags;
+
+ if (!dw || !fn)
+ return;
+
+ spin_lock_irqsave(&dw->selfirq_lock, flags);
+ list_for_each_entry(h, &dw->selfirq_handlers, node) {
+ if (h->fn == fn && h->data == data) {
+ list_del_rcu(&h->node);
+ spin_unlock_irqrestore(&dw->selfirq_lock, flags);
+ synchronize_rcu();
+ kfree(h);
+ return;
+ }
+ }
+ spin_unlock_irqrestore(&dw->selfirq_lock, flags);
+}
+
static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
{
struct dw_edma_chip *chip = dw->chip;
@@ -846,6 +948,10 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
dma_set_max_seg_size(dma->dev, U32_MAX);
+ /* Set DMA device callbacks */
+ dma->device_register_selfirq = dw_edma_register_selfirq;
+ dma->device_unregister_selfirq = dw_edma_unregister_selfirq;
+
/* Register DMA device */
return dma_async_device_register(dma);
}
@@ -959,6 +1065,8 @@ int dw_edma_probe(struct dw_edma_chip *chip)
return -ENOMEM;
dw->chip = chip;
+ INIT_LIST_HEAD(&dw->selfirq_handlers);
+ spin_lock_init(&dw->selfirq_lock);
if (dw->chip->mf == EDMA_MF_HDMA_NATIVE)
dw_hdma_v0_core_register(dw);
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 0608b9044a08..4b9c4b28b49b 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -97,6 +97,12 @@ struct dw_edma_irq {
struct dw_edma *dw;
};
+struct dw_edma_selfirq {
+ struct list_head node;
+ dma_selfirq_fn fn;
+ void *data;
+};
+
struct dw_edma {
char name[32];
@@ -115,6 +121,9 @@ struct dw_edma {
struct dw_edma_chip *chip;
const struct dw_edma_core_ops *core;
+
+ struct list_head selfirq_handlers;
+ spinlock_t selfirq_lock;
};
typedef void (*dw_edma_handler_t)(struct dw_edma_chan *);
@@ -128,6 +137,7 @@ struct dw_edma_core_ops {
void (*start)(struct dw_edma_chunk *chunk, bool first);
void (*ch_config)(struct dw_edma_chan *chan);
void (*debugfs_on)(struct dw_edma *dw);
+ void (*ack_selfirq)(struct dw_edma *dw);
};
struct dw_edma_sg {
@@ -208,6 +218,13 @@ void dw_edma_core_debugfs_on(struct dw_edma *dw)
dw->core->debugfs_on(dw);
}
+static inline
+void dw_edma_core_ack_selfirq(struct dw_edma *dw)
+{
+ if (dw->core->ack_selfirq)
+ dw->core->ack_selfirq(dw);
+}
+
static inline
bool dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan)
{
diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c
index a0441e8aa3b3..68e0d088570d 100644
--- a/drivers/dma/dw-edma/dw-edma-v0-core.c
+++ b/drivers/dma/dw-edma/dw-edma-v0-core.c
@@ -519,6 +519,16 @@ static void dw_edma_v0_core_debugfs_on(struct dw_edma *dw)
dw_edma_v0_debugfs_on(dw);
}
+static void dw_edma_v0_core_ack_selfirq(struct dw_edma *dw)
+{
+ /*
+ * Interrupt emulation may assert the IRQ without setting
+ * DONE/ABORT status bits. A zero write to INT_CLEAR deasserts the
+ * emulated IRQ, while being a no-op for real interrupts.
+ */
+ SET_BOTH_32(dw, int_clear, 0);
+}
+
static const struct dw_edma_core_ops dw_edma_v0_core = {
.off = dw_edma_v0_core_off,
.ch_count = dw_edma_v0_core_ch_count,
@@ -527,6 +537,7 @@ static const struct dw_edma_core_ops dw_edma_v0_core = {
.start = dw_edma_v0_core_start,
.ch_config = dw_edma_v0_core_ch_config,
.debugfs_on = dw_edma_v0_core_debugfs_on,
+ .ack_selfirq = dw_edma_v0_core_ack_selfirq,
};
void dw_edma_v0_core_register(struct dw_edma *dw)
--
2.51.0
Powered by blists - more mailing lists