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
prev parent reply other threads:[~2026-10-29 9:17 UTC|newest]
Thread overview: 17+ 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 8:50 ` [PATCH v9 02/14] ALSA: virtio: Recover PCM state after a failed start Joonho Lee
2026-10-29 8:50 ` [PATCH v9 03/14] ALSA: virtio: Retain queued messages through device reset Joonho Lee
2026-10-29 8:50 ` [PATCH v9 04/14] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Joonho Lee
2026-10-29 8:50 ` [PATCH v9 05/14] ALSA: virtio: Keep control payloads alive through DMA completion Joonho Lee
2026-10-29 8:50 ` [PATCH v9 06/14] ALSA: virtio: wait through signals for PCM teardown Joonho Lee
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:50 ` [PATCH v9 08/14] ALSA: pcm: report partial progress under the stream lock Joonho Lee
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 8:50 ` [PATCH v9 11/14] docs: describe virtio-sound capture options Joonho Lee
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 8:50 ` [PATCH v9 13/14] ALSA: virtio: synchronize staged capture accounting during RESET Joonho Lee
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox