All of lore.kernel.org
 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 14/14] ALSA: virtio: rebase ordinary capture accounting on RESET
Date: Thu, 29 Oct 2026 09:17:17 +0000	[thread overview]
Message-ID: <sashiko-outbox-189016@kernel.org> (raw)
In-Reply-To: <20261029085032.70689-15-mynameisjune111@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] ALSA: virtio: virtsnd_pcm_indirect_reset() schedules work during device reset
- [High] ALSA: virtio: paused virtsnd_pcm_indirect_reset() ignores unconsumed hw_ptr
- [High] ALSA: virtio: virtsnd_pcm_msg_complete() clears XRUN latched by indirect reset
- [Medium] ALSA: virtio: snd_pcm_lib_reset() ACK refill failure bypasses indirect reset
--

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

ALSA: virtio: rebase ordinary capture accounting on RESET

This patch adds virtsnd_pcm_indirect_reset() and selects
virtsnd_pcm_capture_indirect_ops for non-staged capture streams to keep
indirect capture accounting synchronized with the ALSA core during
SNDRV_PCM_IOCTL_RESET. When refilling RX buffers fails after
snd_pcm_lib_reset() mutates hw_ptr, it latches XRUN and schedules
elapsed_period so the core can finish rebasing appl_ptr before reporting
the error.

> diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
> index c3d272163907d..ba255803c6577 100644
> --- a/sound/virtio/virtio_pcm_ops.c
> +++ b/sound/virtio/virtio_pcm_ops.c

[ ... ]

> @@ -1693,6 +1697,62 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
>  	return virtsnd_pcm_sync_stop_locked(substream);
>  }
>  
> +/*
> + * The PCM lock covers indirect accounting and core completion. Once lib_reset
> + * changes hw_ptr, complete the core rebase even if refilling fails. The latched
> + * XRUN rejects subsequent pointer/ACK operations; the worker publishes XRUN to
> + * paused streams too. RESET success therefore does not imply stream recovery.
> + */
> +static int virtsnd_pcm_indirect_reset(struct snd_pcm_substream *substream)
> +{
> +	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +	struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
> +	struct virtsnd_pcm_transfer transfer = {};
> +	int rc;
> +
> +	rc = snd_pcm_lib_reset(substream);
> +	if (rc)
> +		return rc;
> +	if (!snd_pcm_running(substream) &&
> +	    runtime->state != SNDRV_PCM_STATE_PAUSED)
> +		return 0;

[Severity: Medium]
What happens if RX virtqueue refilling fails inside virtsnd_pcm_cp_ack()
while snd_pcm_lib_reset() is updating the pointer on a running stream?

When snd_pcm_running(substream) is true, the call chain is:

  virtsnd_pcm_indirect_reset()
    -> snd_pcm_lib_reset()
      -> snd_pcm_update_hw_ptr()
        -> virtsnd_pcm_cp_pointer()
          -> snd_pcm_indirect_capture_pointer()
            -> virtsnd_pcm_cp_ack()

In include/sound/pcm-indirect.h:snd_pcm_indirect_capture_pointer(), rec
fields are mutated before invoking substream->ops->ack():

	rec->hw_io = ptr;
	rec->hw_ready += bytes;
	qsize = rec->hw_queue_size ? rec->hw_queue_size : rec->hw_buffer_size;
	if (rec->hw_ready > qsize)
		return SNDRV_PCM_POS_XRUN;
	rec->sw_io += bytes;
	if (rec->sw_io >= rec->sw_buffer_size)
		rec->sw_io -= rec->sw_buffer_size;
	if (substream->ops->ack) {
		err = substream->ops->ack(substream);
		if (err == -EPIPE)
			return SNDRV_PCM_POS_XRUN;
	}

Whenever rec->hw_ready > 0 and rec->sw_ready < rec->sw_buffer_size,
virtsnd_pcm_cp_ack() runs snd_pcm_indirect_capture_transfer() and attempts
to refill the RX virtqueue before snd_pcm_lib_reset() returns.

If virtsnd_pcm_msg_send() fails during that ack callback,
snd_pcm_indirect_capture_pointer() returns SNDRV_PCM_POS_XRUN,
snd_pcm_update_hw_ptr0() transitions runtime->state to
SNDRV_PCM_STATE_XRUN and returns -EPIPE, and snd_pcm_lib_reset() executes:

	if (snd_pcm_running(substream) && snd_pcm_update_hw_ptr(substream) >= 0)
		runtime->status->hw_ptr %= runtime->buffer_size;
	else {
		runtime->status->hw_ptr = 0;
		runtime->hw_ptr_wrap = 0;
	}

virtsnd_pcm_indirect_reset() then sees !snd_pcm_running(substream) and
runtime->state != SNDRV_PCM_STATE_PAUSED and returns 0 early, so
snd_pcm_do_reset() sets runtime->control->appl_ptr = 0 while
vss->pcm_indirect retains its partially updated hw_io, hw_ready, and sw_io
values.

Should virtsnd_pcm_cp_ack() be suppressed during snd_pcm_lib_reset() (as
staged capture does with vss->capture_reset_observing) so that refilling
only happens once in virtsnd_pcm_indirect_reset() after hw_ptr is rebased?

> +
> +	guard(spinlock_irqsave)(&queue->lock);
> +	guard(spinlock)(&vss->lock);
> +	if (vss->resetting || !vss->prepared || vss->stopped) {
> +		vss->xfer_xrun = true;
> +		virtsnd_pcm_schedule_period_elapsed(vss);
> +		return 0;
> +	}

[Severity: Critical]
Can scheduling vss->elapsed_period here when vss->resetting is true cause
a use-after-free on substream->runtime during substream close?

Unlike virtsnd_pcm_reset(), which returns 0 immediately when
vss->resetting is true:

	if (vss->resetting)
		return 0;

virtsnd_pcm_indirect_reset() calls virtsnd_pcm_schedule_period_elapsed(vss)
when vss->resetting is true (which can happen after virtsnd_freeze() /
virtsnd_reset_vqs() has already set vss->resetting = true and finished its
own cancel_work_sync(&vss->elapsed_period)).

If userspace then closes the substream while vss->resetting is still true,
virtsnd_pcm_sync_stop_locked() returns -ECANCELED before reaching
cancel_work_sync(&vss->elapsed_period):

	scoped_guard(spinlock_irqsave, &vss->lock) {
		if (vss->resetting)
			return -ECANCELED;
		...
	}
	cancel_work_sync(&vss->elapsed_period);

In addition, both virtsnd_pcm_hw_free() and virtsnd_pcm_close() only call
cancel_work_sync(&vss->elapsed_period) when vss->capture_staging is true:

	if (vss->capture_staging) {
		scoped_guard(spinlock_irqsave, &vss->lock) {
			vss->runtime_active = false;
			vss->xfer_enabled = false;
		}
		cancel_work_sync(&vss->elapsed_period);
	}

When snd_pcm_release_substream() subsequently calls
snd_pcm_detach_substream(), runtime is freed without holding
pcm_stream_lock, while virtsnd_pcm_period_elapsed() may still be queued or
running and dereferencing substream->runtime in
sound/virtio/virtio_pcm.c:virtsnd_pcm_period_elapsed().

Also, if this branch is taken when vss->resetting is false and
!vss->prepared && !vss->stopped, virtsnd_pcm_transfer_failed(vss) is not
called, leaving vss->stopped false so a subsequent virtsnd_pcm_prepare()
skips virtsnd_pcm_sync_stop_locked() without canceling the scheduled work.

> +
> +	/* Pausing does not move the device's fixed DMA buffer positions. */
> +	if (runtime->state == SNDRV_PCM_STATE_PAUSED)
> +		runtime->status->hw_ptr =
> +			bytes_to_frames(runtime, vss->pcm_indirect.sw_io);
> +
> +	transfer.indirect = vss->pcm_indirect;
> +	transfer.indirect.sw_ready = 0;

[Severity: High]
When runtime->state == SNDRV_PCM_STATE_PAUSED, does using
vss->pcm_indirect.sw_io and vss->pcm_indirect.hw_ready directly miss RX
completions that already advanced vss->hw_ptr?

For ordinary capture (!vss->capture_staging), RX completions in
sound/virtio/virtio_pcm_msg.c:virtsnd_pcm_msg_complete() advance
vss->hw_ptr and clear msg->length = 0 under vss->lock without updating
vss->pcm_indirect:

		vss->hw_ptr += data_bytes;
	}

	if (vss->hw_ptr >= vss->buffer_bytes)
		vss->hw_ptr -= vss->buffer_bytes;

	msg->length = 0;

vss->pcm_indirect (hw_io, sw_io, and hw_ready) is only updated from
vss->hw_ptr when virtsnd_pcm_cp_pointer() calls
snd_pcm_indirect_capture_pointer().

When a stream is paused via snd_pcm_pause(), the core does not call
snd_pcm_update_hw_ptr(). Any RX completion whose elapsed_period work runs
after runtime->state becomes SNDRV_PCM_STATE_PAUSED exits early in
sound/virtio/virtio_pcm.c:virtsnd_pcm_period_elapsed():

	if (!snd_pcm_running(substream)) {
		...
		return;
	}

And any RX completion arriving during SNDRV_PCM_TRIGGER_PAUSE_PUSH while
virtsnd_pcm_stop_device() waits for VIRTIO_SND_R_PCM_STOP also advances
vss->hw_ptr without updating vss->pcm_indirect.

When virtsnd_pcm_indirect_reset() then runs in SNDRV_PCM_STATE_PAUSED:
1. snd_pcm_lib_reset() skips snd_pcm_update_hw_ptr() because
   snd_pcm_running(substream) is false, and sets runtime->status->hw_ptr = 0.
2. virtsnd_pcm_indirect_reset() sets runtime->status->hw_ptr from the stale
   vss->pcm_indirect.sw_io and runs snd_pcm_indirect_capture_transfer()
   without folding the unconsumed delta (vss->hw_ptr - vss->pcm_indirect.hw_io)
   into hw_io, sw_io, and hw_ready.

As a result, completed RX messages between hw_io and vss->hw_ptr are not
counted in hw_ready and are not re-submitted to the RX virtqueue,
hw_ptr/appl_ptr are rebased to the stale sw_io instead of vss->hw_ptr, and
after resume the next virtsnd_pcm_cp_pointer() call sees those pre-RESET
bytes as newly captured data. (Note also that if the earlier
vss->resetting || !vss->prepared || vss->stopped check returns 0 while
paused, runtime->status->hw_ptr is left at 0 from snd_pcm_lib_reset()
rather than restored from sw_io.)

> +	/* Consume no application delta while returning discarded RX space. */
> +	transfer.indirect.appl_ptr = runtime->control->appl_ptr;
> +	rc = snd_pcm_indirect_capture_transfer(substream, &transfer.indirect,
> +					       virtsnd_pcm_trans_copy);
> +	if (!rc)
> +		rc = transfer.error;
> +	if (rc) {
> +		virtsnd_pcm_transfer_failed(vss);
> +		vss->xfer_xrun = true;
> +		virtsnd_pcm_schedule_period_elapsed(vss);
> +		return 0;
> +	}

[Severity: High]
Can an in-flight RX completion in virtsnd_pcm_msg_complete() clear
vss->xfer_xrun before virtsnd_pcm_period_elapsed() or subsequent pointer
checks observe the latched XRUN?

When snd_pcm_indirect_capture_transfer() fails here (or on the earlier
error path), virtsnd_pcm_transfer_failed(vss) sets vss->xfer_enabled = false
and vss->stopped = true, but it does not stop the VirtIO device or flush RX
buffers already queued in the virtqueue (including any messages queued and
kicked by earlier iterations of virtsnd_pcm_trans_copy() before a later
period failed).

In sound/virtio/virtio_pcm_msg.c:virtsnd_pcm_msg_complete(), any completing
non-staged message unconditionally clears vss->xfer_xrun under vss->lock:

	msg->length = 0;

	vss->xfer_xrun = false;
	vss->msg_count--;

If an in-flight RX message completes before the scheduled
virtsnd_pcm_period_elapsed() worker runs, vss->xfer_xrun is reset to false.
In sound/virtio/virtio_pcm.c:virtsnd_pcm_period_elapsed():

		scoped_guard(spinlock_irqsave, &vss->lock) {
			runtime = substream->runtime;
			paused_xrun = runtime &&
				      runtime->state == SNDRV_PCM_STATE_PAUSED &&
				      vss->xfer_xrun;
			if (paused_xrun)
				virtsnd_pcm_capture_arm_paused_stop(vss);
		}

paused_xrun then evaluates to false, so a paused stream never transitions
to SNDRV_PCM_STATE_XRUN. Because RESET rebased appl_ptr = hw_ptr
(avail == 0), subsequent reads on the paused stream never invoke pointer()
or ack(), leaving nonblocking reads returning -EAGAIN indefinitely and
blocking reads hanging instead of returning -EPIPE. Furthermore, because
this error return skips updating vss->pcm_indirect, vss->pcm_indirect is
left out of sync with the rebased runtime->control->appl_ptr.

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

      reply	other threads:[~2026-10-29  9:17 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
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 [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-189016@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.