* [PATCH v2] dmaengine: apple-admac: Fix position one period ahead at period boundary
@ 2026-10-30 7:39 Jacob Procee
0 siblings, 0 replies; only message in thread
From: Jacob Procee @ 2026-10-30 7:39 UTC (permalink / raw)
To: Vinod Koul
Cc: Frank Li, Sasha Finkelstein, Sven Peter, Janne Grunau,
Neal Gompa, Martin Povišer, asahi, linux-arm-kernel,
dmaengine, linux-kernel, Jacob Procee, Frank Li
Speaker playback on a MacBook Pro 14" (M1 Pro, J314s) underruns at
irregular intervals, audible as a tick: 8 times in 3.7 hours of
playback. Where an underrun was caught (4 of 8), the ALSA hw_ptr had
jumped by one buffer size while about 70 ms of audio was still queued,
and a kernel trace showed the DMA position stepping back by exactly one
period at each of the 6 underruns it covered.
admac_cyclic_read_residue() derives the position from the report ring
(periods finished but not yet reclaimed) and REG_RESIDUE (bytes left in
the current descriptor). When a descriptor finishes, the residue first
drops to zero, then the report is posted and the next descriptor is
loaded. Usually the last two happen together, but occasionally the
report comes first. A read in that window counts the finished period
twice:
- residue2 == 0 with the report already in ring2: the period is
counted in nreports and again as the fully consumed current
descriptor;
- residue1 == 0 with the report already in ring1, and the next
descriptor loaded by the time residue2 is read: the
residue2 > residue1 branch adds one to a ring1 that already holds
the report.
Either way the position is one period ahead. The next read is behind
it, snd_pcm_update_hw_ptr0() takes the backwards step for a wrap of the
whole buffer, and the stream underruns.
While the channel runs, a zero residue only occurs between descriptors,
so read the registers again while either residue is zero. The retries
are bounded, as the residue also stays zero on a stopped channel or one
that ran out of descriptors; after the last retry the position is
computed as before.
With a debug build counting retries, one hour of playback needed a
retry for 2210 of 3.39 million reads, at most three retries (3.5 us),
also across pause and stream restart. Both states above showed up in
the logged register values. There were no underruns in three hours of
playback with the fix.
Fixes: b127315d9a78 ("dmaengine: apple-admac: Add Apple ADMAC driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Acked-by: Martin Povišer <povik+lin@cutebit.org>
Signed-off-by: Jacob Procee <sn125164@gmail.com>
---
Thanks Frank, Sasha and Martin for the reviews.
Changes in v2:
- Drop the code comment, the explanation is in the commit message
(Sasha Finkelstein).
- Add Frank's Reviewed-by and Martin's Acked-by.
- v1: https://lore.kernel.org/r/20261029105908.22809-1-sn125164@gmail.com
How this was found and tested:
- The underruns were traced with bpftrace on snd_dmaengine_pcm_pointer():
a position read 19-37 us before the period interrupt was exactly one
period ahead of the interrupt's own read. The register states were then
confirmed with a debug build of this driver that logged the raw report
ring and residue values on every retry.
- There is no hardware documentation for the ordering; all of this is
measured. In the 1906 logged retries the next read always showed both
the report and the new descriptor, or neither, never the new descriptor
with the old report count.
- The reverse order (next descriptor loaded before the report is posted)
would put the position one period behind and is not handled here. The
bpftrace probe checked every position read for a backward step, so it
would have caught that too: none occurred in the three hours with the
fix, roughly 10 million reads.
- RESIDUE_RETRIES is empirical as well: of the logged retries, 1189 needed
one, 621 two and 96 three, none more; the longest complete retry
sequence took 3.5 us. A round takes about 1 us, so the limit keeps the
additional polling time, with interrupts off, to roughly 10 us in the
observed environment. If the limit is reached, the position is computed
using the existing calculation, so the original error can still occur
in that case.
- Tested on a MacBook Pro 14" (M1 Pro, J314s) as an out-of-tree module on
the Asahi 7.2.12 kernel, whose apple-admac.c matches this tree apart from
the unrelated admac_hw rework. The three hours above used a limit of 100
retries; the code as posted here (limit 10) ran for another 30 minutes.
On this tree the driver builds cleanly with W=1 and C=1 (arm64
defconfig), but I have not run it here, nor on other Apple SoCs.
AI assistance: the tracing, analysis, the code change and a first draft of
this changelog were done with an LLM (Claude) in a session with me. I have
reviewed and tested the result and can answer for it.
drivers/dma/apple-admac.c | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c
index 9a1c79c12..a505975a5 100644
--- a/drivers/dma/apple-admac.c
+++ b/drivers/dma/apple-admac.c
@@ -28,6 +28,12 @@
*/
#define SRAM_BLOCK 2048
+/*
+ * How often to re-read the residue while a descriptor boundary is in
+ * progress, see admac_cyclic_read_residue().
+ */
+#define RESIDUE_RETRIES 10
+
#define RING_WRITE_SLOT GENMASK(1, 0)
#define RING_READ_SLOT GENMASK(5, 4)
#define RING_FULL BIT(9)
@@ -358,11 +364,14 @@ static u32 admac_cyclic_read_residue(struct admac_data *ad, int channo,
u32 residue1, residue2;
int nreports;
size_t pos;
-
- ring1 = readl_relaxed(ad->base + REG_REPORT_RING(channo));
- residue1 = readl_relaxed(ad->base + REG_RESIDUE(channo));
- ring2 = readl_relaxed(ad->base + REG_REPORT_RING(channo));
- residue2 = readl_relaxed(ad->base + REG_RESIDUE(channo));
+ int tries = 0;
+
+ do {
+ ring1 = readl_relaxed(ad->base + REG_REPORT_RING(channo));
+ residue1 = readl_relaxed(ad->base + REG_RESIDUE(channo));
+ ring2 = readl_relaxed(ad->base + REG_REPORT_RING(channo));
+ residue2 = readl_relaxed(ad->base + REG_RESIDUE(channo));
+ } while ((!residue1 || !residue2) && ++tries < RESIDUE_RETRIES);
if (residue2 > residue1) {
/*
base-commit: aff5e6a77fc69a7c490246465940d18422d7c631
--
2.56.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-30 7:39 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 7:39 [PATCH v2] dmaengine: apple-admac: Fix position one period ahead at period boundary Jacob Procee
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®