* [RFC PATCH 0/2] ALSA: pcm: preserve capture tail during drain
@ 2026-10-14 3:30 Joonho Lee
2026-10-14 3:30 ` [RFC PATCH 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Joonho Lee @ 2026-10-14 3:30 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela; +Cc: linux-sound, linux-kernel, linux-api
Base: d58a3ac9b1719fdf8ce9d286be606128aeebf1aa
("ALSA: virtio: Keep control payloads alive through DMA completion")
Local tip of the five submitted v3 patches; not an upstream commit.
Previous series: <cover.1791881787.git.mynameisjune111@gmail.com>
Link: https://lore.kernel.org/all/cover.1791881787.git.mynameisjune111@gmail.com/
This is a follow-up RFC, not a replacement or v4 of that five-patch series.
The submitted v3 fixes ACK error propagation, failed START recovery, and
message/buffer lifetime through completion and reset. It does not add the
VZ capture profile or the ALSA core drain handling in these companion RFCs.
This series proposes two changes to capture drain handling:
1. Permit capture transfer calls while the runtime is DRAINING, so buffered
tail frames remain readable instead of returning -EBADFD at the state
check. Playback access rules remain unchanged.
2. Refresh active capture hardware pointers before the linked group enters
DRAINING, so a final subperiod tail is accounted. Hold the link read lock
across the initial operation and take each member's stream lock for its
pointer refresh. Propagate pointer errors before the drain transition.
These are ALSA core changes, not VZ quirks. They apply with all VZ options
disabled and can affect other capture drivers, including linked-stream drain
ordering. No new ioctl is added, but applications can observe buffered data
where the previous state check rejected a read. The appropriate core drain
semantics and locking scope need review independently of VZ support.
Dependencies
Apply these two patches in order on v3. They do not depend on the companion
STOP/RELEASE wait patch. The VZ capture RFC requires all three prerequisites.
The base is pinned for reproducibility; this is not a claim that the core
changes are inherently dependent on virtio-snd lifetime fixes.
Validation and limits
The complete local candidate (v3 plus all four RFC patches) was freshly
built on 2026-10-14 as an ARM64 KASAN Image and installed module set with
W=1, release 7.3.0-rc6-snd-lifetime-kasan. No old kernel objects were reused.
There were no build errors or warnings in the changed sound paths; 517
-Woverride-init warnings remain in two unchanged ARM64 base files.
This is a rebuild of the pinned candidate, not a rebase to a newer upstream
or sound maintainer tree.
With this kernel under ordinary TCG, no-query short-tail drain returns
1023 frames (FNV-1a64 20d61b4edef6d535) and 2047 frames
(af2de207e4d27535); drop returns zero. Each run also passes a following
capture with fatal-overrun checking, playback drain guard and teardown.
Earlier W=1 core builds included patch 1 alone. The separate historical
linked-pointer-error fixture used an earlier stable virtio-snd module to
isolate the core change; it is not one of the ten current candidate cases.
The QEMU 11.1.2 fixture includes backend reset cleanup changes as well as
fault injection. These are integrated candidate results, not standalone
v3 or isolated-patch proof, and not stock-QEMU, VZ or hardware acceptance.
Earlier runs failed follow-on capture with ring saturation and overruns;
those logs remain retained. Final passes do not establish a permanent fix
for load or clock/scheduling sensitivity.
Physical ALSA devices and the full range of linked capture/playback groups
remain untested. Historical AirPods capture does not isolate core drain
semantics. Playback access rules remain unchanged, but the shared drain
entry locking/order changes also need review for playback-led groups.
Reproducer
A source-only bundle contains the pinned base/v3/RFC preparation recipe,
kernel builder, QEMU patch stack, guest probes and all ten runtime commands.
See qemu/README.txt and qemu/VALIDATION-RESULTS.txt in that bundle. The final
runtime set passed under ordinary TCG; no KASAN/BUG/WARNING/oops/panic was
matched. The 24 Python harness tests and relocated source comparison also
passed. Existing v3 patches are included only as build prerequisites, not
for resubmission. Reproducer source (pinned commit):
https://github.com/whatisyourname0/virtio-snd-reproducer/tree/7a9876738bdc70ce669f02ec31fe8968eb0ee4fc/rfc/vz-capture-20261014
The source bundle includes qemu/README.txt with the full build/run recipe,
qemu/VALIDATION-RESULTS.txt and checksums. Downloaded public files were
verified against the tested source package. Bundled RFC mail drafts are
reproduction snapshots; these emails are the submission copies.
Questions for review
- Is reading buffered capture data in DRAINING the intended ALSA contract,
or should the driver expose that tail through a different transition?
- Is drain entry the correct place to refresh capture pointers, including
when draining a paused stream or a linked capture/playback group?
- Is the proposed link-lock/member-lock scope appropriate when a pointer
error can stop the group? Which other driver or linked-stream cases
should be required before treating this as a general core fix?
Joonho Lee (2):
ALSA: pcm: allow capture reads while draining
ALSA: pcm: refresh capture pointers before drain
sound/core/pcm_lib.c | 10 +++++++---
sound/core/pcm_native.c | 32 +++++++++++++++++++++++++-------
2 files changed, 32 insertions(+), 10 deletions(-)
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH 1/2] ALSA: pcm: allow capture reads while draining 2026-10-14 3:30 [RFC PATCH 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee @ 2026-10-14 3:30 ` Joonho Lee 2026-10-14 3:30 ` [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain Joonho Lee 2026-10-14 8:15 ` [RFC PATCH v2 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2 siblings, 0 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 3:30 UTC (permalink / raw) To: Takashi Iwai, Jaroslav Kysela; +Cc: linux-sound, linux-kernel, linux-api Allow capture transfer calls in DRAINING so already captured tail frames remain readable until drain completes. Assisted-by: LLM Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> --- sound/core/pcm_lib.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d665b4148d7..de952f639d3b 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2208,13 +2208,17 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream) return 0; } -static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +static int pcm_accessible_state(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: return 0; + case SNDRV_PCM_STATE_DRAINING: + return substream->stream == SNDRV_PCM_STREAM_CAPTURE ? 0 : -EBADFD; case SNDRV_PCM_STATE_XRUN: return -EPIPE; case SNDRV_PCM_STATE_SUSPENDED: @@ -2320,7 +2324,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, nonblock = !!(substream->f_flags & O_NONBLOCK); snd_pcm_stream_lock_irq(substream); - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; @@ -2388,7 +2392,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, atomic_dec(&runtime->buffer_accessing); if (err < 0) goto _end_unlock; - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; appl_ptr += frames; ^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain 2026-10-14 3:30 [RFC PATCH 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 3:30 ` [RFC PATCH 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee @ 2026-10-14 3:30 ` Joonho Lee 2026-10-14 8:15 ` Takashi Iwai 2026-10-14 8:15 ` [RFC PATCH v2 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2 siblings, 1 reply; 13+ messages in thread From: Joonho Lee @ 2026-10-14 3:30 UTC (permalink / raw) To: Takashi Iwai, Jaroslav Kysela; +Cc: linux-sound, linux-kernel, linux-api Refresh active capture pointers before the drain transition so the final captured tail is accounted before reads finish. Assisted-by: LLM Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> --- sound/core/pcm_native.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index defbb2977efe..622ae7be2d04 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2176,14 +2176,32 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, } else if (substream->f_flags & O_NONBLOCK) nonblock = 1; - snd_pcm_stream_lock_irq(substream); - /* resume pause */ - if (runtime->state == SNDRV_PCM_STATE_PAUSED) - snd_pcm_pause(substream, false); + /* Keep linked runtimes alive until the initial drain action completes. */ + scoped_guard(rwsem_read, &snd_pcm_link_rwsem) { + snd_pcm_stream_lock_irq(substream); + /* Resume before refreshing capture positions. */ + if (runtime->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, false); + snd_pcm_stream_unlock_irq(substream); + + /* A pointer error may stop the group: hold only this member's lock. */ + snd_pcm_group_for_each_entry(s, substream) { + if (s->stream != SNDRV_PCM_STREAM_CAPTURE) + continue; + scoped_guard(pcm_stream_lock_irq, s) { + if (s->runtime->state == SNDRV_PCM_STATE_RUNNING) + result = snd_pcm_update_hw_ptr(s); + } + if (result < 0) + break; + } - /* pre-start/stop - all running streams are changed to DRAINING state */ - result = snd_pcm_action(&snd_pcm_action_drain_init, substream, - ACTION_ARG_IGNORE); + snd_pcm_stream_lock_irq(substream); + /* Change the stable group to DRAINING only after all refreshes. */ + if (result >= 0) + result = snd_pcm_action(&snd_pcm_action_drain_init, + substream, ACTION_ARG_IGNORE); + } if (result < 0) goto unlock; /* in non-blocking, we don't wait in ioctl but let caller poll */ ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain 2026-10-14 3:30 ` [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain Joonho Lee @ 2026-10-14 8:15 ` Takashi Iwai 2026-10-14 8:35 ` Takashi Iwai 0 siblings, 1 reply; 13+ messages in thread From: Takashi Iwai @ 2026-10-14 8:15 UTC (permalink / raw) To: Joonho Lee Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, linux-api On Wed, 14 Oct 2026 05:30:57 +0200, Joonho Lee wrote: > > Refresh active capture pointers before the drain transition so the final > captured tail is accounted before reads finish. > > Assisted-by: LLM > Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> > --- > sound/core/pcm_native.c | 32 +++++++++++++++++++++++++------- > 1 file changed, 25 insertions(+), 7 deletions(-) > > diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c > index defbb2977efe..622ae7be2d04 100644 > --- a/sound/core/pcm_native.c > +++ b/sound/core/pcm_native.c > @@ -2176,14 +2176,32 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, > } else if (substream->f_flags & O_NONBLOCK) > nonblock = 1; > > - snd_pcm_stream_lock_irq(substream); > - /* resume pause */ > - if (runtime->state == SNDRV_PCM_STATE_PAUSED) > - snd_pcm_pause(substream, false); > + /* Keep linked runtimes alive until the initial drain action completes. */ > + scoped_guard(rwsem_read, &snd_pcm_link_rwsem) { > + snd_pcm_stream_lock_irq(substream); > + /* Resume before refreshing capture positions. */ > + if (runtime->state == SNDRV_PCM_STATE_PAUSED) > + snd_pcm_pause(substream, false); > + snd_pcm_stream_unlock_irq(substream); > + > + /* A pointer error may stop the group: hold only this member's lock. */ > + snd_pcm_group_for_each_entry(s, substream) { > + if (s->stream != SNDRV_PCM_STREAM_CAPTURE) > + continue; > + scoped_guard(pcm_stream_lock_irq, s) { > + if (s->runtime->state == SNDRV_PCM_STATE_RUNNING) > + result = snd_pcm_update_hw_ptr(s); > + } > + if (result < 0) > + break; > + } > > - /* pre-start/stop - all running streams are changed to DRAINING state */ > - result = snd_pcm_action(&snd_pcm_action_drain_init, substream, > - ACTION_ARG_IGNORE); > + snd_pcm_stream_lock_irq(substream); > + /* Change the stable group to DRAINING only after all refreshes. */ > + if (result >= 0) > + result = snd_pcm_action(&snd_pcm_action_drain_init, > + substream, ACTION_ARG_IGNORE); > + } > if (result < 0) > goto unlock; > /* in non-blocking, we don't wait in ioctl but let caller poll */ IIUC, basically what here we do is to just call snd_pcm_update_hw_ptr() for capture at drain init phase, so calling it inside snd_pcm_do_drain_init() would make more sense, I suppose. But, the problem is that snd_pcm_update_hw_ptr() itself takes an action to stop the stream for XRUN. So we'd need to fiddle along with it, too, as something like below. (Maybe we need more brush-ups.) thanks, Takashi -- 8< -- diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d665b4148d7..a1b19d558234 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -206,8 +206,9 @@ void __snd_pcm_xrun(struct snd_pcm_substream *substream) #endif -int snd_pcm_update_state(struct snd_pcm_substream *substream, - struct snd_pcm_runtime *runtime) +static int do_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime, + bool notify) { snd_pcm_uframes_t avail; @@ -216,12 +217,14 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream, runtime->avail_max = avail; if (runtime->state == SNDRV_PCM_STATE_DRAINING) { if (avail >= runtime->buffer_size) { - snd_pcm_drain_done(substream); + if (notify) + snd_pcm_drain_done(substream); return -EPIPE; } } else { if (avail >= runtime->stop_threshold) { - __snd_pcm_xrun(substream); + if (notify) + __snd_pcm_xrun(substream); return -EPIPE; } } @@ -233,6 +236,12 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream, return 0; } +int snd_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime) +{ + return do_pcm_update_state(substream, runtime, true); +} + static void update_audio_tstamp(struct snd_pcm_substream *substream, struct timespec64 *curr_tstamp, struct timespec64 *audio_tstamp) @@ -283,8 +292,8 @@ static void update_audio_tstamp(struct snd_pcm_substream *substream, runtime->driver_tstamp = driver_tstamp; } -static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, - unsigned int in_interrupt) +int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, + bool in_interrupt, bool notify) { struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_uframes_t pos; @@ -490,13 +499,13 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp); - return snd_pcm_update_state(substream, runtime); + return do_pcm_update_state(substream, runtime, notify); } /* CAUTION: call it with irq disabled */ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) { - return snd_pcm_update_hw_ptr0(substream, 0); + return snd_pcm_update_hw_ptr0(substream, false, true); } /** @@ -1906,7 +1915,7 @@ void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substrea runtime = substream->runtime; if (!snd_pcm_running(substream) || - snd_pcm_update_hw_ptr0(substream, 1) < 0) + snd_pcm_update_hw_ptr0(substream, true, true) < 0) goto _end; #ifdef CONFIG_SND_PCM_TIMER diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h index ecb21697ae3a..12bb6e75329a 100644 --- a/sound/core/pcm_local.h +++ b/sound/core/pcm_local.h @@ -28,6 +28,8 @@ int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream, int snd_pcm_update_state(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime); int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream); +int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, + bool in_interrupt, bool notify); void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index a845f242a163..76983e5f4d4e 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2094,6 +2094,8 @@ static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, snd_pcm_state_t state) { struct snd_pcm_runtime *runtime = substream->runtime; + int err; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: @@ -2119,8 +2121,12 @@ static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, if (runtime->state == SNDRV_PCM_STATE_RUNNING) { snd_pcm_state_t new_state; - new_state = snd_pcm_capture_avail(runtime) > 0 ? - SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; + err = snd_pcm_update_hw_ptr0(substream, false, false); + if (err < 0) + new_state = SNDRV_PCM_STATE_SETUP; + else + new_state = snd_pcm_capture_avail(runtime) > 0 ? + SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; snd_pcm_do_stop(substream, new_state); snd_pcm_post_stop(substream, new_state); } ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain 2026-10-14 8:15 ` Takashi Iwai @ 2026-10-14 8:35 ` Takashi Iwai 2026-10-14 15:20 ` Joonho Lee 0 siblings, 1 reply; 13+ messages in thread From: Takashi Iwai @ 2026-10-14 8:35 UTC (permalink / raw) To: Joonho Lee Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, linux-api On Wed, 14 Oct 2026 10:15:26 +0200, Takashi Iwai wrote: > > On Wed, 14 Oct 2026 05:30:57 +0200, > Joonho Lee wrote: > > > > Refresh active capture pointers before the drain transition so the final > > captured tail is accounted before reads finish. > > > > Assisted-by: LLM > > Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> > > --- > > sound/core/pcm_native.c | 32 +++++++++++++++++++++++++------- > > 1 file changed, 25 insertions(+), 7 deletions(-) > > > > diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c > > index defbb2977efe..622ae7be2d04 100644 > > --- a/sound/core/pcm_native.c > > +++ b/sound/core/pcm_native.c > > @@ -2176,14 +2176,32 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, > > } else if (substream->f_flags & O_NONBLOCK) > > nonblock = 1; > > > > - snd_pcm_stream_lock_irq(substream); > > - /* resume pause */ > > - if (runtime->state == SNDRV_PCM_STATE_PAUSED) > > - snd_pcm_pause(substream, false); > > + /* Keep linked runtimes alive until the initial drain action completes. */ > > + scoped_guard(rwsem_read, &snd_pcm_link_rwsem) { > > + snd_pcm_stream_lock_irq(substream); > > + /* Resume before refreshing capture positions. */ > > + if (runtime->state == SNDRV_PCM_STATE_PAUSED) > > + snd_pcm_pause(substream, false); > > + snd_pcm_stream_unlock_irq(substream); > > + > > + /* A pointer error may stop the group: hold only this member's lock. */ > > + snd_pcm_group_for_each_entry(s, substream) { > > + if (s->stream != SNDRV_PCM_STREAM_CAPTURE) > > + continue; > > + scoped_guard(pcm_stream_lock_irq, s) { > > + if (s->runtime->state == SNDRV_PCM_STATE_RUNNING) > > + result = snd_pcm_update_hw_ptr(s); > > + } > > + if (result < 0) > > + break; > > + } > > > > - /* pre-start/stop - all running streams are changed to DRAINING state */ > > - result = snd_pcm_action(&snd_pcm_action_drain_init, substream, > > - ACTION_ARG_IGNORE); > > + snd_pcm_stream_lock_irq(substream); > > + /* Change the stable group to DRAINING only after all refreshes. */ > > + if (result >= 0) > > + result = snd_pcm_action(&snd_pcm_action_drain_init, > > + substream, ACTION_ARG_IGNORE); > > + } > > if (result < 0) > > goto unlock; > > /* in non-blocking, we don't wait in ioctl but let caller poll */ > > IIUC, basically what here we do is to just call > snd_pcm_update_hw_ptr() for capture at drain init phase, so calling it > inside snd_pcm_do_drain_init() would make more sense, I suppose. > > But, the problem is that snd_pcm_update_hw_ptr() itself takes an > action to stop the stream for XRUN. So we'd need to fiddle along with > it, too, as something like below. (Maybe we need more brush-ups.) On the second thought, it should be fine to call snd_pcm_stop() & co from that context, too. Maybe a small change like below suffices. Takashi -- 8< -- --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2119,8 +2119,11 @@ static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, if (runtime->state == SNDRV_PCM_STATE_RUNNING) { snd_pcm_state_t new_state; - new_state = snd_pcm_capture_avail(runtime) > 0 ? - SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; + if (snd_pcm_update_hw_ptr(substream) < 0) + new_state = SNDRV_PCM_STATE_SETUP; + else + new_state = snd_pcm_capture_avail(runtime) > 0 ? + SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; snd_pcm_do_stop(substream, new_state); snd_pcm_post_stop(substream, new_state); } ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain 2026-10-14 8:35 ` Takashi Iwai @ 2026-10-14 15:20 ` Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 15:33 ` [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain Takashi Iwai 0 siblings, 2 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 15:20 UTC (permalink / raw) To: Takashi Iwai Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, linux-api Hi Takashi, Thanks for the suggestion and thoughts. I tried the smaller change and hit a recursive locking issue with linked capture streams. I used a test driver with two linked capture substreams and made the later member's pointer callback return SNDRV_PCM_POS_XRUN during drain. This led to the function calls as follows! : snd_pcm_do_drain_init() snd_pcm_update_hw_ptr() snd_pcm_update_hw_ptr0() __snd_pcm_xrun() snd_pcm_stop() snd_pcm_action() snd_pcm_stream_group_ref() The outer drain action already holds the group locks here. Lockdep reported the recursive acquisition of group->lock for atomic streams and group->mutex for nonatomic streams. Both tests remained stuck until the 45-second timeout. Removing the link let both tests complete. I went back to the notify=false approach from your first sketch, which is keeping the pointer update inside snd_pcm_do_drain_init(). I also added the guard to the early SNDRV_PCM_POS_XRUN branch, since that path still called __snd_pcm_xrun(). Drain init then handles the stop, while ordinary pointer updates keep their existing behavior. With this change, all 24 linked test cases passed across both locking modes, including pointer errors, tail reads and reopen/recovery. KASAN and lockdep stayed quiet. This was tested with a synthetic driver under ARM64 QEMU; physical-device testing is still pending. One thing I wanted to check before sending a revision is, how should drain report a pointer error? The current change puts the failing member in SETUP and allows a healthy linked member to keep its tail in DRAINING. A blocking capture-only drain returns 0 if the rest of the action succeeds, and reading the failed member afterwards returns -EBADFD. Would you prefer that behavior, or should we remember the error, finish stopping all members, and return -EPIPE from drain? My v2 proposal returned -EPIPE and moved the linked group to XRUN, so this changes what userspace sees. ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v3 0/2] ALSA: pcm: preserve capture tail during drain 2026-10-14 15:20 ` Joonho Lee @ 2026-10-14 15:24 ` Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 2/2] ALSA: pcm: refresh capture pointers in the drain action Joonho Lee 2026-10-14 15:33 ` [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain Takashi Iwai 1 sibling, 2 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 15:24 UTC (permalink / raw) To: Takashi Iwai Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, linux-api This updates the core capture-drain RFC to v3. The separate five-patch virtio-snd lifetime v3 series remains unchanged. Patch 1 still permits reads of buffered capture data in DRAINING. Patch 2 follows Takashi Iwai's suggestion to refresh a running capture pointer inside snd_pcm_do_drain_init(), under the existing drain action's stream and group locks. It removes v2's separate pre-action traversal and link rwsem handling. The pointer helper accepts a notify flag. Drain init disables nested XRUN and drain-complete actions and owns the subsequent stop/state transition. The direct SNDRV_PCM_POS_XRUN return from a driver's pointer callback needs the same guard as the stop-threshold path; the sketch did not guard that early branch. Ordinary pointer updates and period notifications still use notify=true. There is an observable difference from v2 on a capture pointer error. V2 returned EPIPE and put the linked group in XRUN. The new callback ends the failing member in SETUP and continues processing other members. A blocking capture-only drain returns 0 if the action otherwise succeeds; an unaffected member's valid tail can remain readable in DRAINING. The failed member has no readable tail in SETUP. The existing nonblocking EAGAIN policy is unchanged. This policy remains open for review. Takashi's later reply proposed calling snd_pcm_update_hw_ptr() directly: https://lore.kernel.org/all/87y0c0el29.wl-tiwai@suse.de/ I tested that literal alternative with a synthetic linked capture driver. A later member returning SNDRV_PCM_POS_XRUN recursively enters snd_pcm_stop() while the drain action already holds the group locks. Lockdep reports recursive acquisition of group->lock (atomic) and group->mutex (nonatomic); both runs remain blocked until the 45-second host bound. Unlinked controls complete with both implementations. The candidate therefore retains notify=false and guards both XRUN paths. The deadlock above was reproduced with the later direct-call proposal. The submitted v2 used a separate pre-action traversal. Initial review: https://lore.kernel.org/all/8733u8g0kh.wl-tiwai@suse.de/ Previous core v2: https://lore.kernel.org/all/rfc-v2-core-capture-drain-0.2eddd58a796e497f@gmail.com/ Lifetime v3 prerequisite: https://lore.kernel.org/all/cover.1791881787.git.mynameisjune111@gmail.com/ Apply core patches 1 and 2 after the five lifetime v3 patches on the public base below. Core does not require the generic wait or VZ profile patch; this base is pinned to reproduce the integrated test stack. Replace old core 2/2 with this revision. Validation on the rebuilt 7.3.0-rc6-snd-rfc-tiwai-lockdep ARM64 kernel: - Full Image and module build with W=1, KASAN, lockdep and atomic-sleep debugging: no errors or sound warnings. The base emitted 517 -Woverride-init warnings outside sound/. - All 10 QEMU scenarios passed: short capture tails, drop, delayed PREPARE/START/STOP/RELEASE, pending RX after RELEASE, and actual held STOP/RELEASE with SIGKILL, timeout, reset and subsequent I/O recovery. - All 24 synthetic linked cases passed across atomic and nonatomic locks: zero/tail data, sentinel/threshold faults in the first/later member, normal active XRUN reporting, unlink/prepare and close/reopen recovery. The shared-STOP fixture also checks a later member's frozen final tail. - Two additional unlinked candidate controls passed and matched the literal proposal's unlinked state, read error and recovery behavior. - No KASAN or lockdep diagnostics occurred in candidate runs; the two intentional alternative-module failures are recorded separately. - The corrected harness invocations passed 19 lifecycle and 5 direct-reset tests; the initial collection-error log is retained separately. Both signed patches pass strict checkpatch without errors or warnings. The QEMU fault/reset fixtures use modified test backends. The build includes the unchanged companion patches. No new physical microphone or Virtualization.framework test is claimed. Mixed capture/playback linked groups and physical drivers remain untested. Nonblocking EAGAIN preservation was code-reviewed; runtime testing of that behavior remains pending. Questions: - Is the proposed drain-time error policy (local SETUP and successful blocking drain) intended, or should an error be reported after all members have been stopped? - Is refreshing each member immediately before its STOP sufficient for drivers whose first STOP freezes linked hardware, provided later pointer callbacks report their stable final positions? - Which additional physical-driver or mixed capture/playback group tests should be required before this becomes a non-RFC core change? Assisted-by: LLM The new linked reproducer and comparison logs are currently local. The earlier public reproducer package contains the core v2 implementation. This revision does not claim a new public reproducer URL. base-commit: 8d70e364e866bdfca81eee24d0688fac9472cfb9 prerequisite-patch-id: 39fb327a004c966ab90f66a34a64bbdbb3ffb475 prerequisite-patch-id: 7c6bc344656fd839ae0d697ba18d66ac0b47827a prerequisite-patch-id: 9dd290ac3147fa7b49a475d7df8a6172778f2860 prerequisite-patch-id: f4c24a5e441343a690f4ac2437f181c815bc23d0 prerequisite-patch-id: ab4a5b770247a65272ab7af18a75a147daf81287 ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v3 1/2] ALSA: pcm: allow capture reads while draining 2026-10-14 15:24 ` [RFC PATCH v3 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee @ 2026-10-14 15:24 ` Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 2/2] ALSA: pcm: refresh capture pointers in the drain action Joonho Lee 1 sibling, 0 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 15:24 UTC (permalink / raw) To: Takashi Iwai Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, linux-api Allow capture transfer calls in DRAINING so already captured tail frames remain readable until drain completes. Assisted-by: LLM Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> --- sound/core/pcm_lib.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d665b4148d7..de952f639d3b 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2208,13 +2208,17 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream) return 0; } -static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +static int pcm_accessible_state(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: return 0; + case SNDRV_PCM_STATE_DRAINING: + return substream->stream == SNDRV_PCM_STREAM_CAPTURE ? 0 : -EBADFD; case SNDRV_PCM_STATE_XRUN: return -EPIPE; case SNDRV_PCM_STATE_SUSPENDED: @@ -2320,7 +2324,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, nonblock = !!(substream->f_flags & O_NONBLOCK); snd_pcm_stream_lock_irq(substream); - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; @@ -2388,7 +2392,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, atomic_dec(&runtime->buffer_accessing); if (err < 0) goto _end_unlock; - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; appl_ptr += frames; ^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 2/2] ALSA: pcm: refresh capture pointers in the drain action 2026-10-14 15:24 ` [RFC PATCH v3 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee @ 2026-10-14 15:24 ` Joonho Lee 1 sibling, 0 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 15:24 UTC (permalink / raw) To: Takashi Iwai Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, linux-api Capture drain decides whether to retain unread data from the cached hardware pointer. A final subperiod can be omitted if the driver has advanced without a period notification or another pointer query. Refresh each running capture pointer in snd_pcm_do_drain_init(), with the normal drain action holding the stream and linked-group locks. Allow pointer accounting to return an error without issuing a nested XRUN or drain-complete action. Guard both the driver XRUN sentinel and the stop-threshold path; otherwise a linked drain can recursively enter the group stop action while its member locks are held. Let drain init own the stop: an update error ends that member in SETUP, while a successful update retains available capture frames in DRAINING. Ordinary pointer queries and period notifications retain their existing XRUN and drain-completion actions. Suggested-by: Takashi Iwai <tiwai@suse.de> Link: https://lore.kernel.org/all/8733u8g0kh.wl-tiwai@suse.de/ Link: https://lore.kernel.org/all/87y0c0el29.wl-tiwai@suse.de/ Assisted-by: LLM Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> --- sound/core/pcm_lib.c | 34 ++++++++++++++++++++++++---------- sound/core/pcm_local.h | 2 ++ sound/core/pcm_native.c | 10 ++++++++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index de952f639d3b..2bb24d7dd2a1 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -206,8 +206,9 @@ void __snd_pcm_xrun(struct snd_pcm_substream *substream) #endif -int snd_pcm_update_state(struct snd_pcm_substream *substream, - struct snd_pcm_runtime *runtime) +static int do_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime, + bool notify) { snd_pcm_uframes_t avail; @@ -216,12 +217,14 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream, runtime->avail_max = avail; if (runtime->state == SNDRV_PCM_STATE_DRAINING) { if (avail >= runtime->buffer_size) { - snd_pcm_drain_done(substream); + if (notify) + snd_pcm_drain_done(substream); return -EPIPE; } } else { if (avail >= runtime->stop_threshold) { - __snd_pcm_xrun(substream); + if (notify) + __snd_pcm_xrun(substream); return -EPIPE; } } @@ -233,6 +236,12 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream, return 0; } +int snd_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime) +{ + return do_pcm_update_state(substream, runtime, true); +} + static void update_audio_tstamp(struct snd_pcm_substream *substream, struct timespec64 *curr_tstamp, struct timespec64 *audio_tstamp) @@ -283,8 +292,12 @@ static void update_audio_tstamp(struct snd_pcm_substream *substream, runtime->driver_tstamp = driver_tstamp; } -static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, - unsigned int in_interrupt) +/* + * Call with the stream lock held. With notify=false the caller handles + * stopping the stream, so pointer errors must not start a nested action. + */ +int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, + bool in_interrupt, bool notify) { struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_uframes_t pos; @@ -322,7 +335,8 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, } if (pos == SNDRV_PCM_POS_XRUN) { - __snd_pcm_xrun(substream); + if (notify) + __snd_pcm_xrun(substream); return -EPIPE; } if (pos >= runtime->buffer_size) { @@ -490,13 +504,13 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp); - return snd_pcm_update_state(substream, runtime); + return do_pcm_update_state(substream, runtime, notify); } /* CAUTION: call it with irq disabled */ int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) { - return snd_pcm_update_hw_ptr0(substream, 0); + return snd_pcm_update_hw_ptr0(substream, false, true); } /** @@ -1906,7 +1920,7 @@ void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substrea runtime = substream->runtime; if (!snd_pcm_running(substream) || - snd_pcm_update_hw_ptr0(substream, 1) < 0) + snd_pcm_update_hw_ptr0(substream, true, true) < 0) goto _end; #ifdef CONFIG_SND_PCM_TIMER diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h index ecb21697ae3a..12bb6e75329a 100644 --- a/sound/core/pcm_local.h +++ b/sound/core/pcm_local.h @@ -28,6 +28,8 @@ int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream, int snd_pcm_update_state(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime); int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream); +int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, + bool in_interrupt, bool notify); void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index defbb2977efe..91ae46de3521 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2094,6 +2094,8 @@ static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, snd_pcm_state_t state) { struct snd_pcm_runtime *runtime = substream->runtime; + int err; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: @@ -2119,8 +2121,12 @@ static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, if (runtime->state == SNDRV_PCM_STATE_RUNNING) { snd_pcm_state_t new_state; - new_state = snd_pcm_capture_avail(runtime) > 0 ? - SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; + err = snd_pcm_update_hw_ptr0(substream, false, false); + if (err < 0) + new_state = SNDRV_PCM_STATE_SETUP; + else + new_state = snd_pcm_capture_avail(runtime) > 0 ? + SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; snd_pcm_do_stop(substream, new_state); snd_pcm_post_stop(substream, new_state); } -- 2.54.0 (Apple Git-157) ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain 2026-10-14 15:20 ` Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee @ 2026-10-14 15:33 ` Takashi Iwai 1 sibling, 0 replies; 13+ messages in thread From: Takashi Iwai @ 2026-10-14 15:33 UTC (permalink / raw) To: Joonho Lee Cc: Takashi Iwai, Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel, linux-api On Wed, 14 Oct 2026 17:20:55 +0200, Joonho Lee wrote: > > Hi Takashi, > > Thanks for the suggestion and thoughts. I tried the smaller change and hit a > recursive locking issue with linked capture streams. > > I used a test driver with two linked capture substreams and made the > later member's pointer callback return SNDRV_PCM_POS_XRUN during > drain. This led to the function calls as follows! : > > snd_pcm_do_drain_init() > snd_pcm_update_hw_ptr() > snd_pcm_update_hw_ptr0() > __snd_pcm_xrun() > snd_pcm_stop() > snd_pcm_action() > snd_pcm_stream_group_ref() > > The outer drain action already holds the group locks here. Lockdep > reported the recursive acquisition of group->lock for atomic streams and > group->mutex for nonatomic streams. Both tests remained stuck until > the 45-second timeout. Removing the link let both tests complete. OK, so my previous assumption was wrong, obviously... > I went back to the notify=false approach from your first sketch, which is keeping the pointer update inside snd_pcm_do_drain_init(). I also > added the guard to the early SNDRV_PCM_POS_XRUN branch, since that > path still called __snd_pcm_xrun(). Drain init then handles the stop, > while ordinary pointer updates keep their existing behavior. > > With this change, all 24 linked test cases passed across both locking > modes, including pointer errors, tail reads and reopen/recovery. > KASAN and lockdep stayed quiet. This was tested with a synthetic > driver under ARM64 QEMU; physical-device testing is still pending. > > One thing I wanted to check before sending a revision is, how should > drain report a pointer error? > > The current change puts the failing member in SETUP and allows a > healthy linked member to keep its tail in DRAINING. A blocking > capture-only drain returns 0 if the rest of the action succeeds, > and reading the failed member afterwards returns -EBADFD. > > Would you prefer that behavior, or should we remember the error, > finish stopping all members, and return -EPIPE from drain? My v2 proposal > returned -EPIPE and moved the linked group to XRUN, so this changes > what userspace sees. Honestly speaking, I'm no longer sure whether my suggestion is better for now. It's certainly clearer if we can call the update_hwptr stuff inside drain_init call, but this looks like a hard way, and the conditional call at each PCM operation sounds fragile, too. Maybe we can go back to your way as v2, but slightly differently. Namely, factor out the snd_pcm_link_rwsem and update hwptr loop into a helper function, and just call it in snd_pcm_drain(). The already existing calls of snd_pcm_pause() and snd_pcm_action() with snd_pcm_action_drain_init can be kept as-is; they need no protection of snd_pcm_link_rwsem. Also, I think it's fine to call snd_pcm_update_hw_ptr() no matter whether it's playback or capture stream. thanks, Takashi ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 0/2] ALSA: pcm: preserve capture tail during drain 2026-10-14 3:30 [RFC PATCH 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 3:30 ` [RFC PATCH 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee 2026-10-14 3:30 ` [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain Joonho Lee @ 2026-10-14 8:15 ` Joonho Lee 2026-10-14 8:15 ` [RFC PATCH v2 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee 2026-10-14 8:15 ` [RFC PATCH v2 2/2] ALSA: pcm: refresh capture pointers before drain Joonho Lee 2 siblings, 2 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 8:15 UTC (permalink / raw) To: Takashi Iwai, Jaroslav Kysela; +Cc: linux-sound, linux-kernel, linux-api Changes since RFC v1 Patch 1 is unchanged. Patch 2 replaces scoped guards with explicit lock and unlock pairs because include/linux/cleanup.h prohibits mixing cleanup helpers and goto within one function. This addresses the Sashiko review. We did not establish an existing double unlock or skipped unlock: the old gotos were outside the scoped blocks. The link-lock scope, per-member pointer refresh, error path and playback wait loop are preserved. The build and regression results below are from the revised candidate. The public base and ordered prerequisite patch IDs appear below. The local v3 tip d58a3ac9b1719fdf8ce9d286be606128aeebf1aa is reconstructed by applying the five v3 patches to that public base; it is not an upstream commit. This is v2 of this follow-up RFC, not v4 of the five-patch virtio-snd lifetime series. The v3 prerequisite thread is: https://lore.kernel.org/all/cover.1791881787.git.mynameisjune111@gmail.com/ The submitted v3 fixes ACK error propagation, failed START recovery, and message/buffer lifetime through completion and reset. It does not add the VZ capture profile or the ALSA core drain handling in these companion RFCs. This series proposes two changes to capture drain handling: 1. Permit capture transfer calls while the runtime is DRAINING, so buffered tail frames remain readable instead of returning -EBADFD at the state check. Playback access rules remain unchanged. 2. Refresh active capture hardware pointers before the linked group enters DRAINING, so a final subperiod tail is accounted. Hold the link read lock across the initial operation and take each member's stream lock for its pointer refresh. Propagate pointer errors before the drain transition. These are ALSA core changes, not VZ quirks. They apply with all VZ options disabled and can affect other capture drivers, including linked-stream drain ordering. No new ioctl is added, but applications can observe buffered data where the previous state check rejected a read. The appropriate core drain semantics and locking scope need review independently of VZ support. Dependencies Apply these two patches in order on v3. They do not depend on the companion STOP/RELEASE wait patch. The VZ capture RFC requires all three prerequisites. The base is pinned for reproducibility; this is not a claim that the core changes are inherently dependent on virtio-snd lifetime fixes. Validation and limits The complete local candidate (v3 plus all four RFC patches, including the revised core locking) was freshly built on 2026-10-14 as an ARM64 Image and full module set with W=1, KASAN, PROVE_LOCKING, LOCKDEP and DEBUG_ATOMIC_SLEEP, release 7.3.0-rc6-snd-rfc-cleanup-lockdep. There were no build errors or warnings in the changed sound paths; 517 -Woverride-init warnings remain in two unchanged ARM64 base files. This is a rebuild of the pinned candidate, not a rebase to a newer upstream or sound maintainer tree. With this kernel under ordinary TCG, no-query short-tail drain returns 1023 frames (FNV-1a64 20d61b4edef6d535) and 2047 frames (af2de207e4d27535); drop returns zero. Each run also passes a following capture with fatal-overrun checking, playback drain guard and teardown. The linked-stream fixture was also rebuilt against this kernel's config and Module.symvers. Normal unlinked/linked drain passed. An injected pointer XRUN in the later member visited both pointers and returned EPIPE with both streams in XRUN. Unlink/prepare, close/reopen, capture/playback recovery and teardown passed with lockdep enabled. This is a synthetic ALSA fixture, not a physical-device test. The QEMU 11.1.2 fixture includes backend reset cleanup changes as well as fault injection. These are integrated candidate results, not standalone v3 or isolated-patch proof, and not stock-QEMU, VZ or hardware acceptance. Earlier runs failed follow-on capture with ring saturation and overruns; those logs remain retained. Final passes do not establish a permanent fix for load or clock/scheduling sensitivity. Physical ALSA devices and the full range of linked capture/playback groups remain untested. Historical AirPods capture does not isolate core drain semantics. Playback access rules remain unchanged, but the shared drain entry locking/order changes also need review for playback-led groups. All ten ordinary-TCG regression scenarios passed with the fresh candidate, as did the additional linked-stream error/recovery fixture and 24 Python harness tests. Successful runtime logs contained no KASAN, lockdep, WARNING, BUG, Oops or panic diagnostics. Initial harness/CPU-configuration failures are retained as invalid attempts, not counted as kernel passes. No new real-microphone or VZ runtime test was performed for this revision. Reproducer The previously published source package remains at this pinned commit: https://github.com/whatisyourname0/virtio-snd-reproducer/tree/7a9876738bdc70ce669f02ec31fe8968eb0ee4fc/rfc/vz-capture-20261014 It contains the original RFC snapshots, kernel builder, QEMU fixture and ten runtime commands. It does not contain the revised core locking or the newly rebuilt linked-stream fixture. Do not treat it as the exact source of the new results above. Existing v3 patches in it are build prerequisites, not patches being resubmitted. The revised source stack, four signed implementation patches, exact kernel config and portable linked-stream fixture are published at this commit: https://github.com/whatisyourname0/virtio-snd-reproducer/tree/64fa85f3260b45e7dbb85c4eee68a5b4a8b064e1/rfc/vz-capture-20261014/sashiko-followup The public source recipe reconstructs all eight changed files byte-for-byte against the tested candidate. The packaged linked fixture also passed with freshly compiled probes, a new Alpine rootfs and a rebuilt test module on the same KASAN/lockdep kernel. The public archive was downloaded and checked against the local source package. See README.md and RESULTS.txt at that URL. This supplements the earlier bundle; use its corrected core 2/2 patch rather than the earlier core 2/2 snapshot. Questions for review - Is reading buffered capture data in DRAINING the intended ALSA contract, or should the driver expose that tail through a different transition? - Is drain entry the correct place to refresh capture pointers, including when draining a paused stream or a linked capture/playback group? - Is the proposed link-lock/member-lock scope appropriate when a pointer error can stop the group? Which other driver or linked-stream cases should be required before treating this as a general core fix? Joonho Lee (2): ALSA: pcm: allow capture reads while draining ALSA: pcm: refresh capture pointers before drain sound/core/pcm_lib.c | 10 +++++++--- sound/core/pcm_native.c | 26 ++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) base-commit: 8d70e364e866bdfca81eee24d0688fac9472cfb9 prerequisite-patch-id: 39fb327a004c966ab90f66a34a64bbdbb3ffb475 prerequisite-patch-id: 7c6bc344656fd839ae0d697ba18d66ac0b47827a prerequisite-patch-id: 9dd290ac3147fa7b49a475d7df8a6172778f2860 prerequisite-patch-id: f4c24a5e441343a690f4ac2437f181c815bc23d0 prerequisite-patch-id: ab4a5b770247a65272ab7af18a75a147daf81287 ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 1/2] ALSA: pcm: allow capture reads while draining 2026-10-14 8:15 ` [RFC PATCH v2 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee @ 2026-10-14 8:15 ` Joonho Lee 2026-10-14 8:15 ` [RFC PATCH v2 2/2] ALSA: pcm: refresh capture pointers before drain Joonho Lee 1 sibling, 0 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 8:15 UTC (permalink / raw) To: Takashi Iwai, Jaroslav Kysela; +Cc: linux-sound, linux-kernel, linux-api Allow capture transfer calls in DRAINING so already captured tail frames remain readable until drain completes. Assisted-by: LLM Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> --- sound/core/pcm_lib.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d665b4148d7..de952f639d3b 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2208,13 +2208,17 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream) return 0; } -static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +static int pcm_accessible_state(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: return 0; + case SNDRV_PCM_STATE_DRAINING: + return substream->stream == SNDRV_PCM_STREAM_CAPTURE ? 0 : -EBADFD; case SNDRV_PCM_STATE_XRUN: return -EPIPE; case SNDRV_PCM_STATE_SUSPENDED: @@ -2320,7 +2324,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, nonblock = !!(substream->f_flags & O_NONBLOCK); snd_pcm_stream_lock_irq(substream); - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; @@ -2388,7 +2392,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, atomic_dec(&runtime->buffer_accessing); if (err < 0) goto _end_unlock; - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; appl_ptr += frames; ^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v2 2/2] ALSA: pcm: refresh capture pointers before drain 2026-10-14 8:15 ` [RFC PATCH v2 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 8:15 ` [RFC PATCH v2 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee @ 2026-10-14 8:15 ` Joonho Lee 1 sibling, 0 replies; 13+ messages in thread From: Joonho Lee @ 2026-10-14 8:15 UTC (permalink / raw) To: Takashi Iwai, Jaroslav Kysela; +Cc: linux-sound, linux-kernel, linux-api Refresh active capture pointers before the drain transition so the final captured tail is accounted before reads finish. Keep the link rwsem held through pointer refresh and the drain action. Release the initiating stream lock while refreshing, lock each running capture member only while updating its pointer, then reacquire the initiating lock before the state transition. Pair the rwsem on every exit and retain the existing error cleanup path. Assisted-by: LLM Signed-off-by: Joonho Lee <mynameisjune111@gmail.com> --- sound/core/pcm_native.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index defbb2977efe..5375f8f931d9 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2176,14 +2176,32 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, } else if (substream->f_flags & O_NONBLOCK) nonblock = 1; + /* Keep linked runtimes alive until the initial drain action completes. */ + down_read(&snd_pcm_link_rwsem); snd_pcm_stream_lock_irq(substream); - /* resume pause */ + /* Resume before refreshing capture positions. */ if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); + snd_pcm_stream_unlock_irq(substream); + + /* A pointer error may stop the group: hold only this member's lock. */ + snd_pcm_group_for_each_entry(s, substream) { + if (s->stream != SNDRV_PCM_STREAM_CAPTURE) + continue; + snd_pcm_stream_lock_irq(s); + if (s->runtime->state == SNDRV_PCM_STATE_RUNNING) + result = snd_pcm_update_hw_ptr(s); + snd_pcm_stream_unlock_irq(s); + if (result < 0) + break; + } - /* pre-start/stop - all running streams are changed to DRAINING state */ - result = snd_pcm_action(&snd_pcm_action_drain_init, substream, - ACTION_ARG_IGNORE); + snd_pcm_stream_lock_irq(substream); + /* Change the stable group to DRAINING only after all refreshes. */ + if (result >= 0) + result = snd_pcm_action(&snd_pcm_action_drain_init, + substream, ACTION_ARG_IGNORE); + up_read(&snd_pcm_link_rwsem); if (result < 0) goto unlock; /* in non-blocking, we don't wait in ioctl but let caller poll */ ^ permalink raw reply related [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-10-14 15:33 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-14 3:30 [RFC PATCH 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 3:30 ` [RFC PATCH 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee 2026-10-14 3:30 ` [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain Joonho Lee 2026-10-14 8:15 ` Takashi Iwai 2026-10-14 8:35 ` Takashi Iwai 2026-10-14 15:20 ` Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee 2026-10-14 15:24 ` [RFC PATCH v3 2/2] ALSA: pcm: refresh capture pointers in the drain action Joonho Lee 2026-10-14 15:33 ` [RFC PATCH 2/2] ALSA: pcm: refresh capture pointers before drain Takashi Iwai 2026-10-14 8:15 ` [RFC PATCH v2 0/2] ALSA: pcm: preserve capture tail during drain Joonho Lee 2026-10-14 8:15 ` [RFC PATCH v2 1/2] ALSA: pcm: allow capture reads while draining Joonho Lee 2026-10-14 8:15 ` [RFC PATCH v2 2/2] ALSA: pcm: refresh capture pointers before drain Joonho Lee
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.