From: sashiko-bot@kernel.org
To: "Joonho Lee" <mynameisjune111@gmail.com>, linux-sound@vger.kernel.org
Cc: virtualization@lists.linux.dev,
"Michael S. Tsirkin" <mst@redhat.com>,
"Eugenio Perez" <eperezma@redhat.com>
Subject: Re: [PATCH v7 16/16] ALSA: virtio: serialize capture lifecycle and reset state
Date: Wed, 28 Oct 2026 10:26:00 +0000 [thread overview]
Message-ID: <sashiko-outbox-187746@kernel.org> (raw)
In-Reply-To: <20261028094132.20364-17-mynameisjune111@gmail.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] ALSA: virtio: checking vss->suspended in sync_stop wedges suspended streams
- [High] ALSA: virtio: split reset_epoch update and missing checks corrupt pause state
- [High] ALSA: virtio: race and missing RX refill in virtsnd_pcm_ioctl() on RESET
- [Medium] ALSA: virtio: missing failure handling and state check in pause retry path
- [Medium] ALSA: virtio: missing XRUN wakeup on staged native-pause release failure
- [Medium] ALSA: virtio: race between snd_pcm_drop() and virtsnd_pcm_prepare()
--
commit 24d284e1ac3e3d50dab01d4de1927240e8a33acb
Author: Joonho Lee <mynameisjune111@gmail.com>
ALSA: virtio: serialize capture lifecycle and reset state
This patch serializes PCM lifecycle callbacks with a per-substream mutex,
rebases staged capture counters on RESET, and adds reset epoch and suspend
checks to control and sync-stop paths.
> diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c
> index 852cc27ac4a45..2fd8595046563 100644
> --- a/sound/virtio/virtio_card.c
> +++ b/sound/virtio/virtio_card.c
> @@ -354,6 +354,7 @@ static void virtsnd_reset_vqs(struct virtio_snd *snd)
>
> guard(spinlock_irqsave)(&queue->lock);
> guard(spinlock)(&vss->lock);
> + vss->reset_epoch++;
> vss->prepared = false;
> vss->xfer_enabled = false;
> vss->suspended = true;
[Severity: High]
Can incrementing vss->reset_epoch in the first loop of virtsnd_reset_vqs()
while resetting vss->pause_restart_state in the fourth loop corrupt the
pause-restart state machine if a control operation runs concurrently?
In virtsnd_reset_vqs(), the first loop increments vss->reset_epoch and
clears vss->prepared, while the fourth loop later resets
vss->pause_restart_state:
sound/virtio/virtio_card.c:virtsnd_reset_vqs() {
...
for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) {
cancel_work_sync(&vss->elapsed_period);
guard(spinlock_irqsave)(&vss->lock);
vss->stopped = false;
vss->xfer_xrun = false;
vss->pause_restart_state = VIRTSND_PCM_PAUSE_RESTART_NONE;
}
}
If virtsnd_pcm_pause_restart_send() enters between the first and fourth
loops, it samples the already-incremented vss->reset_epoch and transitions
vss->pause_restart_state to submitted_state. After the fourth loop resets
vss->pause_restart_state to VIRTSND_PCM_PAUSE_RESTART_NONE, the post-send
check in virtsnd_pcm_pause_restart_send() sees vss->reset_epoch == epoch
and vss->pause_restart_state != submitted_state, poisoning
vss->pause_restart_state to VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED
across virtsnd_restore().
Similarly, in virtsnd_pcm_pause_release_flush(), waking from
wait_event_timeout(vss->msg_empty, ...) when virtsnd_reset_vqs() cancels
pending RX buffers does not check vss->reset_epoch: if it runs before the
fourth loop it transitions to VIRTSND_PCM_PAUSE_RESTART_RELEASED and
returns 0 across a reset, and if it runs after the fourth loop it sees
vss->pause_restart_state == VIRTSND_PCM_PAUSE_RESTART_NONE and returns
-ETIMEDOUT instead of -ECANCELED. In virtsnd_pcm_pause_restart_start(),
the two locked blocks after virtsnd_pcm_pause_prepare() also omit
vss->reset_epoch checks, allowing vss->prepared = true to be set after the
first loop of virtsnd_reset_vqs() or returning -EBADFD instead of
-ECANCELED after the fourth loop.
[ ... ]
> diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
> index 0685a001cf95d..573fab6525a61 100644
> --- a/sound/virtio/virtio_pcm_ops.c
> +++ b/sound/virtio/virtio_pcm_ops.c
[ ... ]
> @@ -667,6 +724,14 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
> return rc;
> }
>
> +static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
> +{
> + struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
> +
> + guard(mutex)(&vss->lifecycle_lock);
> + return virtsnd_pcm_prepare_locked(substream);
> +}
[Severity: Medium]
Can a concurrent snd_pcm_drop() and snd_pcm_prepare() still race despite
vss->lifecycle_lock?
In sound/core/pcm_native.c, snd_pcm_drop() stops the stream (setting
vss->stopped = true and runtime->stop_operating = true), drops the stream
lock, and calls snd_pcm_sync_stop() without holding runtime->buffer_mutex.
snd_pcm_sync_stop() clears runtime->stop_operating = false before calling
substream->ops->sync_stop(substream) (virtsnd_pcm_sync_stop()), which only
then acquires vss->lifecycle_lock.
If a concurrent snd_pcm_prepare() runs after snd_pcm_drop() clears
runtime->stop_operating = false in snd_pcm_sync_stop() but before
virtsnd_pcm_sync_stop() acquires vss->lifecycle_lock, the prepare path's
own snd_pcm_sync_stop() sees runtime->stop_operating == false and skips
calling virtsnd_pcm_sync_stop(). If virtsnd_pcm_prepare() then acquires
vss->lifecycle_lock first, virtsnd_pcm_prepare_locked() does not call
virtsnd_pcm_sync_stop_locked() (unlike virtsnd_pcm_hw_params()), so it
observes vss->stopped == true and fails with -EBADFD.
Conversely, if the delayed virtsnd_pcm_sync_stop() from snd_pcm_drop()
acquires vss->lifecycle_lock after a concurrent prepare completes,
virtsnd_pcm_sync_stop_locked() now includes vss->prepared in need_release
(and virtsnd_pcm_pause_restart_sync_stop() releases
VIRTSND_PCM_PAUSE_RESTART_PREPARED), so it sends VIRTIO_SND_R_PCM_RELEASE
and clears vss->prepared = false on the newly prepared stream.
[ ... ]
> @@ -771,20 +849,31 @@ static int virtsnd_pcm_pause_restart_pause(struct virtio_pcm_substream *vss)
>
> scoped_guard(spinlock_irqsave, &vss->lock) {
> state = vss->pause_restart_state;
> - if (state == VIRTSND_PCM_PAUSE_RESTART_RELEASED)
> + if (state == VIRTSND_PCM_PAUSE_RESTART_RELEASED) {
> + if (vss->xfer_xrun)
> + virtsnd_pcm_schedule_period_elapsed(vss);
> return vss->xfer_xrun ? -EPIPE : 0;
> + }
> vss->xfer_enabled = false;
> vss->stopped = true;
> }
>
> if (state == VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT) {
> - rc = virtsnd_pcm_pause_restart_sync_stop(vss->substream);
> - if (!rc) {
> - guard(spinlock_irqsave)(&vss->lock);
> - vss->stopped = true;
> - if (vss->xfer_xrun)
> - rc = -EPIPE;
> + unsigned int js = msecs_to_jiffies(virtsnd_msg_timeout_ms);
> +
> + /* Trigger holds the PCM lock: never cancel the period worker here. */
> + if (!wait_event_timeout(vss->msg_empty,
> + !virtsnd_pcm_msg_pending_num(vss), js))
> + return -ETIMEDOUT;
> + scoped_guard(spinlock_irqsave, &vss->lock) {
> + if (vss->suspended)
> + return -ECANCELED;
> + vss->prepared = false;
> + vss->pause_restart_state = VIRTSND_PCM_PAUSE_RESTART_RELEASED;
> + rc = vss->xfer_xrun ? -EPIPE : 0;
> }
> + if (rc)
> + virtsnd_pcm_pause_restart_failed(vss);
> return rc;
> }
[Severity: Medium]
In the VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT branch of
virtsnd_pcm_pause_restart_pause(), does returning -ETIMEDOUT directly on
wait_event_timeout() failure skip calling virtsnd_pcm_pause_restart_failed()?
Previously this path called virtsnd_pcm_pause_restart_sync_stop(), which
invoked virtsnd_pcm_pause_restart_fail(vss, false) on timeout to set
vss->xfer_xrun = true, schedule vss->elapsed_period, and set
substream->runtime->stop_operating = true. In the new open-coded path,
timing out leaves vss->stopped = true while runtime->stop_operating and
vss->xfer_xrun remain false.
Also, when wait_event_timeout() returns non-zero, should the locked block
re-verify that vss->msg_count == 0 and vss->pause_restart_state ==
VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT before setting
VIRTSND_PCM_PAUSE_RESTART_RELEASED, matching
virtsnd_pcm_pause_release_flush() and virtsnd_pcm_pause_restart_sync_stop()?
[ ... ]
> @@ -945,10 +1039,12 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
> return -ENOMEM;
> }
>
> - rc = virtsnd_ctl_msg_send_sync(snd, msg);
> - if (rc)
> + rc = virtsnd_pcm_send_sync(vss, msg);
> + if (rc && rc != -ECANCELED)
> virtsnd_pcm_start_failed(vss);
>
> + if (!rc && vss->capture_staging)
> + virtsnd_pcm_schedule_period_elapsed(vss);
> return rc;
[Severity: Medium]
When SNDRV_PCM_TRIGGER_PAUSE_RELEASE fails for a staged capture stream in
native-pause mode (vss->capture_staging &&
!virtsnd_pcm_pause_restart_enabled(vss)), does calling
virtsnd_pcm_start_failed() leave paused capture readers sleeping without
an XRUN notification?
When virtsnd_pcm_capture_send() fails earlier in virtsnd_pcm_trigger() on
SNDRV_PCM_TRIGGER_PAUSE_RELEASE, the error path sets vss->xfer_xrun = true
and calls virtsnd_pcm_schedule_period_elapsed(vss):
sound/virtio/virtio_pcm_ops.c:virtsnd_pcm_trigger() {
...
if (rc) {
vss->stopped = true;
substream->runtime->stop_operating = true;
if (vss->capture_staging &&
vss->direction == SNDRV_PCM_STREAM_CAPTURE &&
command == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) {
/* Pause undo leaves ALSA paused; wake its readers. */
vss->xfer_xrun = true;
virtsnd_pcm_schedule_period_elapsed(vss);
}
return rc;
}
}
However, if SNDRV_PCM_TRIGGER_PAUSE_RELEASE fails either at the initial
(vss->stopped || !vss->prepared) check or when allocating/sending
VIRTIO_SND_R_PCM_START via virtsnd_pcm_start_failed(), vss->xfer_xrun is
never set and vss->elapsed_period is not scheduled, so ALSA's pause undo
leaves runtime->state in SNDRV_PCM_STATE_PAUSED without transitioning the
stream to XRUN.
[ ... ]
> @@ -1135,8 +1244,12 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
> return !rc ? -ETIMEDOUT : rc;
> }
>
> - scoped_guard(spinlock_irqsave, &vss->lock)
> + scoped_guard(spinlock_irqsave, &vss->lock) {
> + if (vss->suspended)
> + return -ECANCELED;
> + vss->prepared = false;
> vss->stopped = false;
> + }
>
> return 0;
> }
[Severity: High]
Won't returning -ECANCELED when vss->suspended is true in
virtsnd_pcm_sync_stop_locked() and virtsnd_pcm_pause_restart_sync_stop()
permanently wedge the substream after SNDRV_PCM_TRIGGER_SUSPEND?
During system suspend, snd_pcm_suspend_all() triggers
SNDRV_PCM_TRIGGER_SUSPEND, which in virtsnd_pcm_trigger() and
virtsnd_pcm_pause_restart_stop(vss, true) sets both vss->suspended = true
and vss->stopped = true. When virtsnd_pcm_sync_stop_locked() (or
virtsnd_pcm_pause_restart_sync_stop()) then runs, it sends
VIRTIO_SND_R_PCM_RELEASE and flushes the queue, but returns -ECANCELED
because vss->suspended is true, leaving vss->stopped = true and
vss->prepared = true.
Because this patch also removes vss->suspended = false from
virtsnd_pcm_open(), vss->suspended is now only cleared in
virtsnd_pcm_prepare_locked() after the vss->stopped check:
sound/virtio/virtio_pcm_ops.c:virtsnd_pcm_prepare_locked() {
...
scoped_guard(spinlock_irqsave, &vss->lock)
stopped = vss->stopped;
if (stopped) {
dev_err(&vdev->dev, "SID %u: stream release failed\n",
vss->sid);
substream->runtime->stop_operating = true;
return -EBADFD;
}
...
scoped_guard(spinlock_irqsave, &vss->lock) {
...
vss->suspended = false;
}
Once vss->stopped and vss->suspended are both true:
- virtsnd_pcm_prepare_locked() sees vss->stopped == true, sets
runtime->stop_operating = true, and returns -EBADFD before reaching
vss->suspended = false.
- virtsnd_pcm_hw_params() calls virtsnd_pcm_sync_stop_locked(), which sees
need_release (vss->stopped is true) and vss->suspended == true and fails
with -ECANCELED.
- Closing and reopening the PCM device calls virtsnd_pcm_open(), which no
longer clears vss->suspended and calls virtsnd_pcm_sync_stop(), failing
open with -ECANCELED.
In addition, virtsnd_pcm_pause_restart_fail() and
virtsnd_pcm_start_failed() now return early when vss->suspended is true.
In virtsnd_pcm_prepare_locked(), the else branch of if (!suspended) calls
virtsnd_pcm_dev_set_params() while vss->suspended is still true; if
virtsnd_pcm_dev_set_params() fails, calling
virtsnd_pcm_pause_restart_fail(vss, false) is a no-op that skips setting
vss->stopped, vss->xfer_xrun, and runtime->stop_operating. Likewise, after
virtsnd_freeze() and virtsnd_restore(), vss->suspended remains true until
prepare succeeds, so a failed resume via SNDRV_PCM_TRIGGER_PAUSE_RELEASE
causes virtsnd_pcm_pause_restart_fail() to return early without setting
vss->xfer_xrun, scheduling vss->elapsed_period, or setting
runtime->stop_operating.
[ ... ]
> @@ -1415,12 +1531,52 @@ static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
> return 0;
> }
>
> +static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
> +{
> + struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
> +
> + guard(mutex)(&vss->lifecycle_lock);
> + return virtsnd_pcm_sync_stop_locked(substream);
> +}
> +
> +static int virtsnd_pcm_ioctl(struct snd_pcm_substream *substream,
> + unsigned int cmd, void *arg)
> +{
> + struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + size_t position;
> + size_t period;
> + int rc;
> +
> + if (cmd != SNDRV_PCM_IOCTL1_RESET || !vss->capture_staging)
> + return snd_pcm_lib_ioctl(substream, cmd, arg);
> +
> + rc = snd_pcm_lib_ioctl(substream, cmd, arg);
> + if (rc)
> + return rc;
> + guard(pcm_stream_lock_irqsave)(substream);
> + runtime->status->hw_ptr %= runtime->buffer_size;
> + position = frames_to_bytes(runtime, runtime->status->hw_ptr);
> + period = snd_pcm_lib_period_bytes(substream);
> + guard(spinlock_irqsave)(&vss->lock);
> + vss->hw_ptr = position;
> + vss->capture_copied_total = position;
> + vss->capture_acked_total = position;
> + vss->capture_pointer_seen_total = position;
> + vss->capture_ready = 0;
> + vss->capture_notified_periods = period ? position / period : 0;
> + vss->capture_callback_snapshot = 0;
> + vss->capture_callback_snapshot_active = false;
> + vss->pcm_indirect.appl_ptr = runtime->status->hw_ptr;
> + return 0;
> +}
[Severity: High]
What prevents virtsnd_pcm_ioctl() on SNDRV_PCM_IOCTL1_RESET from racing
with RX completions and virtsnd_pcm_period_elapsed() on a running staged
capture stream, or leaving RX buffers unqueued after clearing
vss->capture_ready?
When SNDRV_PCM_IOCTL_RESET is called while the stream is in
SNDRV_PCM_STATE_RUNNING:
1. snd_pcm_lib_ioctl() -> snd_pcm_lib_ioctl_reset() acquires the PCM
stream lock, updates runtime->status->hw_ptr via
virtsnd_pcm_cp_pointer(), reduces runtime->status->hw_ptr modulo
runtime->buffer_size, and drops the PCM stream lock. Before
virtsnd_pcm_ioctl() acquires vss->lock, virtsnd_pcm_capture_complete()
can run in RX interrupt context: it checks ring capacity against the
pre-reset vss->capture_ready (which can trigger a false ring_full XRUN)
and advances vss->hw_ptr and vss->capture_copied_total as new audio is
copied. virtsnd_pcm_ioctl() then overwrites vss->hw_ptr and
vss->capture_copied_total with the older position sampled before
vss->lock was taken, rewinding vss->hw_ptr over newly captured audio.
2. Between snd_pcm_lib_ioctl_reset() dropping the stream lock,
virtsnd_pcm_ioctl() returning, and snd_pcm_do_reset() /
snd_pcm_post_reset() re-acquiring the stream lock to zero
runtime->hw_ptr_base and set runtime->control->appl_ptr =
runtime->status->hw_ptr, virtsnd_pcm_period_elapsed() can run on
system_highpri_wq. If virtsnd_pcm_period_elapsed() advances
runtime->status->hw_ptr before snd_pcm_post_reset() sets
runtime->control->appl_ptr = runtime->status->hw_ptr,
runtime->control->appl_ptr is moved ahead of vss->pcm_indirect.appl_ptr
and vss->capture_acked_total without an ACK callback, leaving
vss->capture_ready inflated and causing subsequent ACKs or completions
to fail with -EPIPE.
3. In bounded capture mode (!vss->capture_free_running),
virtsnd_pcm_capture_enqueue() throttles queued RX descriptors based on
vss->buffer_bytes - vss->capture_ready. After virtsnd_pcm_ioctl() resets
vss->capture_ready = 0 on a running stream, nothing calls
virtsnd_pcm_capture_send(vss) under queue->lock and vss->lock to refill
held-back RX requests.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028094132.20364-1-mynameisjune111@gmail.com?part=16
prev parent reply other threads:[~2026-10-28 10:26 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 9:41 [PATCH v7 00/16] ALSA: virtio: staged capture and dsnoop recovery Joonho Lee
2026-10-28 9:41 ` [PATCH v7 01/16] ALSA: virtio: Propagate PCM send failures through ACK Joonho Lee
2026-10-28 10:07 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 02/16] ALSA: virtio: Recover PCM state after a failed start Joonho Lee
2026-10-28 10:18 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 03/16] ALSA: virtio: Retain queued messages through device reset Joonho Lee
2026-10-28 10:15 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 04/16] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Joonho Lee
2026-10-28 10:12 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 05/16] ALSA: virtio: Keep control payloads alive through DMA completion Joonho Lee
2026-10-28 9:54 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 06/16] ALSA: virtio: wait through signals for PCM teardown Joonho Lee
2026-10-28 10:07 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 07/16] ALSA: virtio: stage VZ capture and restart paused streams Joonho Lee
2026-10-28 10:14 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 08/16] ALSA: virtio: account staged capture reads before RX refill Joonho Lee
2026-10-28 9:54 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 09/16] ALSA: virtio: prioritize staged capture period work Joonho Lee
2026-10-28 10:04 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 10/16] ALSA: pcm: mark stop-threshold accesses for capture policy sampling Joonho Lee
2026-10-28 9:47 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 11/16] ALSA: virtio-snd: fix dsnoop capture with staged buffers Joonho Lee
2026-10-28 10:20 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 12/16] ALSA: virtio: complete control transitions and preserve drain tail Joonho Lee
2026-10-28 9:57 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 13/16] ALSA: virtio: fence staged capture state and recovery Joonho Lee
2026-10-28 10:09 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 14/16] docs: describe virtio-sound capture options Joonho Lee
2026-10-28 9:51 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 15/16] ALSA: pcm: synchronize stopped paused streams Joonho Lee
2026-10-28 10:21 ` sashiko-bot
2026-10-28 9:41 ` [PATCH v7 16/16] ALSA: virtio: serialize capture lifecycle and reset state Joonho Lee
2026-10-28 10:26 ` sashiko-bot [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-187746@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=eperezma@redhat.com \
--cc=linux-sound@vger.kernel.org \
--cc=mst@redhat.com \
--cc=mynameisjune111@gmail.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=virtualization@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.