[PATCH v4 17/24] dmaengine: dw-edma: Recover stopped channels from tx_status()
Koichiro Den <[email protected]> Wed, 29 Jul 2026 23:30:29 +0900
| Newsgroups | org.kernel.vger.dmaengine,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
An LL channel can stop after an append without another interrupt. A synchronous user may then time out with work still pending. Let tx_status() consume IRQ-recorded progress under vc.lock. If the channel is stopped with pending entries, reconcile a fresh LLP boundary and re-kick it. If it still reports running, arm the bounded eDMA tail recheck. Do not use a running LLP read as progress because it can move ahead of payload completion. Refill the ring after consuming progress and update BUSY or IDLE from the remaining entries. Signed-off-by: Koichiro Den <[email protected]> --- Changes in v4: - Rephrase misleading "legacy-eDMA" wording; the tail recheck applies to the eDMA-compatible interface, not a specific map format. drivers/dma/dw-edma/dw-edma-core.c | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 0d1e6f48cac1..b90b33460d22 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -299,6 +299,14 @@ static bool dw_edma_core_enable_ll_irq(struct dw_edma_desc *desc, u32 i, return (chan->ll_head + 1) % DW_EDMA_LL_PROGRESS_INTERVAL == 0; } +static bool dw_edma_ll_recoverable_pending(struct dw_edma_chan *chan, + enum dma_status ch_status) +{ + return chan->request == EDMA_REQ_NONE && + chan->status != EDMA_ST_PAUSE && + dw_edma_ll_pending(chan) && ch_status == DMA_COMPLETE; +} + static void dw_edma_core_ll_start(struct dw_edma_desc *desc) { struct dw_edma_chan *chan = desc->chan; @@ -918,6 +926,38 @@ dw_edma_device_tx_status(struct dma_chan *dchan, dma_cookie_t cookie, if (ret == DMA_COMPLETE) return ret; + if (!chan->non_ll) { + scoped_guard(spinlock_irqsave, &chan->vc.lock) { + struct dw_edma_ll_snapshot snapshot; + enum dma_status ch_status; + + ch_status = dw_edma_core_ch_status(chan); + + /* + * Do not treat raw DMA_LLP polling as normal progress here. Only + * recycle progress recorded by the IRQ pass that consumed the + * status event. + */ + if (chan->request == EDMA_REQ_NONE && + chan->status != EDMA_ST_PAUSE && + ch_status == DMA_COMPLETE && + dw_edma_ll_snapshot_take(chan, &snapshot) && + dw_edma_ll_consume_progress(chan, snapshot.idx)) { + dw_edma_start_transfer(chan); + chan->status = dw_edma_ll_pending(chan) ? + EDMA_ST_BUSY : EDMA_ST_IDLE; + } + + if (dw_edma_ll_recoverable_pending(chan, ch_status)) + chan->status = EDMA_ST_BUSY; + dw_edma_core_ch_maybe_doorbell_or_recheck(chan); + } + + /* dw_edma_ll_clean_pending() may have completed this cookie. */ + ret = dma_cookie_status(dchan, cookie, txstate); + if (ret == DMA_COMPLETE) + return ret; + } if (ret == DMA_IN_PROGRESS && chan->status == EDMA_ST_PAUSE) ret = DMA_PAUSED; -- 2.51.0