* [PATCH v2 0/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain
@ 2026-11-15 9:06 James Hilliard
2026-11-15 9:06 ` [PATCH v2 1/3] dmaengine: virt-dma: share deferred descriptor retirement James Hilliard
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: James Hilliard @ 2026-11-15 9:06 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Hans de Goede, Emilio López, Maxime Ripard
Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
Retain descriptors through callback synchronization using a shared virt-dma
termination helper, then use it to fix sun4i cyclic callback lifetime
and IRQ serialization. The helper keeps termination usable in atomic
context while callbacks finish in process-context synchronization.
Also serialize pending IRQ handling with physical-channel reuse so an
old transfer's interrupt state cannot complete its replacement or disable
the replacement's interrupts.
Assisted-by: Codex:gpt-6-astra
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes in v2:
- Add a shared virt-dma descriptor-retirement helper before the sun4i
fix, following Frank's review.
- Retain all collected descriptors until synchronization, avoiding
provider-specific cyclic checks and preserving repeated termination.
- Retire pending IRQ state before physical-channel reuse and acknowledge
events under the owning virtual channel's lock in a separate fix.
- Link to v1: https://patch.msgid.link/20261103-submit-sun4i-dma-callback-lifetime-v1-v1-1-4c5a0653104f@gmail.com
---
James Hilliard (3):
dmaengine: virt-dma: share deferred descriptor retirement
dmaengine: sun4i: retain cyclic descriptors until callbacks drain
dmaengine: sun4i: retire interrupt state before channel reuse
drivers/dma/sun4i-dma.c | 68 +++++++++++++++++++++++++++++--------------------
drivers/dma/virt-dma.h | 20 ++++++++++++++-
2 files changed, 59 insertions(+), 29 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261103-submit-sun4i-dma-callback-lifetime-v1-b6192060b76f
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/3] dmaengine: virt-dma: share deferred descriptor retirement
2026-11-15 9:06 [PATCH v2 0/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain James Hilliard
@ 2026-11-15 9:06 ` James Hilliard
2026-11-15 9:06 ` [PATCH v2 2/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain James Hilliard
2026-11-15 9:06 ` [PATCH v2 3/3] dmaengine: sun4i: retire interrupt state before channel reuse James Hilliard
2 siblings, 0 replies; 4+ messages in thread
From: James Hilliard @ 2026-11-15 9:06 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Hans de Goede, Emilio López, Maxime Ripard
Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
A cyclic callback can retain its descriptor after the tasklet drops the
virtual-channel lock. Collecting and freeing descriptors during atomic
termination does not wait for that callback, and clearing vc->cyclic alone
only cancels a callback which has not started.
Provide vchan_terminate_all() to clear the pending cyclic callback and
move all channel-owned descriptors to desc_terminated. This also retains
a cyclic descriptor moved to the completed list by an error path, without
requiring provider-specific descriptor-type checks. Already detached
completed descriptors remain owned by the tasklet.
vchan_synchronize() drains callbacks before freeing or recycling the
retired descriptors. Repeated termination leaves them on the terminated
list until that synchronization occurs.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/virt-dma.h | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h
index abf2ae737c30..d3057fc85a9b 100644
--- a/drivers/dma/virt-dma.h
+++ b/drivers/dma/virt-dma.h
@@ -199,6 +199,24 @@ static inline void vchan_get_all_descriptors(struct virt_dma_chan *vc,
list_splice_tail_init(&vc->desc_terminated, head);
}
+/**
+ * vchan_terminate_all - retire descriptors until callbacks have drained
+ * @vc: virtual channel to terminate
+ *
+ * The caller must hold vc.lock and prevent further callback publication.
+ * Descriptors detached by the completion tasklet remain owned by that tasklet.
+ * All other descriptors are retained until vchan_synchronize(), including any
+ * descriptor still referenced by an executing cyclic callback.
+ */
+static inline void vchan_terminate_all(struct virt_dma_chan *vc)
+{
+ LIST_HEAD(head);
+
+ vchan_get_all_descriptors(vc, &head);
+ vc->cyclic = NULL;
+ list_splice_tail_init(&head, &vc->desc_terminated);
+}
+
static inline void vchan_free_chan_resources(struct virt_dma_chan *vc)
{
struct virt_dma_desc *vd;
@@ -232,7 +250,7 @@ static inline const char *vchan_chan_name(struct virt_dma_chan *vc)
* Makes sure that all scheduled or active callbacks have finished running. For
* proper operation the caller has to ensure that no new callbacks are scheduled
* after the invocation of this function started.
- * Free up the terminated cyclic descriptor to prevent memory leakage.
+ * Free or recycle terminated descriptors after their callbacks have drained.
*/
static inline void vchan_synchronize(struct virt_dma_chan *vc)
{
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 2/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain
2026-11-15 9:06 [PATCH v2 0/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain James Hilliard
2026-11-15 9:06 ` [PATCH v2 1/3] dmaengine: virt-dma: share deferred descriptor retirement James Hilliard
@ 2026-11-15 9:06 ` James Hilliard
2026-11-15 9:06 ` [PATCH v2 3/3] dmaengine: sun4i: retire interrupt state before channel reuse James Hilliard
2 siblings, 0 replies; 4+ messages in thread
From: James Hilliard @ 2026-11-15 9:06 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Hans de Goede, Emilio López, Maxime Ripard
Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
A cyclic IRQ queues a virt-dma tasklet with a pointer to the active
descriptor. sun4i_dma_terminate_all() frees it without clearing the queued
cyclic callback or waiting for one which has already started. The tasklet
can subsequently access the freed descriptor. There is no
device_synchronize callback to let clients drain these callbacks before
releasing their own resources either.
Serialize both half-transfer and end-transfer IRQ handling with termination
under the virtual-channel lock. Recheck the physical-channel association
after acquiring that lock so an IRQ which looked up the channel before
termination cannot use its detached transfer state.
Use vchan_terminate_all() to retire descriptors until synchronization. This
clears any pending cyclic callback. Retain them until a new synchronize
callback has waited for the IRQ handler and drained the tasklet. Drain
callbacks before freeing channel resources as well. Termination itself
remains usable from atomic context and from a completion callback.
The shared helper retains all collected descriptors. An in-flight
non-cyclic callback still owns its descriptor on the tasklet's private
list and retires it when the callback returns.
Fixes: b096c1377d1e ("dmaengine: sun4i: Add support for the DMA engine on sun[457]i SoCs")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun4i-dma.c | 41 +++++++++++++++++++++++++++--------------
1 file changed, 27 insertions(+), 14 deletions(-)
diff --git a/drivers/dma/sun4i-dma.c b/drivers/dma/sun4i-dma.c
index 225e4a53502b..b0c91dab62f0 100644
--- a/drivers/dma/sun4i-dma.c
+++ b/drivers/dma/sun4i-dma.c
@@ -293,10 +293,20 @@ static int convert_buswidth(enum dma_slave_buswidth addr_width)
return (addr_width >> 1);
}
+static void sun4i_dma_synchronize(struct dma_chan *chan)
+{
+ struct sun4i_dma_dev *priv = to_sun4i_dma_dev(chan->device);
+ struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
+
+ synchronize_irq(priv->irq);
+ vchan_synchronize(&vchan->vc);
+}
+
static void sun4i_dma_free_chan_resources(struct dma_chan *chan)
{
struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
+ sun4i_dma_synchronize(chan);
vchan_free_chan_resources(&vchan->vc);
}
@@ -322,7 +332,7 @@ static struct sun4i_dma_pchan *find_and_use_pchan(struct sun4i_dma_dev *priv,
spin_lock_irqsave(&priv->lock, flags);
for_each_clear_bit_from(i, priv->pchans_used, max) {
pchan = &pchans[i];
- pchan->vchan = vchan;
+ WRITE_ONCE(pchan->vchan, vchan);
set_bit(i, priv->pchans_used);
break;
}
@@ -339,7 +349,7 @@ static void release_pchan(struct sun4i_dma_dev *priv,
spin_lock_irqsave(&priv->lock, flags);
- pchan->vchan = NULL;
+ WRITE_ONCE(pchan->vchan, NULL);
clear_bit(nr, priv->pchans_used);
spin_unlock_irqrestore(&priv->lock, flags);
@@ -970,13 +980,11 @@ static int sun4i_dma_terminate_all(struct dma_chan *chan)
{
struct sun4i_dma_dev *priv = to_sun4i_dma_dev(chan->device);
struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
- struct sun4i_dma_pchan *pchan = vchan->pchan;
- LIST_HEAD(head);
+ struct sun4i_dma_pchan *pchan;
unsigned long flags;
spin_lock_irqsave(&vchan->vc.lock, flags);
- vchan_get_all_descriptors(&vchan->vc, &head);
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ pchan = vchan->pchan;
/*
* Clearing the configuration register will halt the pchan. Interrupts
@@ -991,13 +999,14 @@ static int sun4i_dma_terminate_all(struct dma_chan *chan)
release_pchan(priv, pchan);
}
- spin_lock_irqsave(&vchan->vc.lock, flags);
/* Clear these so the vchan is usable again */
vchan->processing = NULL;
vchan->pchan = NULL;
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ vchan->contract = NULL;
- vchan_dma_desc_free_list(&vchan->vc, &head);
+ /* A tasklet may already be using the active cyclic descriptor. */
+ vchan_terminate_all(&vchan->vc);
+ spin_unlock_irqrestore(&vchan->vc.lock, flags);
return 0;
}
@@ -1128,9 +1137,15 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
for_each_set_bit(bit, &pendirq, 32) {
pchan = &pchans[bit >> 1];
- vchan = pchan->vchan;
+ vchan = READ_ONCE(pchan->vchan);
if (!vchan) /* a terminated channel may still interrupt */
continue;
+
+ spin_lock(&vchan->vc.lock);
+ if (vchan->pchan != pchan || !vchan->processing) {
+ spin_unlock(&vchan->vc.lock);
+ continue;
+ }
contract = vchan->contract;
/*
@@ -1138,8 +1153,6 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
* interrupt (odd bit)
*/
if (bit & 1) {
- spin_lock(&vchan->vc.lock);
-
/*
* Move the promise into the completed list now that
* we're done with it
@@ -1173,8 +1186,6 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
disableirqs |= BIT(bit);
release_pchan(priv, pchan);
}
-
- spin_unlock(&vchan->vc.lock);
} else {
/* Half done interrupt */
if (contract->is_cyclic)
@@ -1182,6 +1193,7 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
else
disableirqs |= BIT(bit);
}
+ spin_unlock(&vchan->vc.lock);
}
/* Disable the IRQs for events we handled */
@@ -1276,6 +1288,7 @@ static int sun4i_dma_probe(struct platform_device *pdev)
priv->slave.device_prep_dma_cyclic = sun4i_dma_prep_dma_cyclic;
priv->slave.device_config = sun4i_dma_config;
priv->slave.device_terminate_all = sun4i_dma_terminate_all;
+ priv->slave.device_synchronize = sun4i_dma_synchronize;
priv->slave.copy_align = 2;
priv->slave.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 3/3] dmaengine: sun4i: retire interrupt state before channel reuse
2026-11-15 9:06 [PATCH v2 0/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain James Hilliard
2026-11-15 9:06 ` [PATCH v2 1/3] dmaengine: virt-dma: share deferred descriptor retirement James Hilliard
2026-11-15 9:06 ` [PATCH v2 2/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain James Hilliard
@ 2026-11-15 9:06 ` James Hilliard
2 siblings, 0 replies; 4+ messages in thread
From: James Hilliard @ 2026-11-15 9:06 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Hans de Goede, Emilio López, Maxime Ripard
Cc: dmaengine, linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
The IRQ handler samples pending status before looking up the physical
channel's virtual owner. Another CPU can terminate that owner and start
a transfer on a different virtual channel using the same physical channel.
The old pending bit then passes the new owner's association check and can
complete its transfer before the DMA has finished.
The handler also releases a completed transfer's physical channel before
masking and acknowledging its interrupts. A replacement transfer can start
in between, only to have its interrupts disabled or cleared by the old
handler's deferred cleanup.
Clear both pending interrupt sources when disabling a physical channel,
and read the status back before allowing the channel to be reused. Disable
the sources before releasing a completed transfer's channel. Re-read and
acknowledge each pending event under the owning virtual channel's lock,
before completing or rearming the transfer. This rejects stale status and
keeps the old handler from clearing a replacement transfer's events.
Fixes: b096c1377d1e ("dmaengine: sun4i: Add support for the DMA engine on sun[457]i SoCs")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun4i-dma.c | 29 ++++++++++++++---------------
1 file changed, 14 insertions(+), 15 deletions(-)
diff --git a/drivers/dma/sun4i-dma.c b/drivers/dma/sun4i-dma.c
index b0c91dab62f0..6ecc45cadd91 100644
--- a/drivers/dma/sun4i-dma.c
+++ b/drivers/dma/sun4i-dma.c
@@ -400,6 +400,13 @@ static void set_pchan_interrupt(struct sun4i_dma_dev *priv,
writel_relaxed(reg, priv->base + SUN4I_DMA_IRQ_ENABLE_REG);
+ /* Do not leave pending events for the channel's next owner. */
+ if (!half && !end) {
+ writel_relaxed(BIT(pchan_number * 2) | BIT(pchan_number * 2 + 1),
+ priv->base + SUN4I_DMA_IRQ_PENDING_STATUS_REG);
+ readl(priv->base + SUN4I_DMA_IRQ_PENDING_STATUS_REG);
+ }
+
spin_unlock_irqrestore(&priv->lock, flags);
}
@@ -1125,14 +1132,13 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
struct sun4i_dma_vchan *vchan;
struct sun4i_dma_contract *contract;
struct sun4i_dma_promise *promise;
- unsigned long pendirq, irqs, disableirqs;
+ unsigned long pendirq, irqs;
int bit, i, free_room, allow_mitigation = 1;
pendirq = readl_relaxed(priv->base + SUN4I_DMA_IRQ_PENDING_STATUS_REG);
handle_pending:
- disableirqs = 0;
free_room = 0;
for_each_set_bit(bit, &pendirq, 32) {
@@ -1142,10 +1148,13 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
continue;
spin_lock(&vchan->vc.lock);
- if (vchan->pchan != pchan || !vchan->processing) {
+ /* The channel may have been reused since pendirq was sampled. */
+ irqs = readl_relaxed(priv->base + SUN4I_DMA_IRQ_PENDING_STATUS_REG);
+ if (vchan->pchan != pchan || !vchan->processing || !(irqs & BIT(bit))) {
spin_unlock(&vchan->vc.lock);
continue;
}
+ writel_relaxed(BIT(bit), priv->base + SUN4I_DMA_IRQ_PENDING_STATUS_REG);
contract = vchan->contract;
/*
@@ -1183,7 +1192,7 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
vchan->pchan = NULL;
free_room = 1;
- disableirqs |= BIT(bit);
+ set_pchan_interrupt(priv, pchan, 0, 0);
release_pchan(priv, pchan);
}
} else {
@@ -1191,21 +1200,11 @@ static irqreturn_t sun4i_dma_interrupt(int irq, void *dev_id)
if (contract->is_cyclic)
vchan_cyclic_callback(&contract->vd);
else
- disableirqs |= BIT(bit);
+ set_pchan_interrupt(priv, pchan, 0, 1);
}
spin_unlock(&vchan->vc.lock);
}
- /* Disable the IRQs for events we handled */
- spin_lock(&priv->lock);
- irqs = readl_relaxed(priv->base + SUN4I_DMA_IRQ_ENABLE_REG);
- writel_relaxed(irqs & ~disableirqs,
- priv->base + SUN4I_DMA_IRQ_ENABLE_REG);
- spin_unlock(&priv->lock);
-
- /* Writing 1 to the pending field will clear the pending interrupt */
- writel_relaxed(pendirq, priv->base + SUN4I_DMA_IRQ_PENDING_STATUS_REG);
-
/*
* If a pchan was freed, we may be able to schedule something else,
* so have a look around
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-15 9:06 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-15 9:06 [PATCH v2 0/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain James Hilliard
2026-11-15 9:06 ` [PATCH v2 1/3] dmaengine: virt-dma: share deferred descriptor retirement James Hilliard
2026-11-15 9:06 ` [PATCH v2 2/3] dmaengine: sun4i: retain cyclic descriptors until callbacks drain James Hilliard
2026-11-15 9:06 ` [PATCH v2 3/3] dmaengine: sun4i: retire interrupt state before channel reuse James Hilliard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®