mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 15:22:21 -04:00
dmaengine: dw-edma: Complete descriptors before pausing
If PAUSE is requested while the final burst 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() can select that depleted descriptor
again even though no burst remains, leaving the channel in an invalid
busy state.
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: e63d79d1ff ("dmaengine: Add Synopsys eDMA IP core driver")
Cc: stable@vger.kernel.org
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
Link: https://patch.msgid.link/20260717180639.2643243-5-den@valinux.co.jp
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
@@ -255,7 +255,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;
|
||||
@@ -601,10 +602,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->start_burst >= desc->nburst) {
|
||||
dw_hdma_set_callback_result(vd,
|
||||
@@ -613,6 +620,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;
|
||||
@@ -624,11 +637,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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user