* [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; 8+ 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] 8+ 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; 8+ 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 [flat|nested] 8+ 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; 8+ 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 [flat|nested] 8+ 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; 8+ 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 [flat|nested] 8+ 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; 8+ 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] 8+ 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; 8+ 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 [flat|nested] 8+ 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; 8+ 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 [flat|nested] 8+ 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
0 siblings, 0 replies; 8+ 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] 8+ messages in thread
end of thread, other threads:[~2026-10-14 8:35 UTC | newest]
Thread overview: 8+ 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 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 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®