* [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime
@ 2026-10-23 23:38 James Hilliard
2026-10-23 23:38 ` [PATCH v3 1/9] dmaengine: sun6i: fix physical channel index in interrupt handler James Hilliard
` (8 more replies)
0 siblings, 9 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard,
stable, Frank Li, Sashiko
Correct interrupt routing and serialize physical-channel ownership across
allocation, completion, pause and termination. Keep paused virtual channels
off the allocator, drain completion callbacks before releasing resources,
and associate cyclic state with the descriptor that owns it.
The first patch carries Hongling Zeng's physical-channel indexing fix [0],
adapted to the configurable interrupt-bank size introduced for A733. The
second handles partially populated banks, including H3's twelve channels.
The ownership work is split into lock ordering, physical-channel locking,
completed-descriptor retirement and interrupt cleanup. Pause bookkeeping,
callback synchronization and per-descriptor cyclic state follow separately.
Based on DMAengine next at aff5e6a77fc6, which includes A733 support, with
the posted IRQ-safe locking fix [1], channel pause-bit fix [2], and the
virt-dma synchronization helper from [3] applied first. The synchronization
patch uses that shared helper for the sun6i device_synchronize callback.
[0] https://lore.kernel.org/r/20260706064123.66035-1-zenghongling@kylinos.cn
[1] https://lore.kernel.org/r/20260926171049.841335-1-tien.nguyenminh@embeddedlinux.blog
[2] https://lore.kernel.org/r/20261021-submit-sun6i-dma-pause-bit-v1-v1-1-38d286b69dab@gmail.com
[3] https://lore.kernel.org/r/20261022-submit-mtk-uart-dma-synchronize-v1-v2-1-fdaef0e2edd6@gmail.com
Assisted-by: Codex:gpt-6-astra
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes in v3:
- Split the ownership patch into four commits covering lock ordering,
physical-channel locking, completed-descriptor retirement and interrupt
cleanup, following Frank Li's review.
- Document why virt-dma still takes vc.lock independently of sdev->lock.
- Check pending interrupt status first in the per-bank loop condition.
- Link to v2: https://lore.kernel.org/r/20261023-submit-sun6i-dma-lifetime-next-v1-v2-0-c9e07cc44bf8@gmail.com
Changes in v2:
- Clear the active descriptor pointer before handing finite completion to
virt-dma, replacing the completed-descriptor pointer.
- Release completed physical channels while their virtual channels remain
paused, and clear the hardware pause request before reassignment.
- Keep the ownership changes in patch 3 and the pause policy in patch 4.
- Link to v1: https://lore.kernel.org/r/20261022-submit-sun6i-dma-lifetime-next-v1-v1-0-01085f684c9f@gmail.com
---
Hongling Zeng (1):
dmaengine: sun6i: fix physical channel index in interrupt handler
James Hilliard (8):
dmaengine: sun6i: handle the last partial interrupt bank
dmaengine: sun6i: take the device lock before the virtual channel lock
dmaengine: sun6i: serialize physical channel ownership
dmaengine: sun6i: detach completed descriptors before invoking callbacks
dmaengine: sun6i: retire interrupt state before reusing physical channels
dmaengine: sun6i: track paused virtual channels
dmaengine: sun6i: synchronize callbacks before releasing channel resources
dmaengine: sun6i: keep cyclic state with the prepared descriptor
drivers/dma/sun6i-dma.c | 183 ++++++++++++++++++++++++++++--------------------
1 file changed, 106 insertions(+), 77 deletions(-)
---
base-commit: aff5e6a77fc69a7c490246465940d18422d7c631
change-id: 20261022-submit-sun6i-dma-lifetime-next-v1-53c3158f4841
prerequisite-patch-id: d6398447647aa0dbb005b5e6dc6ba96e642efa86
prerequisite-patch-id: f67c1f8d8d9e014cc650f2f37648662430c3b77b
prerequisite-patch-id: 8522878df4ecd4fb007cfd32611359e6eb58a1ef
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 1/9] dmaengine: sun6i: fix physical channel index in interrupt handler
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-23 23:38 ` [PATCH v3 2/9] dmaengine: sun6i: handle the last partial interrupt bank James Hilliard
` (7 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard,
stable, Frank Li
From: Hongling Zeng <zenghongling@kylinos.cn>
The interrupt handler iterates over register banks in the outer loop,
but the physical channel lookup uses only the inner loop index. It
therefore handles every bank as if it belonged to the first channels.
For controllers with eight channels per bank, interrupts from channels
8 and above are routed to channels 0-7. On A733, each bank covers one
channel, so every interrupt is routed to channel 0. This can falsely
complete descriptors for unrelated channels and corrupt their data.
Use the absolute channel index, calculated from the bank number and the
controller's configured number of channels per bank.
Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
Cc: stable@vger.kernel.org
Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
[james: use the configured bank size and describe the A733 case]
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 8752969366ff..9d94a37fcf10 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -603,7 +603,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
sun6i_write_irq_stat(sdev, i, status);
for (j = 0; (j < sdev->cfg->num_channels_per_reg) && status; j++) {
- pchan = sdev->pchans + j;
+ pchan = sdev->pchans + i * sdev->cfg->num_channels_per_reg + j;
vchan = pchan->vchan;
if (vchan && (status & vchan->irq_type)) {
if (vchan->cyclic) {
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 2/9] dmaengine: sun6i: handle the last partial interrupt bank
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
2026-10-23 23:38 ` [PATCH v3 1/9] dmaengine: sun6i: fix physical channel index in interrupt handler James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-26 20:57 ` Frank Li
2026-10-23 23:38 ` [PATCH v3 3/9] dmaengine: sun6i: take the device lock before the virtual channel lock James Hilliard
` (6 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard,
Sashiko
The number of physical channels need not be a multiple of the number
covered by an interrupt-status register. H3 has twelve channels and
eight channels per bank. Rounding down the number of banks leaves its
channels 8 through 11 without completion handling.
Round up the number of banks to visit, and stop processing the last bank
at num_pchans so unused status bits cannot index past the physical-channel
array. Use the controller's configured bank size for both limits and
the physical-channel index.
Fixes: f008db8c00c1 ("dmaengine: sun6i: Add support for Allwinner H3 (sun8i) variant")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/r/20260706065650.34B131F00A3A@smtp.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 9d94a37fcf10..3a6525dd3d32 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -587,12 +587,13 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
{
struct sun6i_dma_dev *sdev = dev_id;
+ u32 channels_per_reg = sdev->cfg->num_channels_per_reg;
struct sun6i_vchan *vchan;
struct sun6i_pchan *pchan;
int i, j, ret = IRQ_NONE;
u32 status;
- for (i = 0; i < sdev->num_pchans / sdev->cfg->num_channels_per_reg; i++) {
+ for (i = 0; i < DIV_ROUND_UP(sdev->num_pchans, channels_per_reg); i++) {
status = sun6i_read_irq_stat(sdev, i);
if (!status)
continue;
@@ -602,8 +603,9 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
sun6i_write_irq_stat(sdev, i, status);
- for (j = 0; (j < sdev->cfg->num_channels_per_reg) && status; j++) {
- pchan = sdev->pchans + i * sdev->cfg->num_channels_per_reg + j;
+ for (j = 0; status && j < channels_per_reg &&
+ i * channels_per_reg + j < sdev->num_pchans; j++) {
+ pchan = sdev->pchans + i * channels_per_reg + j;
vchan = pchan->vchan;
if (vchan && (status & vchan->irq_type)) {
if (vchan->cyclic) {
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 3/9] dmaengine: sun6i: take the device lock before the virtual channel lock
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
2026-10-23 23:38 ` [PATCH v3 1/9] dmaengine: sun6i: fix physical channel index in interrupt handler James Hilliard
2026-10-23 23:38 ` [PATCH v3 2/9] dmaengine: sun6i: handle the last partial interrupt bank James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-26 21:05 ` Frank Li
2026-10-23 23:38 ` [PATCH v3 4/9] dmaengine: sun6i: serialize physical channel ownership James Hilliard
` (5 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
The pending queue is protected by sdev->lock, while virt-dma uses vc.lock
for descriptor lists and cookies. issue_pending() and resume() currently
take the device lock inside the virtual-channel lock when queueing work.
Take sdev->lock first in both paths, and inspect the physical-channel
assignment after acquiring the locks. This establishes the nesting order
needed to keep allocation and descriptor startup in one critical section.
virt-dma preparation, submission and callback processing continue to use
vc.lock independently.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 3a6525dd3d32..435d7daa1c0f 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -952,23 +952,24 @@ static int sun6i_dma_resume(struct dma_chan *chan)
{
struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
- struct sun6i_pchan *pchan = vchan->phy;
+ struct sun6i_pchan *pchan;
unsigned long flags;
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: resume\n", &vchan->vc);
- spin_lock_irqsave(&vchan->vc.lock, flags);
+ spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
+ pchan = vchan->phy;
if (pchan) {
writel(DMA_CHAN_PAUSE_RESUME,
pchan->base + DMA_CHAN_PAUSE);
} else if (!list_empty(&vchan->vc.desc_issued)) {
- spin_lock(&sdev->lock);
list_add_tail(&vchan->node, &sdev->pending);
- spin_unlock(&sdev->lock);
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ spin_unlock(&vchan->vc.lock);
+ spin_unlock_irqrestore(&sdev->lock, flags);
return 0;
}
@@ -1057,25 +1058,23 @@ static void sun6i_dma_issue_pending(struct dma_chan *chan)
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
unsigned long flags;
- spin_lock_irqsave(&vchan->vc.lock, flags);
+ spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
if (vchan_issue_pending(&vchan->vc)) {
- spin_lock(&sdev->lock);
-
if (!vchan->phy && list_empty(&vchan->node)) {
list_add_tail(&vchan->node, &sdev->pending);
tasklet_schedule(&sdev->task);
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: issued\n",
&vchan->vc);
}
-
- spin_unlock(&sdev->lock);
} else {
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: nothing to issue\n",
&vchan->vc);
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ spin_unlock(&vchan->vc.lock);
+ spin_unlock_irqrestore(&sdev->lock, flags);
}
static void sun6i_dma_free_chan_resources(struct dma_chan *chan)
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 4/9] dmaengine: sun6i: serialize physical channel ownership
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
` (2 preceding siblings ...)
2026-10-23 23:38 ` [PATCH v3 3/9] dmaengine: sun6i: take the device lock before the virtual channel lock James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-26 21:14 ` Frank Li
2026-10-23 23:38 ` [PATCH v3 5/9] dmaengine: sun6i: detach completed descriptors before invoking callbacks James Hilliard
` (4 subsequent siblings)
8 siblings, 1 reply; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard,
Sashiko
The allocator changes vchan->phy and pchan->vchan under sdev->lock, but
termination reads the physical channel before taking either lock. The IRQ
handler also takes an unprotected owner snapshot. Termination can miss a
new assignment or detach an owner while an interrupt is about to complete
its descriptor. A sampled IRQ can also be applied after reassignment.
Protect physical-channel ownership with sdev->lock across allocation,
interrupt handling, pause, termination and residue queries. Read and
acknowledge IRQ status under that lock, and allocate and start each pending
channel in the same critical section. This also serializes updates to the
shared interrupt-enable registers.
Nest vc.lock inside the device lock when accessing descriptor lists or
updating cookies, including cyclic notifications. virt-dma takes vc.lock
independently when preparing, submitting and completing descriptors, so
the device lock does not replace it. Client callbacks run outside both
locks in the virtual-channel tasklet.
Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/r/20260706065650.34B131F00A3A@smtp.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 67 ++++++++++++++++++++++++-------------------------
1 file changed, 33 insertions(+), 34 deletions(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 435d7daa1c0f..d3a63c866645 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -217,6 +217,10 @@ struct sun6i_dma_dev {
struct clk *clk;
struct clk *clk_mbus;
int irq;
+ /*
+ * Physical channels, pending list and registers; nests outside vc.lock.
+ * virt-dma also takes vc.lock independently for its lists and cookies.
+ */
spinlock_t lock;
struct reset_control *rstc;
struct tasklet_struct task;
@@ -524,11 +528,11 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
struct sun6i_dma_dev *sdev = from_tasklet(sdev, t, task);
struct sun6i_vchan *vchan;
struct sun6i_pchan *pchan;
- unsigned int pchan_alloc = 0;
unsigned int pchan_idx;
+ spin_lock_irq(&sdev->lock);
list_for_each_entry(vchan, &sdev->slave.channels, vc.chan.device_node) {
- spin_lock_irq(&vchan->vc.lock);
+ spin_lock(&vchan->vc.lock);
pchan = vchan->phy;
@@ -545,10 +549,9 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
pchan->vchan = NULL;
}
}
- spin_unlock_irq(&vchan->vc.lock);
+ spin_unlock(&vchan->vc.lock);
}
- spin_lock_irq(&sdev->lock);
for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
pchan = &sdev->pchans[pchan_idx];
@@ -557,31 +560,20 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
vchan = list_first_entry(&sdev->pending,
struct sun6i_vchan, node);
+ spin_lock(&vchan->vc.lock);
/* Remove from pending channels */
list_del_init(&vchan->node);
- pchan_alloc |= BIT(pchan_idx);
/* Mark this channel allocated */
pchan->vchan = vchan;
vchan->phy = pchan;
dev_dbg(sdev->slave.dev, "pchan %u: alloc vchan %p\n",
pchan->idx, &vchan->vc);
+ sun6i_dma_start_desc(vchan);
+ spin_unlock(&vchan->vc.lock);
}
spin_unlock_irq(&sdev->lock);
-
- for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
- if (!(pchan_alloc & BIT(pchan_idx)))
- continue;
-
- pchan = sdev->pchans + pchan_idx;
- vchan = pchan->vchan;
- if (vchan) {
- spin_lock_irq(&vchan->vc.lock);
- sun6i_dma_start_desc(vchan);
- spin_unlock_irq(&vchan->vc.lock);
- }
- }
}
static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
@@ -590,9 +582,11 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
u32 channels_per_reg = sdev->cfg->num_channels_per_reg;
struct sun6i_vchan *vchan;
struct sun6i_pchan *pchan;
+ unsigned long flags;
int i, j, ret = IRQ_NONE;
u32 status;
+ spin_lock_irqsave(&sdev->lock, flags);
for (i = 0; i < DIV_ROUND_UP(sdev->num_pchans, channels_per_reg); i++) {
status = sun6i_read_irq_stat(sdev, i);
if (!status)
@@ -608,14 +602,14 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
pchan = sdev->pchans + i * channels_per_reg + j;
vchan = pchan->vchan;
if (vchan && (status & vchan->irq_type)) {
+ spin_lock(&vchan->vc.lock);
if (vchan->cyclic) {
vchan_cyclic_callback(&pchan->desc->vd);
} else {
- spin_lock(&vchan->vc.lock);
vchan_cookie_complete(&pchan->desc->vd);
pchan->done = pchan->desc;
- spin_unlock(&vchan->vc.lock);
}
+ spin_unlock(&vchan->vc.lock);
}
status = status >> DMA_IRQ_CHAN_WIDTH;
@@ -625,6 +619,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
tasklet_schedule(&sdev->task);
ret = IRQ_HANDLED;
}
+ spin_unlock_irqrestore(&sdev->lock, flags);
return ret;
}
@@ -931,19 +926,20 @@ static int sun6i_dma_pause(struct dma_chan *chan)
{
struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
- struct sun6i_pchan *pchan = vchan->phy;
+ struct sun6i_pchan *pchan;
unsigned long flags;
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: pause\n", &vchan->vc);
+ spin_lock_irqsave(&sdev->lock, flags);
+ pchan = vchan->phy;
if (pchan) {
writel(DMA_CHAN_PAUSE_PAUSE,
pchan->base + DMA_CHAN_PAUSE);
} else {
- spin_lock_irqsave(&sdev->lock, flags);
list_del_init(&vchan->node);
- spin_unlock_irqrestore(&sdev->lock, flags);
}
+ spin_unlock_irqrestore(&sdev->lock, flags);
return 0;
}
@@ -978,15 +974,14 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
{
struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
- struct sun6i_pchan *pchan = vchan->phy;
+ struct sun6i_pchan *pchan;
unsigned long flags;
LIST_HEAD(head);
spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
+ pchan = vchan->phy;
list_del_init(&vchan->node);
- spin_unlock_irqrestore(&sdev->lock, flags);
-
- spin_lock_irqsave(&vchan->vc.lock, flags);
if (pchan && pchan->desc && pchan->desc != pchan->done) {
struct virt_dma_desc *vd = &pchan->desc->vd;
@@ -1007,7 +1002,8 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
pchan->done = NULL;
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
+ spin_unlock(&vchan->vc.lock);
+ spin_unlock_irqrestore(&sdev->lock, flags);
vchan_dma_desc_free_list(&vchan->vc, &head);
@@ -1018,8 +1014,9 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *state)
{
+ struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
- struct sun6i_pchan *pchan = vchan->phy;
+ struct sun6i_pchan *pchan;
struct sun6i_dma_lli *lli;
struct virt_dma_desc *vd;
struct sun6i_desc *txd;
@@ -1027,11 +1024,12 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
unsigned long flags;
size_t bytes = 0;
+ spin_lock_irqsave(&sdev->lock, flags);
+ spin_lock(&vchan->vc.lock);
ret = dma_cookie_status(chan, cookie, state);
if (ret == DMA_COMPLETE || !state)
- return ret;
-
- spin_lock_irqsave(&vchan->vc.lock, flags);
+ goto out;
+ pchan = vchan->phy;
vd = vchan_find_desc(&vchan->vc, cookie);
txd = to_sun6i_desc(&vd->tx);
@@ -1045,9 +1043,10 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
bytes = sun6i_get_chan_size(pchan);
}
- spin_unlock_irqrestore(&vchan->vc.lock, flags);
-
dma_set_residue(state, bytes);
+out:
+ spin_unlock(&vchan->vc.lock);
+ spin_unlock_irqrestore(&sdev->lock, flags);
return ret;
}
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 5/9] dmaengine: sun6i: detach completed descriptors before invoking callbacks
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
` (3 preceding siblings ...)
2026-10-23 23:38 ` [PATCH v3 4/9] dmaengine: sun6i: serialize physical channel ownership James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-23 23:38 ` [PATCH v3 6/9] dmaengine: sun6i: retire interrupt state before reusing physical channels James Hilliard
` (3 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
Finite completion publishes a descriptor to virt-dma but leaves pchan->desc
pointing to it until the allocator runs. The virtual-channel tasklet may
free that descriptor first. A residue query for another incomplete cookie
can still pass the retired descriptor to sun6i_get_chan_size().
Clear the active descriptor pointer before publishing completion. An
assigned physical channel without a descriptor is ready for the allocator
to start its next transfer or release it. Use that state in place of the
completed-descriptor pointer, so residue queries and termination only
consult descriptors still owned by the physical channel.
Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 23 ++++++++++-------------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index d3a63c866645..900bb629ba3b 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -198,7 +198,6 @@ struct sun6i_pchan {
void __iomem *base;
struct sun6i_vchan *vchan;
struct sun6i_desc *desc;
- struct sun6i_desc *done;
};
struct sun6i_vchan {
@@ -490,16 +489,12 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
if (!pchan)
return -EAGAIN;
- if (!desc) {
- pchan->desc = NULL;
- pchan->done = NULL;
+ if (!desc)
return -EAGAIN;
- }
list_del(&desc->node);
pchan->desc = to_sun6i_desc(&desc->tx);
- pchan->done = NULL;
sun6i_dma_dump_lli(vchan, pchan->desc->v_lli, pchan->desc->p_lli);
@@ -536,7 +531,7 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
pchan = vchan->phy;
- if (pchan && pchan->done) {
+ if (pchan && !pchan->desc) {
if (sun6i_dma_start_desc(vchan)) {
/*
* No current txd associated with this channel
@@ -582,6 +577,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
u32 channels_per_reg = sdev->cfg->num_channels_per_reg;
struct sun6i_vchan *vchan;
struct sun6i_pchan *pchan;
+ struct sun6i_desc *desc;
unsigned long flags;
int i, j, ret = IRQ_NONE;
u32 status;
@@ -601,13 +597,15 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
i * channels_per_reg + j < sdev->num_pchans; j++) {
pchan = sdev->pchans + i * channels_per_reg + j;
vchan = pchan->vchan;
- if (vchan && (status & vchan->irq_type)) {
+ desc = pchan->desc;
+ if (vchan && desc && (status & vchan->irq_type)) {
spin_lock(&vchan->vc.lock);
if (vchan->cyclic) {
- vchan_cyclic_callback(&pchan->desc->vd);
+ vchan_cyclic_callback(&desc->vd);
} else {
- vchan_cookie_complete(&pchan->desc->vd);
- pchan->done = pchan->desc;
+ /* Completion transfers ownership to virt-dma. */
+ pchan->desc = NULL;
+ vchan_cookie_complete(&desc->vd);
}
spin_unlock(&vchan->vc.lock);
}
@@ -983,7 +981,7 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
pchan = vchan->phy;
list_del_init(&vchan->node);
- if (pchan && pchan->desc && pchan->desc != pchan->done) {
+ if (pchan && pchan->desc) {
struct virt_dma_desc *vd = &pchan->desc->vd;
vchan_terminate_vdesc(vd);
@@ -999,7 +997,6 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
vchan->phy = NULL;
pchan->vchan = NULL;
pchan->desc = NULL;
- pchan->done = NULL;
}
spin_unlock(&vchan->vc.lock);
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 6/9] dmaengine: sun6i: retire interrupt state before reusing physical channels
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
` (4 preceding siblings ...)
2026-10-23 23:38 ` [PATCH v3 5/9] dmaengine: sun6i: detach completed descriptors before invoking callbacks James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-23 23:38 ` [PATCH v3 7/9] dmaengine: sun6i: track paused virtual channels James Hilliard
` (2 subsequent siblings)
8 siblings, 0 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
Termination stops a physical channel and drops its owner without masking
or clearing its interrupt state. A completion latched by the old transfer
can therefore be delivered to a descriptor installed after reassignment.
Mask and acknowledge the stopped channel's interrupts before releasing
its ownership, and clear pending status before starting a new descriptor.
Keep these register updates under the same device lock used to sample and
acknowledge interrupts in the handler.
Schedule the allocator after termination so pending virtual channels can
use the released physical channel even when no further interrupt occurs.
Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 900bb629ba3b..dab17b3863fc 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -501,6 +501,11 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
irq_reg = pchan->idx / sdev->cfg->num_channels_per_reg;
irq_offset = pchan->idx % sdev->cfg->num_channels_per_reg;
+ /* Do not deliver a previous transfer's interrupt to this descriptor. */
+ sun6i_write_irq_stat(sdev, irq_reg,
+ (DMA_IRQ_HALF | DMA_IRQ_PKG | DMA_IRQ_QUEUE) <<
+ (irq_offset * DMA_IRQ_CHAN_WIDTH));
+
vchan->irq_type = vchan->cyclic ? DMA_IRQ_PKG : DMA_IRQ_QUEUE;
irq_val = sun6i_read_irq_en(sdev, irq_reg);
@@ -973,6 +978,7 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
struct sun6i_pchan *pchan;
+ u32 irq_reg, irq_shift, irq_mask;
unsigned long flags;
LIST_HEAD(head);
@@ -994,6 +1000,14 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
writel(DMA_CHAN_ENABLE_STOP, pchan->base + DMA_CHAN_ENABLE);
writel(DMA_CHAN_PAUSE_RESUME, pchan->base + DMA_CHAN_PAUSE);
+ irq_reg = pchan->idx / sdev->cfg->num_channels_per_reg;
+ irq_shift = (pchan->idx % sdev->cfg->num_channels_per_reg) *
+ DMA_IRQ_CHAN_WIDTH;
+ irq_mask = (DMA_IRQ_HALF | DMA_IRQ_PKG | DMA_IRQ_QUEUE) << irq_shift;
+ sun6i_write_irq_en(sdev, irq_reg,
+ sun6i_read_irq_en(sdev, irq_reg) & ~irq_mask);
+ sun6i_write_irq_stat(sdev, irq_reg, irq_mask);
+
vchan->phy = NULL;
pchan->vchan = NULL;
pchan->desc = NULL;
@@ -1003,6 +1017,8 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
spin_unlock_irqrestore(&sdev->lock, flags);
vchan_dma_desc_free_list(&vchan->vc, &head);
+ if (!atomic_read(&sdev->tasklet_shutdown))
+ tasklet_schedule(&sdev->task);
return 0;
}
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 7/9] dmaengine: sun6i: track paused virtual channels
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
` (5 preceding siblings ...)
2026-10-23 23:38 ` [PATCH v3 6/9] dmaengine: sun6i: retire interrupt state before reusing physical channels James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-23 23:38 ` [PATCH v3 8/9] dmaengine: sun6i: synchronize callbacks before releasing channel resources James Hilliard
2026-10-23 23:38 ` [PATCH v3 9/9] dmaengine: sun6i: keep cyclic state with the prepared descriptor James Hilliard
8 siblings, 0 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
sun6i_dma_tx_status() only returns the cookie state, so a paused but
incomplete transfer is reported as DMA_IN_PROGRESS. The 8250 RX flush
path pauses the channel and calls its completion helper before terminating
it. That helper ignores an in-progress transfer to avoid consuming a new
RX operation. With sun6i it consequently drops the partial buffer and
leaves rx_running set, preventing another receive from being submitted.
Track pause state under the channel-assignment lock and report DMA_PAUSED
for incomplete cookies, including status queries without a residue output.
Flush the hardware pause request before returning. Completed cookies
remain DMA_COMPLETE.
Keep paused channels out of the pending queue and do not start a queued
descriptor when a paused channel's previous descriptor completes. Release
the completed physical channel so other virtual channels can use it, and
clear its hardware pause request before reassignment. The paused virtual
channel retains its queued descriptors until resume.
Resume requeues an unassigned channel only once and schedules the
allocator, so an idle controller does not require an unrelated completion
to resume it. Clear the pause state when terminating the channel.
Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index dab17b3863fc..25987f550707 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -208,6 +208,7 @@ struct sun6i_vchan {
u8 port;
u8 irq_type;
bool cyclic;
+ bool paused;
};
struct sun6i_dma_dev {
@@ -537,13 +538,17 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
pchan = vchan->phy;
if (pchan && !pchan->desc) {
- if (sun6i_dma_start_desc(vchan)) {
+ if (vchan->paused || sun6i_dma_start_desc(vchan)) {
/*
* No current txd associated with this channel
*/
dev_dbg(sdev->slave.dev, "pchan %u: free\n",
pchan->idx);
+ /* The next owner must not inherit the pause request. */
+ writel(DMA_CHAN_PAUSE_RESUME,
+ pchan->base + DMA_CHAN_PAUSE);
+
/* Mark this channel free */
vchan->phy = NULL;
pchan->vchan = NULL;
@@ -935,10 +940,12 @@ static int sun6i_dma_pause(struct dma_chan *chan)
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: pause\n", &vchan->vc);
spin_lock_irqsave(&sdev->lock, flags);
+ vchan->paused = true;
pchan = vchan->phy;
if (pchan) {
writel(DMA_CHAN_PAUSE_PAUSE,
pchan->base + DMA_CHAN_PAUSE);
+ readl(pchan->base + DMA_CHAN_PAUSE);
} else {
list_del_init(&vchan->node);
}
@@ -960,12 +967,14 @@ static int sun6i_dma_resume(struct dma_chan *chan)
spin_lock(&vchan->vc.lock);
pchan = vchan->phy;
+ vchan->paused = false;
if (pchan) {
writel(DMA_CHAN_PAUSE_RESUME,
pchan->base + DMA_CHAN_PAUSE);
- } else if (!list_empty(&vchan->vc.desc_issued)) {
+ } else if (!list_empty(&vchan->vc.desc_issued) && list_empty(&vchan->node)) {
list_add_tail(&vchan->node, &sdev->pending);
}
+ tasklet_schedule(&sdev->task);
spin_unlock(&vchan->vc.lock);
spin_unlock_irqrestore(&sdev->lock, flags);
@@ -994,6 +1003,7 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
}
vchan->cyclic = false;
+ vchan->paused = false;
vchan_get_all_descriptors(&vchan->vc, &head);
if (pchan) {
@@ -1040,6 +1050,8 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
spin_lock_irqsave(&sdev->lock, flags);
spin_lock(&vchan->vc.lock);
ret = dma_cookie_status(chan, cookie, state);
+ if (ret == DMA_IN_PROGRESS && vchan->paused)
+ ret = DMA_PAUSED;
if (ret == DMA_COMPLETE || !state)
goto out;
pchan = vchan->phy;
@@ -1074,7 +1086,7 @@ static void sun6i_dma_issue_pending(struct dma_chan *chan)
spin_lock(&vchan->vc.lock);
if (vchan_issue_pending(&vchan->vc)) {
- if (!vchan->phy && list_empty(&vchan->node)) {
+ if (!vchan->paused && !vchan->phy && list_empty(&vchan->node)) {
list_add_tail(&vchan->node, &sdev->pending);
tasklet_schedule(&sdev->task);
dev_dbg(dmaengine_chan_dev(chan), "vchan %p: issued\n",
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 8/9] dmaengine: sun6i: synchronize callbacks before releasing channel resources
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
` (6 preceding siblings ...)
2026-10-23 23:38 ` [PATCH v3 7/9] dmaengine: sun6i: track paused virtual channels James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
2026-10-23 23:38 ` [PATCH v3 9/9] dmaengine: sun6i: keep cyclic state with the prepared descriptor James Hilliard
8 siblings, 0 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
The driver has no device_synchronize callback. dmaengine_terminate_sync()
therefore returns after stopping the channel without waiting for the
virtual-channel tasklet. That tasklet may already have removed completed
descriptors from the shared list and be executing their callbacks. For
example, serial8250_release_dma() frees the RX buffer and unmaps the TX
buffer immediately after terminate_sync(), while those callbacks can
still access them.
Use vchan_dma_synchronize() for device_synchronize. Channel termination
excludes the interrupt handler and allocator before detaching the channel,
so they cannot schedule another completion for that transfer. Also stop
the channel and synchronize when freeing its resources.
Keep terminated cyclic descriptors on desc_terminated until synchronization
finishes. vchan_complete() may retain that descriptor outside vc.lock.
Ordinary active descriptors have not published a callback and can still be
freed by terminate_all(). This avoids accumulating descriptors on each
8250 partial-RX flush, which uses the atomic termination API.
Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 25987f550707..b83138745bd1 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -999,12 +999,20 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
if (pchan && pchan->desc) {
struct virt_dma_desc *vd = &pchan->desc->vd;
- vchan_terminate_vdesc(vd);
+ /* A cyclic callback may still hold this descriptor outside vc.lock. */
+ if (vchan->cyclic)
+ vchan_terminate_vdesc(vd);
+ else
+ list_add_tail(&vd->node, &head);
}
vchan->cyclic = false;
vchan->paused = false;
- vchan_get_all_descriptors(&vchan->vc, &head);
+ /* Leave terminated cyclic descriptors for the sleepable synchronize. */
+ list_splice_tail_init(&vchan->vc.desc_allocated, &head);
+ list_splice_tail_init(&vchan->vc.desc_submitted, &head);
+ list_splice_tail_init(&vchan->vc.desc_issued, &head);
+ list_splice_tail_init(&vchan->vc.desc_completed, &head);
if (pchan) {
writel(DMA_CHAN_ENABLE_STOP, pchan->base + DMA_CHAN_ENABLE);
@@ -1103,14 +1111,10 @@ static void sun6i_dma_issue_pending(struct dma_chan *chan)
static void sun6i_dma_free_chan_resources(struct dma_chan *chan)
{
- struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
- unsigned long flags;
-
- spin_lock_irqsave(&sdev->lock, flags);
- list_del_init(&vchan->node);
- spin_unlock_irqrestore(&sdev->lock, flags);
+ sun6i_dma_terminate_all(chan);
+ vchan_dma_synchronize(chan);
vchan_free_chan_resources(&vchan->vc);
}
@@ -1483,6 +1487,7 @@ static int sun6i_dma_probe(struct platform_device *pdev)
sdc->slave.device_pause = sun6i_dma_pause;
sdc->slave.device_resume = sun6i_dma_resume;
sdc->slave.device_terminate_all = sun6i_dma_terminate_all;
+ sdc->slave.device_synchronize = vchan_dma_synchronize;
sdc->slave.src_addr_widths = sdc->cfg->src_addr_widths;
sdc->slave.dst_addr_widths = sdc->cfg->dst_addr_widths;
sdc->slave.directions = BIT(DMA_DEV_TO_MEM) |
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 9/9] dmaengine: sun6i: keep cyclic state with the prepared descriptor
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
` (7 preceding siblings ...)
2026-10-23 23:38 ` [PATCH v3 8/9] dmaengine: sun6i: synchronize callbacks before releasing channel resources James Hilliard
@ 2026-10-23 23:38 ` James Hilliard
8 siblings, 0 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-23 23:38 UTC (permalink / raw)
To: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine
Cc: Hongling Zeng, Chengfeng Ye, Nguyen Minh Tien, dmaengine,
linux-arm-kernel, linux-sunxi, linux-kernel, James Hilliard
Preparing a cyclic transfer sets vchan->cyclic before the descriptor is
submitted. A finite transfer on that channel can then select package
interrupts and take the cyclic callback path instead of completing its
cookie. Merely preparing an unrelated future transfer must not change
the active transfer or the meaning of queued descriptors.
Store the flag in sun6i_desc and consult the active descriptor when
selecting interrupts, completing a transfer and terminating it. Finite
descriptors retain their zero-initialized flag. This also makes callback
retirement depend on the descriptor whose lifetime is being ended.
The shared state was identified during the H616 cyclic UART RX review.
Fixes: a90e173f3faf ("dmaengine: sun6i: Add cyclic capability")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/sun6i-dma.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index b83138745bd1..2152979f72f2 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -191,6 +191,7 @@ struct sun6i_desc {
struct virt_dma_desc vd;
dma_addr_t p_lli;
struct sun6i_dma_lli *v_lli;
+ bool cyclic;
};
struct sun6i_pchan {
@@ -207,7 +208,6 @@ struct sun6i_vchan {
struct sun6i_pchan *phy;
u8 port;
u8 irq_type;
- bool cyclic;
bool paused;
};
@@ -507,7 +507,7 @@ static int sun6i_dma_start_desc(struct sun6i_vchan *vchan)
(DMA_IRQ_HALF | DMA_IRQ_PKG | DMA_IRQ_QUEUE) <<
(irq_offset * DMA_IRQ_CHAN_WIDTH));
- vchan->irq_type = vchan->cyclic ? DMA_IRQ_PKG : DMA_IRQ_QUEUE;
+ vchan->irq_type = pchan->desc->cyclic ? DMA_IRQ_PKG : DMA_IRQ_QUEUE;
irq_val = sun6i_read_irq_en(sdev, irq_reg);
irq_val &= ~((DMA_IRQ_HALF | DMA_IRQ_PKG | DMA_IRQ_QUEUE) <<
@@ -610,7 +610,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
desc = pchan->desc;
if (vchan && desc && (status & vchan->irq_type)) {
spin_lock(&vchan->vc.lock);
- if (vchan->cyclic) {
+ if (desc->cyclic) {
vchan_cyclic_callback(&desc->vd);
} else {
/* Completion transfers ownership to virt-dma. */
@@ -911,7 +911,7 @@ static struct dma_async_tx_descriptor *sun6i_dma_prep_dma_cyclic(
prev->p_lli_next = txd->p_lli; /* cyclic list */
- vchan->cyclic = true;
+ txd->cyclic = true;
return vchan_tx_prep(&vchan->vc, &txd->vd, flags);
@@ -1000,13 +1000,12 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
struct virt_dma_desc *vd = &pchan->desc->vd;
/* A cyclic callback may still hold this descriptor outside vc.lock. */
- if (vchan->cyclic)
+ if (pchan->desc->cyclic)
vchan_terminate_vdesc(vd);
else
list_add_tail(&vd->node, &head);
}
- vchan->cyclic = false;
vchan->paused = false;
/* Leave terminated cyclic descriptors for the sleepable synchronize. */
list_splice_tail_init(&vchan->vc.desc_allocated, &head);
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/9] dmaengine: sun6i: handle the last partial interrupt bank
2026-10-23 23:38 ` [PATCH v3 2/9] dmaengine: sun6i: handle the last partial interrupt bank James Hilliard
@ 2026-10-26 20:57 ` Frank Li
0 siblings, 0 replies; 14+ messages in thread
From: Frank Li @ 2026-10-26 20:57 UTC (permalink / raw)
To: James Hilliard
Cc: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine, Hongling Zeng, Chengfeng Ye,
Nguyen Minh Tien, dmaengine, linux-arm-kernel, linux-sunxi,
linux-kernel, Sashiko
On Fri, Oct 23, 2026 at 05:38:21PM -0600, James Hilliard wrote:
> The number of physical channels need not be a multiple of the number
> covered by an interrupt-status register. H3 has twelve channels and
> eight channels per bank. Rounding down the number of banks leaves its
> channels 8 through 11 without completion handling.
>
> Round up the number of banks to visit, and stop processing the last bank
> at num_pchans so unused status bits cannot index past the physical-channel
> array. Use the controller's configured bank size for both limits and
> the physical-channel index.
>
> Fixes: f008db8c00c1 ("dmaengine: sun6i: Add support for Allwinner H3 (sun8i) variant")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://lore.kernel.org/r/20260706065650.34B131F00A3A@smtp.kernel.org
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/sun6i-dma.c | 8 +++++---
> 1 file changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index 9d94a37fcf10..3a6525dd3d32 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
> @@ -587,12 +587,13 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
> static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> {
> struct sun6i_dma_dev *sdev = dev_id;
> + u32 channels_per_reg = sdev->cfg->num_channels_per_reg;
> struct sun6i_vchan *vchan;
> struct sun6i_pchan *pchan;
> int i, j, ret = IRQ_NONE;
> u32 status;
>
> - for (i = 0; i < sdev->num_pchans / sdev->cfg->num_channels_per_reg; i++) {
> + for (i = 0; i < DIV_ROUND_UP(sdev->num_pchans, channels_per_reg); i++) {
> status = sun6i_read_irq_stat(sdev, i);
> if (!status)
> continue;
> @@ -602,8 +603,9 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
>
> sun6i_write_irq_stat(sdev, i, status);
>
> - for (j = 0; (j < sdev->cfg->num_channels_per_reg) && status; j++) {
> - pchan = sdev->pchans + i * sdev->cfg->num_channels_per_reg + j;
> + for (j = 0; status && j < channels_per_reg &&
> + i * channels_per_reg + j < sdev->num_pchans; j++) {
> + pchan = sdev->pchans + i * channels_per_reg + j;
> vchan = pchan->vchan;
> if (vchan && (status & vchan->irq_type)) {
> if (vchan->cyclic) {
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/9] dmaengine: sun6i: take the device lock before the virtual channel lock
2026-10-23 23:38 ` [PATCH v3 3/9] dmaengine: sun6i: take the device lock before the virtual channel lock James Hilliard
@ 2026-10-26 21:05 ` Frank Li
0 siblings, 0 replies; 14+ messages in thread
From: Frank Li @ 2026-10-26 21:05 UTC (permalink / raw)
To: James Hilliard
Cc: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine, Hongling Zeng, Chengfeng Ye,
Nguyen Minh Tien, dmaengine, linux-arm-kernel, linux-sunxi,
linux-kernel
On Fri, Oct 23, 2026 at 05:38:22PM -0600, James Hilliard wrote:
> The pending queue is protected by sdev->lock, while virt-dma uses vc.lock
> for descriptor lists and cookies. issue_pending() and resume() currently
> take the device lock inside the virtual-channel lock when queueing work.
>
> Take sdev->lock first in both paths, and inspect the physical-channel
> assignment after acquiring the locks. This establishes the nesting order
> needed to keep allocation and descriptor startup in one critical section.
> virt-dma preparation, submission and callback processing continue to use
> vc.lock independently.
>
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/sun6i-dma.c | 21 ++++++++++-----------
> 1 file changed, 10 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index 3a6525dd3d32..435d7daa1c0f 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
> @@ -952,23 +952,24 @@ static int sun6i_dma_resume(struct dma_chan *chan)
> {
> struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
> struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> - struct sun6i_pchan *pchan = vchan->phy;
> + struct sun6i_pchan *pchan;
> unsigned long flags;
>
> dev_dbg(dmaengine_chan_dev(chan), "vchan %p: resume\n", &vchan->vc);
>
> - spin_lock_irqsave(&vchan->vc.lock, flags);
> + spin_lock_irqsave(&sdev->lock, flags);
> + spin_lock(&vchan->vc.lock);
> + pchan = vchan->phy;
>
> if (pchan) {
> writel(DMA_CHAN_PAUSE_RESUME,
> pchan->base + DMA_CHAN_PAUSE);
> } else if (!list_empty(&vchan->vc.desc_issued)) {
> - spin_lock(&sdev->lock);
> list_add_tail(&vchan->node, &sdev->pending);
> - spin_unlock(&sdev->lock);
> }
>
> - spin_unlock_irqrestore(&vchan->vc.lock, flags);
> + spin_unlock(&vchan->vc.lock);
> + spin_unlock_irqrestore(&sdev->lock, flags);
>
> return 0;
> }
> @@ -1057,25 +1058,23 @@ static void sun6i_dma_issue_pending(struct dma_chan *chan)
> struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> unsigned long flags;
>
> - spin_lock_irqsave(&vchan->vc.lock, flags);
> + spin_lock_irqsave(&sdev->lock, flags);
> + spin_lock(&vchan->vc.lock);
>
> if (vchan_issue_pending(&vchan->vc)) {
> - spin_lock(&sdev->lock);
> -
> if (!vchan->phy && list_empty(&vchan->node)) {
> list_add_tail(&vchan->node, &sdev->pending);
> tasklet_schedule(&sdev->task);
> dev_dbg(dmaengine_chan_dev(chan), "vchan %p: issued\n",
> &vchan->vc);
> }
> -
> - spin_unlock(&sdev->lock);
> } else {
> dev_dbg(dmaengine_chan_dev(chan), "vchan %p: nothing to issue\n",
> &vchan->vc);
> }
>
> - spin_unlock_irqrestore(&vchan->vc.lock, flags);
> + spin_unlock(&vchan->vc.lock);
> + spin_unlock_irqrestore(&sdev->lock, flags);
> }
>
> static void sun6i_dma_free_chan_resources(struct dma_chan *chan)
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 4/9] dmaengine: sun6i: serialize physical channel ownership
2026-10-23 23:38 ` [PATCH v3 4/9] dmaengine: sun6i: serialize physical channel ownership James Hilliard
@ 2026-10-26 21:14 ` Frank Li
2026-10-26 21:42 ` James Hilliard
0 siblings, 1 reply; 14+ messages in thread
From: Frank Li @ 2026-10-26 21:14 UTC (permalink / raw)
To: James Hilliard
Cc: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine, Hongling Zeng, Chengfeng Ye,
Nguyen Minh Tien, dmaengine, linux-arm-kernel, linux-sunxi,
linux-kernel, Sashiko
On Fri, Oct 23, 2026 at 05:38:23PM -0600, James Hilliard wrote:
> The allocator changes vchan->phy and pchan->vchan under sdev->lock, but
> termination reads the physical channel before taking either lock. The IRQ
> handler also takes an unprotected owner snapshot. Termination can miss a
> new assignment or detach an owner while an interrupt is about to complete
> its descriptor. A sampled IRQ can also be applied after reassignment.
>
> Protect physical-channel ownership with sdev->lock across allocation,
> interrupt handling, pause, termination and residue queries. Read and
> acknowledge IRQ status under that lock, and allocate and start each pending
> channel in the same critical section. This also serializes updates to the
> shared interrupt-enable registers.
>
> Nest vc.lock inside the device lock when accessing descriptor lists or
> updating cookies, including cyclic notifications. virt-dma takes vc.lock
> independently when preparing, submitting and completing descriptors, so
> the device lock does not replace it. Client callbacks run outside both
> locks in the virtual-channel tasklet.
>
> Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://lore.kernel.org/r/20260706065650.34B131F00A3A@smtp.kernel.org
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
> drivers/dma/sun6i-dma.c | 67 ++++++++++++++++++++++++-------------------------
> 1 file changed, 33 insertions(+), 34 deletions(-)
>
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index 435d7daa1c0f..d3a63c866645 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
> @@ -217,6 +217,10 @@ struct sun6i_dma_dev {
> struct clk *clk;
> struct clk *clk_mbus;
> int irq;
> + /*
> + * Physical channels, pending list and registers; nests outside vc.lock.
> + * virt-dma also takes vc.lock independently for its lists and cookies.
> + */
> spinlock_t lock;
> struct reset_control *rstc;
> struct tasklet_struct task;
> @@ -524,11 +528,11 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
> struct sun6i_dma_dev *sdev = from_tasklet(sdev, t, task);
> struct sun6i_vchan *vchan;
> struct sun6i_pchan *pchan;
> - unsigned int pchan_alloc = 0;
> unsigned int pchan_idx;
>
> + spin_lock_irq(&sdev->lock);
> list_for_each_entry(vchan, &sdev->slave.channels, vc.chan.device_node) {
> - spin_lock_irq(&vchan->vc.lock);
> + spin_lock(&vchan->vc.lock);
>
> pchan = vchan->phy;
>
> @@ -545,10 +549,9 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
> pchan->vchan = NULL;
> }
> }
> - spin_unlock_irq(&vchan->vc.lock);
> + spin_unlock(&vchan->vc.lock);
> }
>
> - spin_lock_irq(&sdev->lock);
> for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
> pchan = &sdev->pchans[pchan_idx];
>
> @@ -557,31 +560,20 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
>
> vchan = list_first_entry(&sdev->pending,
> struct sun6i_vchan, node);
> + spin_lock(&vchan->vc.lock);
>
> /* Remove from pending channels */
> list_del_init(&vchan->node);
> - pchan_alloc |= BIT(pchan_idx);
>
> /* Mark this channel allocated */
> pchan->vchan = vchan;
> vchan->phy = pchan;
> dev_dbg(sdev->slave.dev, "pchan %u: alloc vchan %p\n",
> pchan->idx, &vchan->vc);
> + sun6i_dma_start_desc(vchan);
> + spin_unlock(&vchan->vc.lock);
> }
> spin_unlock_irq(&sdev->lock);
> -
> - for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
> - if (!(pchan_alloc & BIT(pchan_idx)))
> - continue;
> -
> - pchan = sdev->pchans + pchan_idx;
> - vchan = pchan->vchan;
> - if (vchan) {
> - spin_lock_irq(&vchan->vc.lock);
> - sun6i_dma_start_desc(vchan);
> - spin_unlock_irq(&vchan->vc.lock);
> - }
> - }
> }
>
> static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> @@ -590,9 +582,11 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> u32 channels_per_reg = sdev->cfg->num_channels_per_reg;
> struct sun6i_vchan *vchan;
> struct sun6i_pchan *pchan;
> + unsigned long flags;
> int i, j, ret = IRQ_NONE;
> u32 status;
>
> + spin_lock_irqsave(&sdev->lock, flags);
> for (i = 0; i < DIV_ROUND_UP(sdev->num_pchans, channels_per_reg); i++) {
> status = sun6i_read_irq_stat(sdev, i);
> if (!status)
> @@ -608,14 +602,14 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> pchan = sdev->pchans + i * channels_per_reg + j;
> vchan = pchan->vchan;
> if (vchan && (status & vchan->irq_type)) {
> + spin_lock(&vchan->vc.lock);
> if (vchan->cyclic) {
> vchan_cyclic_callback(&pchan->desc->vd);
> } else {
> - spin_lock(&vchan->vc.lock);
> vchan_cookie_complete(&pchan->desc->vd);
> pchan->done = pchan->desc;
> - spin_unlock(&vchan->vc.lock);
> }
> + spin_unlock(&vchan->vc.lock);
> }
>
> status = status >> DMA_IRQ_CHAN_WIDTH;
> @@ -625,6 +619,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> tasklet_schedule(&sdev->task);
> ret = IRQ_HANDLED;
> }
> + spin_unlock_irqrestore(&sdev->lock, flags);
>
> return ret;
> }
> @@ -931,19 +926,20 @@ static int sun6i_dma_pause(struct dma_chan *chan)
> {
> struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
> struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> - struct sun6i_pchan *pchan = vchan->phy;
> + struct sun6i_pchan *pchan;
> unsigned long flags;
>
> dev_dbg(dmaengine_chan_dev(chan), "vchan %p: pause\n", &vchan->vc);
>
> + spin_lock_irqsave(&sdev->lock, flags);
> + pchan = vchan->phy;
> if (pchan) {
> writel(DMA_CHAN_PAUSE_PAUSE,
> pchan->base + DMA_CHAN_PAUSE);
> } else {
> - spin_lock_irqsave(&sdev->lock, flags);
> list_del_init(&vchan->node);
suppose need vchan lock here?
Frank
> - spin_unlock_irqrestore(&sdev->lock, flags);
> }
> + spin_unlock_irqrestore(&sdev->lock, flags);
>
> return 0;
> }
> @@ -978,15 +974,14 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
> {
> struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
> struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> - struct sun6i_pchan *pchan = vchan->phy;
> + struct sun6i_pchan *pchan;
> unsigned long flags;
> LIST_HEAD(head);
>
> spin_lock_irqsave(&sdev->lock, flags);
> + spin_lock(&vchan->vc.lock);
> + pchan = vchan->phy;
> list_del_init(&vchan->node);
> - spin_unlock_irqrestore(&sdev->lock, flags);
> -
> - spin_lock_irqsave(&vchan->vc.lock, flags);
>
> if (pchan && pchan->desc && pchan->desc != pchan->done) {
> struct virt_dma_desc *vd = &pchan->desc->vd;
> @@ -1007,7 +1002,8 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
> pchan->done = NULL;
> }
>
> - spin_unlock_irqrestore(&vchan->vc.lock, flags);
> + spin_unlock(&vchan->vc.lock);
> + spin_unlock_irqrestore(&sdev->lock, flags);
>
> vchan_dma_desc_free_list(&vchan->vc, &head);
>
> @@ -1018,8 +1014,9 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
> dma_cookie_t cookie,
> struct dma_tx_state *state)
> {
> + struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
> struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> - struct sun6i_pchan *pchan = vchan->phy;
> + struct sun6i_pchan *pchan;
> struct sun6i_dma_lli *lli;
> struct virt_dma_desc *vd;
> struct sun6i_desc *txd;
> @@ -1027,11 +1024,12 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
> unsigned long flags;
> size_t bytes = 0;
>
> + spin_lock_irqsave(&sdev->lock, flags);
> + spin_lock(&vchan->vc.lock);
> ret = dma_cookie_status(chan, cookie, state);
> if (ret == DMA_COMPLETE || !state)
> - return ret;
> -
> - spin_lock_irqsave(&vchan->vc.lock, flags);
> + goto out;
> + pchan = vchan->phy;
>
> vd = vchan_find_desc(&vchan->vc, cookie);
> txd = to_sun6i_desc(&vd->tx);
> @@ -1045,9 +1043,10 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
> bytes = sun6i_get_chan_size(pchan);
> }
>
> - spin_unlock_irqrestore(&vchan->vc.lock, flags);
> -
> dma_set_residue(state, bytes);
> +out:
> + spin_unlock(&vchan->vc.lock);
> + spin_unlock_irqrestore(&sdev->lock, flags);
>
> return ret;
> }
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 4/9] dmaengine: sun6i: serialize physical channel ownership
2026-10-26 21:14 ` Frank Li
@ 2026-10-26 21:42 ` James Hilliard
0 siblings, 0 replies; 14+ messages in thread
From: James Hilliard @ 2026-10-26 21:42 UTC (permalink / raw)
To: Frank Li
Cc: Vinod Koul, Frank Li, Chen-Yu Tsai, Jernej Skrabec,
Samuel Holland, Arnd Bergmann, Maxime Ripard, Jens Kuske,
Jean-Francois Moine, Hongling Zeng, Chengfeng Ye,
Nguyen Minh Tien, dmaengine, linux-arm-kernel, linux-sunxi,
linux-kernel, Sashiko
On Mon, Oct 26, 2026 at 3:14 PM Frank Li <Frank.li@oss.nxp.com> wrote:
>
> On Fri, Oct 23, 2026 at 05:38:23PM -0600, James Hilliard wrote:
> > The allocator changes vchan->phy and pchan->vchan under sdev->lock, but
> > termination reads the physical channel before taking either lock. The IRQ
> > handler also takes an unprotected owner snapshot. Termination can miss a
> > new assignment or detach an owner while an interrupt is about to complete
> > its descriptor. A sampled IRQ can also be applied after reassignment.
> >
> > Protect physical-channel ownership with sdev->lock across allocation,
> > interrupt handling, pause, termination and residue queries. Read and
> > acknowledge IRQ status under that lock, and allocate and start each pending
> > channel in the same critical section. This also serializes updates to the
> > shared interrupt-enable registers.
> >
> > Nest vc.lock inside the device lock when accessing descriptor lists or
> > updating cookies, including cyclic notifications. virt-dma takes vc.lock
> > independently when preparing, submitting and completing descriptors, so
> > the device lock does not replace it. Client callbacks run outside both
> > locks in the virtual-channel tasklet.
> >
> > Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller")
> > Reported-by: Sashiko <sashiko-bot@kernel.org>
> > Closes: https://lore.kernel.org/r/20260706065650.34B131F00A3A@smtp.kernel.org
> > Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> > ---
> > drivers/dma/sun6i-dma.c | 67 ++++++++++++++++++++++++-------------------------
> > 1 file changed, 33 insertions(+), 34 deletions(-)
> >
> > diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> > index 435d7daa1c0f..d3a63c866645 100644
> > --- a/drivers/dma/sun6i-dma.c
> > +++ b/drivers/dma/sun6i-dma.c
> > @@ -217,6 +217,10 @@ struct sun6i_dma_dev {
> > struct clk *clk;
> > struct clk *clk_mbus;
> > int irq;
> > + /*
> > + * Physical channels, pending list and registers; nests outside vc.lock.
> > + * virt-dma also takes vc.lock independently for its lists and cookies.
> > + */
> > spinlock_t lock;
> > struct reset_control *rstc;
> > struct tasklet_struct task;
> > @@ -524,11 +528,11 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
> > struct sun6i_dma_dev *sdev = from_tasklet(sdev, t, task);
> > struct sun6i_vchan *vchan;
> > struct sun6i_pchan *pchan;
> > - unsigned int pchan_alloc = 0;
> > unsigned int pchan_idx;
> >
> > + spin_lock_irq(&sdev->lock);
> > list_for_each_entry(vchan, &sdev->slave.channels, vc.chan.device_node) {
> > - spin_lock_irq(&vchan->vc.lock);
> > + spin_lock(&vchan->vc.lock);
> >
> > pchan = vchan->phy;
> >
> > @@ -545,10 +549,9 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
> > pchan->vchan = NULL;
> > }
> > }
> > - spin_unlock_irq(&vchan->vc.lock);
> > + spin_unlock(&vchan->vc.lock);
> > }
> >
> > - spin_lock_irq(&sdev->lock);
> > for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
> > pchan = &sdev->pchans[pchan_idx];
> >
> > @@ -557,31 +560,20 @@ static void sun6i_dma_tasklet(struct tasklet_struct *t)
> >
> > vchan = list_first_entry(&sdev->pending,
> > struct sun6i_vchan, node);
> > + spin_lock(&vchan->vc.lock);
> >
> > /* Remove from pending channels */
> > list_del_init(&vchan->node);
> > - pchan_alloc |= BIT(pchan_idx);
> >
> > /* Mark this channel allocated */
> > pchan->vchan = vchan;
> > vchan->phy = pchan;
> > dev_dbg(sdev->slave.dev, "pchan %u: alloc vchan %p\n",
> > pchan->idx, &vchan->vc);
> > + sun6i_dma_start_desc(vchan);
> > + spin_unlock(&vchan->vc.lock);
> > }
> > spin_unlock_irq(&sdev->lock);
> > -
> > - for (pchan_idx = 0; pchan_idx < sdev->num_pchans; pchan_idx++) {
> > - if (!(pchan_alloc & BIT(pchan_idx)))
> > - continue;
> > -
> > - pchan = sdev->pchans + pchan_idx;
> > - vchan = pchan->vchan;
> > - if (vchan) {
> > - spin_lock_irq(&vchan->vc.lock);
> > - sun6i_dma_start_desc(vchan);
> > - spin_unlock_irq(&vchan->vc.lock);
> > - }
> > - }
> > }
> >
> > static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> > @@ -590,9 +582,11 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> > u32 channels_per_reg = sdev->cfg->num_channels_per_reg;
> > struct sun6i_vchan *vchan;
> > struct sun6i_pchan *pchan;
> > + unsigned long flags;
> > int i, j, ret = IRQ_NONE;
> > u32 status;
> >
> > + spin_lock_irqsave(&sdev->lock, flags);
> > for (i = 0; i < DIV_ROUND_UP(sdev->num_pchans, channels_per_reg); i++) {
> > status = sun6i_read_irq_stat(sdev, i);
> > if (!status)
> > @@ -608,14 +602,14 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> > pchan = sdev->pchans + i * channels_per_reg + j;
> > vchan = pchan->vchan;
> > if (vchan && (status & vchan->irq_type)) {
> > + spin_lock(&vchan->vc.lock);
> > if (vchan->cyclic) {
> > vchan_cyclic_callback(&pchan->desc->vd);
> > } else {
> > - spin_lock(&vchan->vc.lock);
> > vchan_cookie_complete(&pchan->desc->vd);
> > pchan->done = pchan->desc;
> > - spin_unlock(&vchan->vc.lock);
> > }
> > + spin_unlock(&vchan->vc.lock);
> > }
> >
> > status = status >> DMA_IRQ_CHAN_WIDTH;
> > @@ -625,6 +619,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> > tasklet_schedule(&sdev->task);
> > ret = IRQ_HANDLED;
> > }
> > + spin_unlock_irqrestore(&sdev->lock, flags);
> >
> > return ret;
> > }
> > @@ -931,19 +926,20 @@ static int sun6i_dma_pause(struct dma_chan *chan)
> > {
> > struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
> > struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> > - struct sun6i_pchan *pchan = vchan->phy;
> > + struct sun6i_pchan *pchan;
> > unsigned long flags;
> >
> > dev_dbg(dmaengine_chan_dev(chan), "vchan %p: pause\n", &vchan->vc);
> >
> > + spin_lock_irqsave(&sdev->lock, flags);
> > + pchan = vchan->phy;
> > if (pchan) {
> > writel(DMA_CHAN_PAUSE_PAUSE,
> > pchan->base + DMA_CHAN_PAUSE);
> > } else {
> > - spin_lock_irqsave(&sdev->lock, flags);
> > list_del_init(&vchan->node);
>
> suppose need vchan lock here?
vchan->node links the channel into sdev->pending; it is separate from
virt-dma's descriptor-list nodes. All runtime accesses to that pending
list are protected by sdev->lock.
The same lock protects vchan->phy, so it also keeps the physical-channel
assignment stable while pause() checks it and either pauses the hardware
or removes the channel from the pending list.
resume() additionally needs vc.lock because it inspects vc.desc_issued,
and issue_pending() needs it to move descriptors between virt-dma lists.
pause() does not access those lists or cookies, so taking vc.lock here
would not protect any additional state.
>
> Frank
> > - spin_unlock_irqrestore(&sdev->lock, flags);
> > }
> > + spin_unlock_irqrestore(&sdev->lock, flags);
> >
> > return 0;
> > }
> > @@ -978,15 +974,14 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
> > {
> > struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
> > struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> > - struct sun6i_pchan *pchan = vchan->phy;
> > + struct sun6i_pchan *pchan;
> > unsigned long flags;
> > LIST_HEAD(head);
> >
> > spin_lock_irqsave(&sdev->lock, flags);
> > + spin_lock(&vchan->vc.lock);
> > + pchan = vchan->phy;
> > list_del_init(&vchan->node);
> > - spin_unlock_irqrestore(&sdev->lock, flags);
> > -
> > - spin_lock_irqsave(&vchan->vc.lock, flags);
> >
> > if (pchan && pchan->desc && pchan->desc != pchan->done) {
> > struct virt_dma_desc *vd = &pchan->desc->vd;
> > @@ -1007,7 +1002,8 @@ static int sun6i_dma_terminate_all(struct dma_chan *chan)
> > pchan->done = NULL;
> > }
> >
> > - spin_unlock_irqrestore(&vchan->vc.lock, flags);
> > + spin_unlock(&vchan->vc.lock);
> > + spin_unlock_irqrestore(&sdev->lock, flags);
> >
> > vchan_dma_desc_free_list(&vchan->vc, &head);
> >
> > @@ -1018,8 +1014,9 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
> > dma_cookie_t cookie,
> > struct dma_tx_state *state)
> > {
> > + struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
> > struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
> > - struct sun6i_pchan *pchan = vchan->phy;
> > + struct sun6i_pchan *pchan;
> > struct sun6i_dma_lli *lli;
> > struct virt_dma_desc *vd;
> > struct sun6i_desc *txd;
> > @@ -1027,11 +1024,12 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
> > unsigned long flags;
> > size_t bytes = 0;
> >
> > + spin_lock_irqsave(&sdev->lock, flags);
> > + spin_lock(&vchan->vc.lock);
> > ret = dma_cookie_status(chan, cookie, state);
> > if (ret == DMA_COMPLETE || !state)
> > - return ret;
> > -
> > - spin_lock_irqsave(&vchan->vc.lock, flags);
> > + goto out;
> > + pchan = vchan->phy;
> >
> > vd = vchan_find_desc(&vchan->vc, cookie);
> > txd = to_sun6i_desc(&vd->tx);
> > @@ -1045,9 +1043,10 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan,
> > bytes = sun6i_get_chan_size(pchan);
> > }
> >
> > - spin_unlock_irqrestore(&vchan->vc.lock, flags);
> > -
> > dma_set_residue(state, bytes);
> > +out:
> > + spin_unlock(&vchan->vc.lock);
> > + spin_unlock_irqrestore(&sdev->lock, flags);
> >
> > return ret;
> > }
> >
> > --
> > 2.53.0
> >
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-26 21:42 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-23 23:38 [PATCH v3 0/9] dmaengine: sun6i: fix interrupt routing and channel lifetime James Hilliard
2026-10-23 23:38 ` [PATCH v3 1/9] dmaengine: sun6i: fix physical channel index in interrupt handler James Hilliard
2026-10-23 23:38 ` [PATCH v3 2/9] dmaengine: sun6i: handle the last partial interrupt bank James Hilliard
2026-10-26 20:57 ` Frank Li
2026-10-23 23:38 ` [PATCH v3 3/9] dmaengine: sun6i: take the device lock before the virtual channel lock James Hilliard
2026-10-26 21:05 ` Frank Li
2026-10-23 23:38 ` [PATCH v3 4/9] dmaengine: sun6i: serialize physical channel ownership James Hilliard
2026-10-26 21:14 ` Frank Li
2026-10-26 21:42 ` James Hilliard
2026-10-23 23:38 ` [PATCH v3 5/9] dmaengine: sun6i: detach completed descriptors before invoking callbacks James Hilliard
2026-10-23 23:38 ` [PATCH v3 6/9] dmaengine: sun6i: retire interrupt state before reusing physical channels James Hilliard
2026-10-23 23:38 ` [PATCH v3 7/9] dmaengine: sun6i: track paused virtual channels James Hilliard
2026-10-23 23:38 ` [PATCH v3 8/9] dmaengine: sun6i: synchronize callbacks before releasing channel resources James Hilliard
2026-10-23 23:38 ` [PATCH v3 9/9] dmaengine: sun6i: keep cyclic state with the prepared descriptor 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®