* [PATCH 1/3] dmaengine: dw-axi-dmac: poll the correct enable bit during termination
2026-11-09 5:02 [PATCH 0/3] dmaengine: dw-axi-dmac: fix termination and pause acknowledgment James Hilliard
@ 2026-11-09 5:02 ` James Hilliard
2026-11-09 22:49 ` Andy Shevchenko
2026-11-09 5:02 ` [PATCH 2/3] dmaengine: dw-axi-dmac: retain descriptors when termination times out James Hilliard
2026-11-09 5:02 ` [PATCH 3/3] dmaengine: dw-axi-dmac: preserve suspension status until pause observes it James Hilliard
2 siblings, 1 reply; 5+ messages in thread
From: James Hilliard @ 2026-11-09 5:02 UTC (permalink / raw)
To: Eugeniy Paltsev, Vinod Koul, Frank Li, Sergey Khimich,
Andy Shevchenko, Sia Jee Heng
Cc: dmaengine, linux-kernel, James Hilliard, stable
For channels 16 through 31, the channel enable bits occupy bits 32 through
47 of the extended DMAC_CHEN register. dma_chan_terminate_all() only reads
the low word and tests BIT(chan->id), so it can conclude that an upper-bank
channel has stopped while its actual enable bit remains set. Descriptors
can then be freed while hardware still owns them.
Poll axi_chan_is_hw_enable(), which already handles both register banks
and the smaller channel layouts, instead of constructing another mask.
Fixes: 495e18b16e3d ("dmaengine: dw-axi-dmac: Add support DMAX_NUM_CHANNELS > 16")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index 7520c78f55cc..49f65486e862 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -1181,16 +1181,15 @@ static irqreturn_t dw_axi_dma_interrupt(int irq, void *dev_id)
static int dma_chan_terminate_all(struct dma_chan *dchan)
{
struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);
- u32 chan_active = BIT(chan->id) << DMAC_CHAN_EN_SHIFT;
unsigned long flags;
- u32 val;
+ bool enabled;
int ret;
LIST_HEAD(head);
axi_chan_disable(chan);
- ret = readl_poll_timeout_atomic(chan->chip->regs + DMAC_CHEN, val,
- !(val & chan_active), 1000, 50000);
+ ret = read_poll_timeout_atomic(axi_chan_is_hw_enable, enabled, !enabled,
+ 1000, 50000, false, chan);
if (ret == -ETIMEDOUT)
dev_warn(dmaengine_chan_dev(dchan),
"%s failed to stop\n", axi_chan_name(chan));
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH 1/3] dmaengine: dw-axi-dmac: poll the correct enable bit during termination
2026-11-09 5:02 ` [PATCH 1/3] dmaengine: dw-axi-dmac: poll the correct enable bit during termination James Hilliard
@ 2026-11-09 22:49 ` Andy Shevchenko
0 siblings, 0 replies; 5+ messages in thread
From: Andy Shevchenko @ 2026-11-09 22:49 UTC (permalink / raw)
To: James Hilliard
Cc: Eugeniy Paltsev, Vinod Koul, Frank Li, Sergey Khimich,
Sia Jee Heng, dmaengine, linux-kernel, stable
On Sun, Nov 08, 2026 at 10:02:36PM -0700, James Hilliard wrote:
> For channels 16 through 31, the channel enable bits occupy bits 32 through
> 47 of the extended DMAC_CHEN register. dma_chan_terminate_all() only reads
> the low word and tests BIT(chan->id), so it can conclude that an upper-bank
> channel has stopped while its actual enable bit remains set. Descriptors
> can then be freed while hardware still owns them.
>
> Poll axi_chan_is_hw_enable(), which already handles both register banks
> and the smaller channel layouts, instead of constructing another mask.
...
> - ret = readl_poll_timeout_atomic(chan->chip->regs + DMAC_CHEN, val,
> - !(val & chan_active), 1000, 50000);
> + ret = read_poll_timeout_atomic(axi_chan_is_hw_enable, enabled, !enabled,
> + 1000, 50000, false, chan);
With a single argument it's readx_poll_timeout_atomic().
While at it, you may use 1 * USEC_PER_MSEC and 50 * USEC_PER_MSEC instead of
plain numbers.
ret = readx_poll_timeout_atomic(axi_chan_is_hw_enable, chan,
enabled, !enabled,
1 * USEC_PER_MSEC, 50 * USEC_PER_MSEC);
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/3] dmaengine: dw-axi-dmac: retain descriptors when termination times out
2026-11-09 5:02 [PATCH 0/3] dmaengine: dw-axi-dmac: fix termination and pause acknowledgment James Hilliard
2026-11-09 5:02 ` [PATCH 1/3] dmaengine: dw-axi-dmac: poll the correct enable bit during termination James Hilliard
@ 2026-11-09 5:02 ` James Hilliard
2026-11-09 5:02 ` [PATCH 3/3] dmaengine: dw-axi-dmac: preserve suspension status until pause observes it James Hilliard
2 siblings, 0 replies; 5+ messages in thread
From: James Hilliard @ 2026-11-09 5:02 UTC (permalink / raw)
To: Eugeniy Paltsev, Vinod Koul, Frank Li, Sergey Khimich,
Andy Shevchenko, Sia Jee Heng
Cc: dmaengine, linux-kernel, James Hilliard, stable
dma_chan_terminate_all() warns when the channel enable bit does not clear,
but then frees the descriptors and returns success. Its synchronize
callback only drains virtual-channel callbacks, so terminate_sync() can
also report success while hardware still accesses the transfer.
Return the polling error before detaching descriptors or changing the
peripheral routing. This keeps the transfer resources available while
the channel remains enabled and lets the caller retry termination without
treating the timeout as a successful stop.
Hold the channel lock across disabling, polling and descriptor detachment
so an interrupt handler cannot restart the channel between the stop check
and cleanup.
Fixes: 1deb96c0fa58 ("dmaegine: dw-axi-dmac: Support device_prep_dma_cyclic()")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index 49f65486e862..de092f190528 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -1186,21 +1186,24 @@ static int dma_chan_terminate_all(struct dma_chan *dchan)
int ret;
LIST_HEAD(head);
+ spin_lock_irqsave(&chan->vc.lock, flags);
+
axi_chan_disable(chan);
ret = read_poll_timeout_atomic(axi_chan_is_hw_enable, enabled, !enabled,
1000, 50000, false, chan);
- if (ret == -ETIMEDOUT)
+ if (ret) {
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
dev_warn(dmaengine_chan_dev(dchan),
"%s failed to stop\n", axi_chan_name(chan));
+ return ret;
+ }
if (chan->direction != DMA_MEM_TO_MEM)
dw_axi_dma_set_hw_channel(chan, false);
if (chan->direction == DMA_MEM_TO_DEV)
dw_axi_dma_set_byte_halfword(chan, false);
- spin_lock_irqsave(&chan->vc.lock, flags);
-
vchan_get_all_descriptors(&chan->vc, &head);
chan->cyclic = false;
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 3/3] dmaengine: dw-axi-dmac: preserve suspension status until pause observes it
2026-11-09 5:02 [PATCH 0/3] dmaengine: dw-axi-dmac: fix termination and pause acknowledgment James Hilliard
2026-11-09 5:02 ` [PATCH 1/3] dmaengine: dw-axi-dmac: poll the correct enable bit during termination James Hilliard
2026-11-09 5:02 ` [PATCH 2/3] dmaengine: dw-axi-dmac: retain descriptors when termination times out James Hilliard
@ 2026-11-09 5:02 ` James Hilliard
2 siblings, 0 replies; 5+ messages in thread
From: James Hilliard @ 2026-11-09 5:02 UTC (permalink / raw)
To: Eugeniy Paltsev, Vinod Koul, Frank Li, Sergey Khimich,
Andy Shevchenko, Sia Jee Heng
Cc: dmaengine, linux-kernel, James Hilliard, stable
dma_chan_pause() clears SUSPENDED even when polling times out. Hardware
can set that status after the last poll, including during the final
delay, and the unconditional clear then discards an acknowledgment the
caller never observed. Another channel's interrupt can also consume the
status because the shared IRQ handler clears every channel's status
before taking its lock. Masking the suspend interrupt signal does not
prevent either path from clearing the status used by the polling code.
Leave SUSPENDED to the pause path and acknowledge it only after a
successful poll. A timed-out pause can then be retried without losing a
late acknowledgment. Mark the channel paused only after confirmation,
and let repeated pause calls succeed while it remains paused.
Clear the saved state and any old suspension status when enabling a
channel or completing termination, so a later transfer must obtain its
own acknowledgment. These state changes are serialized with pause by
the channel lock.
Fixes: 1fe20f1b8454 ("dmaengine: Introduce DW AXI DMAC driver")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index de092f190528..33340f0e7462 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -226,6 +226,9 @@ static inline void axi_chan_enable(struct axi_dma_chan *chan)
{
u64 val;
+ axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
+ chan->is_paused = false;
+
if (chan->chip->dw->hdata->nr_channels >= DMAC_CHAN_16) {
val = axi_dma_ioread64(chan->chip, DMAC_CHEN);
if (chan->id >= DMAC_CHAN_16) {
@@ -1161,6 +1164,8 @@ static irqreturn_t dw_axi_dma_interrupt(int irq, void *dev_id)
for (i = 0; i < dw->hdata->nr_channels; i++) {
chan = &dw->chan[i];
status = axi_chan_irq_read(chan);
+ /* Suspension status is consumed by dma_chan_pause(). */
+ status &= ~DWAXIDMAC_IRQ_SUSPENDED;
axi_chan_irq_clear(chan, status);
dev_vdbg(chip->dev, "%s %u IRQ status: 0x%08x\n",
@@ -1206,6 +1211,8 @@ static int dma_chan_terminate_all(struct dma_chan *dchan)
vchan_get_all_descriptors(&chan->vc, &head);
+ axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
+ chan->is_paused = false;
chan->cyclic = false;
spin_unlock_irqrestore(&chan->vc.lock, flags);
@@ -1225,6 +1232,9 @@ static int dma_chan_pause(struct dma_chan *dchan)
spin_lock_irqsave(&chan->vc.lock, flags);
+ if (chan->is_paused)
+ goto out;
+
if (chan->chip->dw->hdata->nr_channels >= DMAC_CHAN_16) {
val = axi_dma_ioread64(chan->chip, DMAC_CHSUSPREG);
if (chan->id >= DMAC_CHAN_16) {
@@ -1258,10 +1268,12 @@ static int dma_chan_pause(struct dma_chan *dchan)
udelay(2);
} while (--timeout);
- axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
-
- chan->is_paused = true;
+ if (timeout) {
+ axi_chan_irq_clear(chan, DWAXIDMAC_IRQ_SUSPENDED);
+ chan->is_paused = true;
+ }
+out:
spin_unlock_irqrestore(&chan->vc.lock, flags);
return timeout ? 0 : -EAGAIN;
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread