Linux virtualization list
 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 v9 09/14] ALSA: virtio: add staged capture accounting and lifecycle recovery
Date: Thu, 29 Oct 2026 09:29:59 +0000	[thread overview]
Message-ID: <sashiko-outbox-189039@kernel.org> (raw)
In-Reply-To: <20261029085032.70689-10-mynameisjune111@gmail.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] ALSA: virtio: race clearing vqueue in virtsnd_reset_vqs() before del_vqs()
- [High] ALSA: virtio: uncanceled elapsed_period work on sync_stop and close paths
- [Medium] ALSA: virtio: incomplete pause failure recovery in virtsnd_pcm_trigger()
- [Medium] ALSA: virtio: race in virtsnd_pcm_sync_stop() across prepare generations
- [Medium] ALSA: virtio: race between virtsnd_pcm_prepare_locked() and staged ack()
--

commit a385aa4275f0e96bd41c0f455c953ca4c07a25b9
Author: Joonho Lee <mynameisjune111@gmail.com>

ALSA: virtio: add staged capture accounting and lifecycle recovery

This commit introduces staged capture with request-owned DMA buffers and
separate produced, observed, and consumed accounting positions for virtio-snd.
It also adds lifecycle serialization and state tracking for control requests,
pause-restart recovery, and virtqueue resets.

> diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c
> index df7091b74a521..07d66225ce04a 100644
> --- a/sound/virtio/virtio_card.c
> +++ b/sound/virtio/virtio_card.c
> @@ -348,6 +348,22 @@ static void virtsnd_reset_vqs(struct virtio_snd *snd)
>  	struct virtio_device *vdev = snd->vdev;
>  	unsigned int i;
>  
> +	for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) {
> +		struct virtio_pcm_substream *vss = &snd->substreams[i];
> +		struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
> +
> +		guard(spinlock_irqsave)(&queue->lock);
> +		guard(spinlock)(&vss->lock);
> +		vss->reset_epoch++;
> +		vss->resetting = true;
> +		vss->prepared = false;
> +		vss->device_running = false;
> +		vss->release_required = false;
> +		vss->control_uncertain = false;
> +		vss->capture_reset_observing = false;
> +		vss->xfer_enabled = false;
> +		vss->suspended = true;
> +	}
>  	virtio_reset_device(vdev);
>  
>  	/* Deleting a virtqueue does not unmap its outstanding descriptors. */
> @@ -364,9 +380,20 @@ static void virtsnd_reset_vqs(struct virtio_snd *snd)
>  		spin_unlock_irqrestore(&queue->lock, flags);
>  	}
>  
> -	vdev->config->del_vqs(vdev);
> -	for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)
> +	for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) {
> +		guard(spinlock_irqsave)(&snd->queues[i].lock);
>  		snd->queues[i].vqueue = NULL;
> +	}
> +	vdev->config->del_vqs(vdev);

[Severity: High]
Could clearing snd->queues[i].vqueue before calling vdev->config->del_vqs(vdev)
cause a NULL pointer dereference in virtsnd_pcm_notify_cb()?

On transports whose reset callback does not call synchronize_irq() (such as
virtio_mmio's vm_reset() or virtio_vdpa's virtio_vdpa_reset()), virtqueue IRQ
handlers are not synchronized or freed until vdev->config->del_vqs(vdev) runs.

Unlike virtsnd_ctl_notify_cb() and virtsnd_event_notify_cb(), which use their
vqueue parameter directly, virtsnd_pcm_notify_cb() in
sound/virtio/virtio_pcm_msg.c dereferences queue->vqueue after acquiring
queue->lock:

	guard(spinlock_irqsave)(&queue->lock);
	do {
		virtqueue_disable_cb(queue->vqueue);
		while ((msg = virtqueue_get_buf(queue->vqueue, &written_bytes)))
			virtsnd_pcm_msg_complete(msg, written_bytes);
	} while (!virtqueue_enable_cb(queue->vqueue));

If an in-flight TX or RX interrupt on another CPU has entered
virtsnd_pcm_tx_notify_cb() or virtsnd_pcm_rx_notify_cb() and is spinning on
queue->lock while virtsnd_reset_vqs() sets snd->queues[i].vqueue = NULL, won't
virtsnd_pcm_notify_cb() dereference a NULL queue->vqueue as soon as
queue->lock is released?

> diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
> index de11bf162006e..ee07e5bb3c62f 100644
> --- a/sound/virtio/virtio_pcm_ops.c
> +++ b/sound/virtio/virtio_pcm_ops.c

[ ... ]

> @@ -320,48 +706,372 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)

[ ... ]

> +	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		if (epoch != vss->reset_epoch || vss->resetting)
> +			return -ECANCELED;
> +		vss->prepared = false;
> +		vss->buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
> +		vss->hw_ptr = 0;
> +		if (!pause_restart)
> +			vss->xfer_xrun = false;
> +		vss->suspended = false;
> +		vss->msg_count = 0;
> +		virtsnd_pcm_msg_reset(vss);
> +		memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect));
> +		vss->pcm_indirect.sw_buffer_size = vss->buffer_bytes;
> +		vss->pcm_indirect.hw_buffer_size = vss->buffer_bytes;
> +		if (vss->capture_staging) {
> +			vss->capture_boundary = substream->runtime->boundary;
> +			rc = virtsnd_pcm_capture_refresh_mode_locked(vss,
> +							     &free_running);
> +			if (rc)
> +				return rc;
> +		}
> +	}
>  
> -	memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect));
> -	vss->pcm_indirect.sw_buffer_size =
> -		vss->pcm_indirect.hw_buffer_size =
> -		snd_pcm_lib_buffer_bytes(substream);
> +	rc = virtsnd_pcm_dev_prepare(vss, epoch);
> +	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		if (epoch != vss->reset_epoch || vss->resetting)
> +			return -ECANCELED;
> +		vss->prepared = !rc;
> +		if (!rc)
> +			vss->release_required = true;
> +	}

[Severity: Medium]
Can virtsnd_pcm_prepare_locked() race with a concurrent
virtsnd_pcm_capture_staged_ack() call and latch a false xrun on the newly
prepared substream?

In the ALSA PCM core, snd_pcm_do_prepare() calls substream->ops->prepare()
under runtime->buffer_mutex without holding pcm_stream_lock, and only resets
runtime->control->appl_ptr to 0 afterward in snd_pcm_do_reset().

Here in virtsnd_pcm_prepare_locked(), virtsnd_pcm_msg_reset(vss) zeroes
capture_copied_total, capture_acked_total, capture_pointer_seen_total, and
capture_ready, and memset() zeroes vss->pcm_indirect.appl_ptr while
vss->runtime_active remains true. It then sleeps in virtsnd_pcm_dev_prepare()
and sets vss->prepared = true before returning to snd_pcm_do_prepare().

Meanwhile, a concurrent snd_pcm_sync_ptr() ioctl (without
SNDRV_PCM_SYNC_PTR_HWSYNC or SNDRV_PCM_SYNC_PTR_APPL) only acquires
pcm_stream_lock and invokes:

  pcm_lib_apply_appl_ptr()
    -> virtsnd_pcm_cp_ack()
      -> virtsnd_pcm_capture_staged_ack()

In virtsnd_pcm_capture_staged_ack():

		if (!vss->prepared && runtime->state != SNDRV_PCM_STATE_DRAINING &&
		    !(runtime->state == SNDRV_PCM_STATE_PAUSED &&
		      virtsnd_pcm_pause_restart_enabled(vss) &&
		      vss->pause_restart_state == VIRTSND_PCM_PAUSE_RESTART_RELEASED))
			return -EBADFD;

Once vss->prepared is set to true (or while virtsnd_pcm_dev_prepare() is
sleeping if runtime->state is SNDRV_PCM_STATE_DRAINING or PAUSED),
virtsnd_pcm_capture_staged_ack() can compare the unreset pre-prepare
runtime->control->appl_ptr against the zeroed vss->pcm_indirect.appl_ptr and
vss->capture_copied_total, setting vss->xfer_xrun = true. Even with the
follow-up commit "ALSA: virtio: synchronize staged capture accounting during
RESET", virtsnd_pcm_reset() returns early when vss->xfer_xrun is true, so
wouldn't the next start fail with -EPIPE?

[ ... ]

> @@ -377,68 +1087,218 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)

[ ... ]

>  	switch (command) {
>  	case SNDRV_PCM_TRIGGER_START:
>  	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> -		queue = virtsnd_pcm_queue(vss);
> -
> -		scoped_guard(spinlock_irqsave, &queue->lock) {
> -			guard(spinlock)(&vss->lock);
> -			if (vss->stopped) {
> -				substream->runtime->stop_operating = true;
> -				return -EBADFD;
> -			}
> -			if (vss->direction == SNDRV_PCM_STREAM_CAPTURE)
> -				rc = virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes);
> -			if (rc) {
> -				vss->stopped = true;
> -				substream->runtime->stop_operating = true;
> -				return rc;
> +		scoped_guard(spinlock_irqsave, &vss->lock) {
> +			if (vss->device_running && !vss->control_uncertain &&
> +			    !vss->resetting && !vss->stopped) {
> +				vss->xfer_enabled = true;
> +				return 0;
>  			}
> -			vss->xfer_enabled = true;
> +		}
> +		rc = virtsnd_pcm_start_queue(vss, epoch);
> +		if (rc) {
> +			virtsnd_pcm_start_failed(vss, epoch);
> +			return rc;
>  		}
>  
>  		msg = virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_START,
> -						GFP_KERNEL);
> +						GFP_KERNEL, epoch);
>  		if (!msg) {
> -			virtsnd_pcm_start_failed(vss);
> +			virtsnd_pcm_start_failed(vss, epoch);
>  			return -ENOMEM;
>  		}
>  
> -		rc = virtsnd_ctl_msg_send_sync(snd, msg);
> -		if (rc)
> -			virtsnd_pcm_start_failed(vss);
> +		rc = virtsnd_pcm_send_sync(vss, msg);
> +		if (rc && rc != -ECANCELED)
> +			virtsnd_pcm_start_failed(vss, epoch);
>  
> +		if (!rc) {
> +			guard(spinlock_irqsave)(&vss->lock);
> +			if (vss->resetting || vss->reset_epoch != epoch)
> +				return -ECANCELED;
> +			vss->device_running = true;
> +		}
> +		if (!rc && vss->capture_staging)
> +			virtsnd_pcm_schedule_period_elapsed(vss);
>  		return rc;
>  	case SNDRV_PCM_TRIGGER_SUSPEND:
> -		vss->suspended = true;
> +		scoped_guard(spinlock_irqsave, &vss->lock)
> +			vss->suspended = true;
>  		fallthrough;
>  	case SNDRV_PCM_TRIGGER_STOP:
>  	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
>  		scoped_guard(spinlock_irqsave, &vss->lock) {
> +			if (!vss->prepared)
> +				return 0;
>  			if (command != SNDRV_PCM_TRIGGER_PAUSE_PUSH)
>  				vss->stopped = true;
>  			vss->xfer_enabled = false;
> +			if (!vss->device_running)
> +				return 0;
>  		}
>  
> +		rc = virtsnd_pcm_stop_device(vss, epoch);
> +		if (rc && command == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
> +			guard(spinlock_irqsave)(&vss->lock);
> +			if (vss->reset_epoch == epoch && !vss->resetting &&
> +			    !vss->control_uncertain && vss->device_running &&
> +			    !vss->xfer_xrun)
> +				vss->xfer_enabled = true;
> +		}
> +		return rc;

[Severity: Medium]
What happens to staged capture RX descriptors if virtsnd_pcm_stop_device()
fails during SNDRV_PCM_TRIGGER_PAUSE_PUSH when pause-restart mode is disabled?

Before calling virtsnd_pcm_stop_device(), SNDRV_PCM_TRIGGER_PAUSE_PUSH clears
vss->xfer_enabled = false and drops vss->lock while the device is still
running. Any staged capture RX buffers that complete during this window see
vss->xfer_enabled == false in virtsnd_pcm_capture_complete(), so their audio
is discarded, msg->length is cleared to 0, vss->msg_count is decremented, and
virtsnd_pcm_capture_enqueue() is skipped.

If virtsnd_pcm_stop_device() fails with a definite error (so
vss->control_uncertain is false and vss->device_running remains true), both
the recovery path above and the SNDRV_PCM_TRIGGER_PAUSE_RELEASE fast path
invoked by snd_pcm_undo_pause() set vss->xfer_enabled = true without calling
virtsnd_pcm_capture_send() to refill the drained RX buffers. If all queued RX
descriptors completed while vss->xfer_enabled was false, won't vss->msg_count
remain 0 and stall capture indefinitely?

Also, the SNDRV_PCM_TRIGGER_PAUSE_RELEASE fast path when vss->device_running
is true does not check !vss->xfer_xrun before setting vss->xfer_enabled = true,
and if virtsnd_pcm_stop_device() fails with vss->control_uncertain == true on
a linked substream where snd_pcm_action_group() does not call
snd_pcm_undo_pause() on the failing stream, the substream is left in
SNDRV_PCM_STATE_RUNNING with vss->xfer_enabled == false, vss->stopped == false,
and runtime->stop_operating == false.

[ ... ]

> @@ -455,31 +1315,49 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
>  	unsigned int js = msecs_to_jiffies(virtsnd_msg_timeout_ms);
>  	bool need_release;
>  	int rc;
> +	u64 epoch;
> +
> +	if (virtsnd_pcm_pause_restart_enabled(vss))
> +		return virtsnd_pcm_pause_restart_sync_stop(substream);
>  
>  	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		if (vss->resetting)
> +			return -ECANCELED;
> +		epoch = vss->reset_epoch;
>  		vss->xfer_enabled = false;
>  		need_release = vss->release_required;
>  		if (need_release)
>  			vss->stopped = true;
>  	}
>  	cancel_work_sync(&vss->elapsed_period);

[Severity: High]
Can vss->elapsed_period remain pending across sync_stop, hw_free, and close,
racing with snd_pcm_detach_substream() freeing substream->runtime?

There appear to be two ways vss->elapsed_period is left uncanceled when a
substream is closed:

1. In virtsnd_reset_vqs(), vss->resetting = true is set in the first loop
before virtio_reset_device() and vdev->config->del_vqs() run, while
cancel_work_sync(&vss->elapsed_period) only runs in the final loop (and if
virtsnd_restore() fails in virtsnd_find_vqs(), vss->resetting stays true).
When vss->resetting is true, virtsnd_pcm_sync_stop_locked() and
virtsnd_pcm_pause_restart_sync_stop() return -ECANCELED before reaching
cancel_work_sync(&vss->elapsed_period) (and virtsnd_pcm_sync_stop() also
returns 0 early when !vss->stopped without canceling work). For non-staged
substreams (!vss->capture_staging), neither virtsnd_pcm_hw_free() nor
virtsnd_pcm_close() calls cancel_work_sync(&vss->elapsed_period):

sound/virtio/virtio_pcm_ops.c:virtsnd_pcm_close() {
	if (vss->capture_staging) {
		scoped_guard(spinlock_irqsave, &vss->lock) {
			vss->runtime_active = false;
			vss->xfer_enabled = false;
		}
		cancel_work_sync(&vss->elapsed_period);
	}
}

2. In virtsnd_pcm_pause_restart_sync_stop(), cancel_work_sync() runs near the
beginning of the function, but the subsequent error paths call
virtsnd_pcm_pause_restart_fail(), which re-schedules vss->elapsed_period when
vss->runtime_active is true.

When snd_pcm_release_substream() then calls snd_pcm_detach_substream(), which
clears substream->runtime = NULL and calls kfree(runtime) without holding
pcm_stream_lock, can virtsnd_pcm_period_elapsed() concurrently access the
freed runtime?

[ ... ]

> @@ -637,6 +1675,19 @@ 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);
> +	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		/* A newer prepare may have consumed this stop generation. */
> +		if (!vss->stopped)
> +			return 0;
> +	}
> +	return virtsnd_pcm_sync_stop_locked(substream);
> +}

[Severity: Medium]
Is checking !vss->stopped under vss->lifecycle_lock sufficient to detect that
a newer prepare already consumed an earlier stop generation?

In sound/core/pcm_native.c, snd_pcm_drop() drops pcm_stream_lock before
calling snd_pcm_sync_stop() without holding runtime->buffer_mutex. If thread A
in snd_pcm_sync_stop() clears runtime->stop_operating = false and is preempted
before acquiring vss->lifecycle_lock in virtsnd_pcm_sync_stop(), thread B can:

1. Call snd_pcm_prepare(), which acquires vss->lifecycle_lock, consumes thread
   A's stop via virtsnd_pcm_sync_stop_locked(), and prepares a new stream
   generation.
2. Start the stream via virtsnd_pcm_trigger(SNDRV_PCM_TRIGGER_START).
3. Issue SNDRV_PCM_TRIGGER_PAUSE_PUSH or SNDRV_PCM_TRIGGER_STOP, which run
   under pcm_stream_lock without vss->lifecycle_lock and set
   vss->stopped = true.

When thread A then acquires vss->lifecycle_lock, vss->stopped is true again,
so thread A proceeds into virtsnd_pcm_sync_stop_locked() against the new
generation.

In pause-restart mode, if thread B is mid-transition inside
virtsnd_pcm_pause_restart_pause() or virtsnd_pcm_pause_restart_stop(), thread
A will see virtsnd_pcm_pause_restart_transitioning(state) and force
VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED; or if thread B has completed
PAUSE_PUSH (reaching VIRTSND_PCM_PAUSE_RESTART_RELEASED with
vss->stopped == true), thread A will clear vss->stopped = false in
virtsnd_pcm_pause_restart_sync_stop(), causing a subsequent PAUSE_RELEASE in
virtsnd_pcm_pause_restart_start() to fail with -EBADFD. In normal mode, if
thread B is inside virtsnd_pcm_trigger(SNDRV_PCM_TRIGGER_STOP), thread A can
issue overlapping STOP and RELEASE control requests concurrently.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029085032.70689-1-mynameisjune111@gmail.com?part=9

  reply	other threads:[~2026-10-29  9:30 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29  8:50 [PATCH v9 00/14] ALSA: virtio: staged capture recovery and PCM RESET synchronization Joonho Lee
2026-10-29  8:50 ` [PATCH v9 01/14] ALSA: virtio: Propagate PCM send failures through ACK Joonho Lee
2026-10-29  9:14   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 02/14] ALSA: virtio: Recover PCM state after a failed start Joonho Lee
2026-10-29  9:25   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 03/14] ALSA: virtio: Retain queued messages through device reset Joonho Lee
2026-10-29  9:19   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 04/14] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Joonho Lee
2026-10-29  9:18   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 05/14] ALSA: virtio: Keep control payloads alive through DMA completion Joonho Lee
2026-10-29  9:02   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 06/14] ALSA: virtio: wait through signals for PCM teardown Joonho Lee
2026-10-29  9:13   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 07/14] ALSA: pcm: mark stop-threshold accesses for capture policy sampling Joonho Lee
2026-10-29  8:55   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 08/14] ALSA: pcm: report partial progress under the stream lock Joonho Lee
2026-10-29  8:56   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 09/14] ALSA: virtio: add staged capture accounting and lifecycle recovery Joonho Lee
2026-10-29  9:29   ` sashiko-bot [this message]
2026-10-29  8:50 ` [PATCH v9 10/14] ALSA: virtio: prioritize staged capture period work Joonho Lee
2026-10-29  9:00   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 11/14] docs: describe virtio-sound capture options Joonho Lee
2026-10-29  8:58   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 12/14] ALSA: pcm: add an optional reset operation under the stream lock Joonho Lee
2026-10-29  9:02   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 13/14] ALSA: virtio: synchronize staged capture accounting during RESET Joonho Lee
2026-10-29  9:09   ` sashiko-bot
2026-10-29  8:50 ` [PATCH v9 14/14] ALSA: virtio: rebase ordinary capture accounting on RESET Joonho Lee
2026-10-29  9:17   ` sashiko-bot

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-189039@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