Linux Sound subsystem development
 help / color / mirror / Atom feed
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

      reply	other threads:[~2026-10-28 10:26 UTC|newest]

Thread overview: 21+ 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  9:41 ` [PATCH v7 02/16] ALSA: virtio: Recover PCM state after a failed start Joonho Lee
2026-10-28  9:41 ` [PATCH v7 03/16] ALSA: virtio: Retain queued messages through device reset Joonho Lee
2026-10-28  9:41 ` [PATCH v7 04/16] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Joonho Lee
2026-10-28  9:41 ` [PATCH v7 05/16] ALSA: virtio: Keep control payloads alive through DMA completion Joonho Lee
2026-10-28  9:41 ` [PATCH v7 06/16] ALSA: virtio: wait through signals for PCM teardown Joonho Lee
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:41 ` [PATCH v7 09/16] ALSA: virtio: prioritize staged capture period work Joonho Lee
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: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:41 ` [PATCH v7 13/16] ALSA: virtio: fence staged capture state and recovery Joonho Lee
2026-10-28  9:41 ` [PATCH v7 14/16] docs: describe virtio-sound capture options Joonho Lee
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox