[PATCH v4 4/9] dmaengine: dw-edma: Complete descriptors before pausing

From: Koichiro Den

Date: Thu Jul 16 2026 - 12:00:34 EST


If PAUSE is requested while the final chunk of a descriptor is in
flight, the DONE interrupt takes the PAUSE path without checking whether
the descriptor has been depleted. The depleted descriptor remains on the
issued list and the channel enters EDMA_ST_PAUSE.

On resume, dw_edma_start_transfer() finds the descriptor but no chunk to
start and returns 0. The caller ignores that result and leaves the
channel stuck in EDMA_ST_BUSY with no transfer running.

Check for descriptor completion before acknowledging PAUSE. If there is
no work to start on resume, leave the channel idle. Also ignore DONE
interrupts while the channel is paused so a stale or repeated interrupt
cannot change its state or start queued work.

Fixes: e63d79d1ffcd ("dmaengine: Add Synopsys eDMA IP core driver")
Cc: stable@xxxxxxxxxxxxxxx
Reviewed-by: Frank Li <Frank.Li@xxxxxxx>
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v4:
- Collect Frank's Reviewed-by tag; no code changes.

drivers/dma/dw-edma/dw-edma-core.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index daf70df59e07..ac40a832a9c7 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -327,7 +327,8 @@ static int dw_edma_device_resume(struct dma_chan *dchan)
err = -EPERM;
} else {
chan->status = EDMA_ST_BUSY;
- dw_edma_start_transfer(chan);
+ if (!dw_edma_start_transfer(chan))
+ chan->status = EDMA_ST_IDLE;
}

return err;
@@ -687,10 +688,16 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
unsigned long flags;

spin_lock_irqsave(&chan->vc.lock, flags);
+ if (chan->status == EDMA_ST_PAUSE) {
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+ return;
+ }
+
vd = vchan_next_desc(&chan->vc);
if (vd) {
switch (chan->request) {
case EDMA_REQ_NONE:
+ case EDMA_REQ_PAUSE:
desc = vd2dw_edma_desc(vd);
if (!desc->chunks_alloc) {
dw_hdma_set_callback_result(vd,
@@ -699,6 +706,12 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
vchan_cookie_complete(vd);
}

+ if (chan->request == EDMA_REQ_PAUSE) {
+ chan->request = EDMA_REQ_NONE;
+ chan->status = EDMA_ST_PAUSE;
+ break;
+ }
+
/* Continue transferring if there are remaining chunks or issued requests.
*/
chan->status = dw_edma_start_transfer(chan) ? EDMA_ST_BUSY : EDMA_ST_IDLE;
@@ -710,11 +723,6 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
chan->status = EDMA_ST_IDLE;
break;

- case EDMA_REQ_PAUSE:
- chan->request = EDMA_REQ_NONE;
- chan->status = EDMA_ST_PAUSE;
- break;
-
default:
break;
}
--
2.51.0