lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<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

Powered by Openwall GNU/*/Linux Powered by OpenVZ