mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 13:14:02 +02:00
dmaengine: sun6i: fix non-atomic read of DMA position registers
sun6i_get_chan_size() reads DMA_CHAN_LLI_ADDR and DMA_CHAN_CUR_CNT in two
separate readl() calls with no synchronisation between them:
pos = readl(pchan->base + DMA_CHAN_LLI_ADDR);
bytes = readl(pchan->base + DMA_CHAN_CUR_CNT);
DMA_CHAN_LLI_ADDR holds the physical address of the *next* descriptor the
engine will load once the current one completes. DMA_CHAN_CUR_CNT holds the
remaining byte count for the *current* descriptor. If the DMA engine
advances to the next LLI entry between the two reads, pos becomes stale: it
still points to what was the next descriptor at the time of the first read,
but that descriptor is now the current one and CUR_CNT reflects its initial
(full) byte count. The subsequent virtual-chain walk starts one entry too
early and accumulates an extra full period's worth of bytes into the
residue estimate.
Fix this by re-reading DMA_CHAN_LLI_ADDR after DMA_CHAN_CUR_CNT and
retrying if the value changed. This double-read pattern guarantees that
both registers were sampled during the same descriptor interval. The cost
is at most one extra readl() pair per call in the racy case, which occurs
only at descriptor boundaries (~every 2 ms) and is negligible.
Fixes: a90e173f3f ("dmaengine: sun6i: Add cyclic capability")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-sonnet-4-6
Signed-off-by: Christian Lugnberg <christian.lugnberg@soundtrack.io>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Link: https://patch.msgid.link/20260817135723.12807-2-christian.lugnberg@soundtrack.io
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
parent
cee9c863ee
commit
c90b6973da
|
|
@ -354,8 +354,10 @@ static size_t sun6i_get_chan_size(struct sun6i_pchan *pchan)
|
|||
size_t bytes;
|
||||
dma_addr_t pos;
|
||||
|
||||
pos = readl(pchan->base + DMA_CHAN_LLI_ADDR);
|
||||
bytes = readl(pchan->base + DMA_CHAN_CUR_CNT);
|
||||
do {
|
||||
pos = readl(pchan->base + DMA_CHAN_LLI_ADDR);
|
||||
bytes = readl(pchan->base + DMA_CHAN_CUR_CNT);
|
||||
} while (pos != readl(pchan->base + DMA_CHAN_LLI_ADDR));
|
||||
|
||||
if (pos == LLI_LAST_ITEM)
|
||||
return bytes;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user