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 v11 09/14] ALSA: virtio: add staged capture accounting and lifecycle recovery
Date: Sat, 31 Oct 2026 02:51:04 +0000 [thread overview]
Message-ID: <sashiko-outbox-191718@kernel.org> (raw)
In-Reply-To: <20261031020855.2402-10-mynameisjune111@gmail.com>
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] ALSA: virtio: false XRUN on capture drain in virtsnd_pcm_msg_complete()
- [High] ALSA: virtio: buffer_mutex trylock in ack corrupts indirect PCM state
- [High] ALSA: virtio: buffer_mutex trylock in virtsnd_pcm_pb_ack() fails during RESET
- [Medium] ALSA: virtio: staged capture drops RX completions during pause in virtsnd_pcm_capture_complete()
- [Medium] ALSA: virtio: missed XRUN wakeup when RX ring fills during pause in virtsnd_pcm_msg_complete()
- [Medium] ALSA: virtio: virtsnd_pcm_event() skips period work for ordinary capture XRUN events
- [Medium] ALSA: virtio: virtsnd_pcm_start_queue() drops period notifications
- [Medium] ALSA: virtio: failed STOP in virtsnd_pcm_trigger() misses xfer_xrun latch
- [Medium] ALSA: virtio: premature state reset in virtsnd_pcm_prepare_locked()
- [Medium] ALSA: virtio: missing stopped check in virtsnd_pcm_capture_staged_ack()
--
commit 89969d05596ec30bdd3e3e6560275f8867f0911d
Author: Joonho Lee <mynameisjune111@gmail.com>
ALSA: virtio: add staged capture accounting and lifecycle recovery
This commit adds staged capture accounting using request-owned DMA buffers and
monotonic position counters, while separating device running state, RELEASE
obligations, and reset epochs from ALSA runtime state. It also serializes PCM
lifecycle callbacks with lifecycle_lock and updates pause, XRUN, and period
wakeup handling across ordinary and staged capture paths.
Link: https://sashiko.dev/#/patchset/20261030052629.27742-1-mynameisjune111%40gmail.com?part=9
> diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c
> index be3893de40a55..aed94a892aaa4 100644
> --- a/sound/virtio/virtio_pcm.c
> +++ b/sound/virtio/virtio_pcm.c
[ ... ]
> @@ -517,8 +630,11 @@ void virtsnd_pcm_event(struct virtio_snd *snd, struct virtio_snd_event *event)
> break;
> case VIRTIO_SND_EVT_PCM_XRUN:
> scoped_guard(spinlock, &vss->lock) {
> - if (vss->xfer_enabled)
> + if (vss->xfer_enabled) {
> vss->xfer_xrun = true;
> + if (vss->capture_staging)
> + virtsnd_pcm_schedule_period_elapsed(vss);
> + }
> }
> break;
> }
[Severity: Medium]
When VIRTIO_SND_EVT_PCM_XRUN arrives in virtsnd_pcm_event() while
vss->xfer_enabled is true, why is virtsnd_pcm_schedule_period_elapsed(vss)
only called when vss->capture_staging is set?
In virtsnd_pcm_period_elapsed(), ordinary capture now checks vss->xfer_xrun
and calls snd_pcm_progress_under_stream_lock(substream), which observes
SNDRV_PCM_POS_XRUN from virtsnd_pcm_cp_pointer() and triggers
__snd_pcm_xrun().
However, if the device sends VIRTIO_SND_EVT_PCM_XRUN on an ordinary capture
substream (!vss->capture_staging) and stops completing RX buffers, skipping
virtsnd_pcm_schedule_period_elapsed(vss) here leaves ALSA unaware of the XRUN
until a read timeout expires. Should this check vss->direction ==
SNDRV_PCM_STREAM_CAPTURE instead of vss->capture_staging?
> diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c
> index b94ab24f5ffef..cdd26729568a1 100644
> --- a/sound/virtio/virtio_pcm_msg.c
> +++ b/sound/virtio/virtio_pcm_msg.c
[ ... ]
> @@ -478,6 +530,290 @@ int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset,
[ ... ]
> +static void virtsnd_pcm_capture_complete(struct virtio_pcm_msg *msg,
> + size_t written_bytes)
> +{
> + struct virtio_pcm_substream *vss = msg->substream;
> + size_t audio_bytes = 0;
> + size_t next_ready = 0;
> + u64 next_copied_total = 0;
> + bool free_running = false;
> + bool got_audio = false;
> +
> + guard(spinlock)(&vss->lock);
> + vss->msg_count--;
> + if (vss->runtime_active && vss->xfer_enabled && !vss->xfer_xrun) {
> + struct snd_pcm_runtime *runtime = vss->substream->runtime;
[Severity: Medium]
In virtsnd_pcm_capture_complete(), does gating completion processing on
vss->xfer_enabled drop in-flight RX completions during a native pause
transition?
When userspace triggers SNDRV_PCM_TRIGGER_PAUSE_PUSH on an active staged
capture stream (with virtsnd_capture_pause_restart disabled),
virtsnd_pcm_trigger() clears vss->xfer_enabled = false under vss->lock while
keeping vss->prepared = true and vss->stopped = false, and then calls
virtsnd_pcm_stop_device() to send VIRTIO_SND_R_PCM_STOP synchronously:
sound/virtio/virtio_pcm_ops.c:virtsnd_pcm_trigger() {
...
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);
}
While VIRTIO_SND_R_PCM_STOP is in flight (both when STOP succeeds and when
the device rejects STOP), the device can complete already-queued RX requests.
Unlike ordinary capture in virtsnd_pcm_msg_complete(), which checks
vss->prepared && !vss->stopped for NOMMU copyout and still updates
vss->hw_ptr, vss->capture_copied_total, and vss->xfer_xrun when
vss->xfer_enabled is false, virtsnd_pcm_capture_complete() skips status
validation, overrun checks, memcpy() into runtime->dma_area, and counter
updates, and then resets msg->length = 0.
When pause is rejected or later resumed via SNDRV_PCM_TRIGGER_PAUSE_RELEASE,
virtsnd_pcm_capture_enqueue() zeroes and re-queues those descriptors,
permanently losing the captured audio frames and any device status or ring
overrun error.
[ ... ]
> @@ -580,14 +924,22 @@ static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
> * the actual SG payload capacity before accounting them.
> */
> if (vss->direction == SNDRV_PCM_STREAM_PLAYBACK ||
> - written_bytes <= sizeof(msg->status)) {
> - vss->hw_ptr += msg->length;
> - } else {
> - size_t data_bytes = min_t(size_t,
> - written_bytes - sizeof(msg->status),
> - msg->length);
> -
> - vss->hw_ptr += data_bytes;
> + written_bytes <= sizeof(*msg->status))
> + completed_bytes = msg->length;
> + else
> + completed_bytes = min_t(size_t,
> + written_bytes - sizeof(*msg->status),
> + msg->length);
> + vss->hw_ptr += completed_bytes;
> + if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) {
> + /* A full unseen ring cannot be represented by the modulo pointer. */
> + if (check_add_overflow(vss->capture_copied_total,
> + (u64)completed_bytes,
> + &vss->capture_copied_total) ||
> + vss->capture_copied_total < vss->capture_pointer_seen_total ||
> + vss->capture_copied_total - vss->capture_pointer_seen_total >=
> + vss->buffer_bytes)
> + vss->xfer_xrun = true;
> }
[Severity: High]
Can this unconditional update of vss->capture_copied_total in
virtsnd_pcm_msg_complete() trigger a false XRUN during capture drain?
When an application calls snd_pcm_drain() (SNDRV_PCM_IOCTL_DRAIN) on a
running ordinary capture stream with unread frames in the buffer,
snd_pcm_do_drain_init() transitions the stream to SNDRV_PCM_STATE_DRAINING
and invokes virtsnd_pcm_trigger(SNDRV_PCM_TRIGGER_STOP), which sets
vss->stopped = true and vss->xfer_enabled = false and sends
VIRTIO_SND_R_PCM_STOP.
In SNDRV_PCM_STATE_DRAINING, snd_pcm_running(substream) is false and
virtsnd_pcm_cp_pointer() is no longer called, so
vss->capture_pointer_seen_total stops advancing. However,
virtsnd_pcm_msg_complete() continues to add completed_bytes (including a full
period msg->length when written_bytes <= sizeof(*msg->status) for empty or
status-only completions) to vss->hw_ptr and vss->capture_copied_total even
when vss->stopped is true.
When the seeded RX ring descriptors complete after STOP or RELEASE,
vss->capture_copied_total - vss->capture_pointer_seen_total reaches
vss->buffer_bytes and latches vss->xfer_xrun = true. Subsequent draining
reads via snd_pcm_readi() or SNDRV_PCM_IOCTL_SYNC_PTR then call
virtsnd_pcm_cp_ack_locked(), which checks:
if (vss->xfer_xrun ||
(vss->stopped && runtime->state != SNDRV_PCM_STATE_DRAINING)) {
rc = -EPIPE;
}
and aborts the capture drain with -EPIPE, discarding the already-captured
audio frames.
> if (vss->hw_ptr >= vss->buffer_bytes)
> @@ -595,7 +947,9 @@ static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
>
> msg->length = 0;
>
> - vss->xfer_xrun = false;
> + /* Preserve a latched capture error until prepare recovers it. */
> + if (vss->direction == SNDRV_PCM_STREAM_PLAYBACK)
> + vss->xfer_xrun = false;
> vss->msg_count--;
>
> if (vss->xfer_enabled) {
> @@ -603,8 +957,8 @@ static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
>
> runtime->delay =
> bytes_to_frames(runtime,
> - le32_to_cpu(msg->status.latency_bytes));
> - schedule_work(&vss->elapsed_period);
> + le32_to_cpu(msg->status->latency_bytes));
> + virtsnd_pcm_schedule_period_elapsed(vss);
> } else if (!vss->msg_count) {
> wake_up_all(&vss->msg_empty);
> }
[Severity: Medium]
What happens if in-flight ordinary capture RX completions fill the unread
ring while SNDRV_PCM_TRIGGER_PAUSE_PUSH is waiting for VIRTIO_SND_R_PCM_STOP
and the STOP request succeeds?
During SNDRV_PCM_TRIGGER_PAUSE_PUSH, virtsnd_pcm_trigger() clears
vss->xfer_enabled = false before calling virtsnd_pcm_stop_device(vss, epoch).
If the device completes queued RX requests before processing
VIRTIO_SND_R_PCM_STOP and vss->capture_copied_total -
vss->capture_pointer_seen_total reaches vss->buffer_bytes,
virtsnd_pcm_msg_complete() latches vss->xfer_xrun = true, but skips
virtsnd_pcm_schedule_period_elapsed(vss) because vss->xfer_enabled is false.
When virtsnd_pcm_stop_device() returns 0, virtsnd_pcm_trigger() only checks
if (rc && command == SNDRV_PCM_TRIGGER_PAUSE_PUSH) and returns 0 without
scheduling elapsed_period. ALSA transitions runtime->state to
SNDRV_PCM_STATE_PAUSED, and the paused_xrun handler in
virtsnd_pcm_period_elapsed() never runs to move the stream to
SNDRV_PCM_STATE_XRUN, leaving readers in wait_for_avail() sleeping
indefinitely.
> diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
> index ad602b6b6530b..d587c8f699553 100644
> --- a/sound/virtio/virtio_pcm_ops.c
> +++ b/sound/virtio/virtio_pcm_ops.c
[ ... ]
> @@ -320,47 +706,383 @@ 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;
> + }
> + }
> +
> + 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;
> + }
> + if (pause_restart) {
> + if (rc) {
> + if (rc == -ECANCELED)
> + return rc;
> + virtsnd_pcm_pause_restart_fail(vss, false, epoch);
> + } else {
> + scoped_guard(spinlock_irqsave, &vss->lock) {
> + if (vss->resetting || vss->reset_epoch != epoch)
> + return -ECANCELED;
> + vss->stopped = false;
> + vss->xfer_xrun = false;
> + }
> + }
> + }
> +
> + return rc;
> +}
[Severity: Medium]
In virtsnd_pcm_prepare_locked(), if virtsnd_pcm_dev_prepare() fails when
pause_restart is false, doesn't clearing vss->xfer_xrun and resetting
vss->pcm_indirect and the capture counters before sending
VIRTIO_SND_R_PCM_PREPARE leave the driver out of sync with ALSA's runtime
state?
When userspace invokes SNDRV_PCM_IOCTL_PREPARE on a capture substream in
SNDRV_PCM_STATE_XRUN or SNDRV_PCM_STATE_DRAINING, snd_pcm_do_prepare() calls
virtsnd_pcm_prepare_locked(). Before virtsnd_pcm_dev_prepare() is called,
this block clears vss->xfer_xrun = false (when !pause_restart), calls
virtsnd_pcm_msg_reset(vss), and zeroes vss->pcm_indirect.
If virtsnd_pcm_dev_prepare() then returns an error, snd_pcm_do_prepare()
aborts before snd_pcm_do_reset() and snd_pcm_post_prepare(), leaving
runtime->state and runtime->control->appl_ptr unchanged. Because only the
pause_restart branch restores vss->xfer_xrun = true and vss->stopped = true
on failure:
- If the stream was in SNDRV_PCM_STATE_XRUN, the latched vss->xfer_xrun state
is lost even though prepare failed.
- If the stream was in SNDRV_PCM_STATE_DRAINING, runtime->state remains
SNDRV_PCM_STATE_DRAINING with runtime->control->appl_ptr at its pre-prepare
value while vss->pcm_indirect is zeroed and vss->prepared, vss->stopped,
and vss->xfer_xrun are all false; a subsequent read or
SNDRV_PCM_IOCTL_SYNC_PTR in SNDRV_PCM_STATE_DRAINING bypasses the
!vss->prepared and vss->stopped checks in virtsnd_pcm_cp_ack_locked() and
subtracts appl_ptr from a zeroed vss->pcm_indirect.sw_ready.
[ ... ]
> +static int virtsnd_pcm_start_queue(struct virtio_pcm_substream *vss, u64 epoch)
> +{
> + struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
> + int rc = 0;
> +
> + guard(spinlock_irqsave)(&queue->lock);
> + guard(spinlock)(&vss->lock);
> + if (vss->resetting || vss->reset_epoch != epoch)
> + return -ECANCELED;
> + if (vss->stopped || !vss->prepared || vss->xfer_xrun)
> + return vss->xfer_xrun ? -EPIPE : -EBADFD;
> + if (vss->control_uncertain)
> + return -EIO;
> + if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) {
> + if (vss->capture_staging) {
> + rc = vss->runtime_active ? virtsnd_pcm_capture_send(vss) : -EBADFD;
> + } else if (!vss->capture_rx_seeded) {
> + rc = virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes);
> + if (!rc)
> + vss->capture_rx_seeded = true;
> + }
> + }
> if (!rc) {
> - guard(spinlock_irqsave)(&vss->lock);
> - vss->release_required = true;
> + if (vss->direction == SNDRV_PCM_STREAM_CAPTURE &&
> + !vss->capture_staging) {
> + size_t period = snd_pcm_lib_period_bytes(vss->substream);
> +
> + /* A synthetic resume notification is not a new period IRQ. */
> + vss->capture_notified_periods = period ?
> + div64_ul(vss->capture_copied_total, period) : 0;
> + }
> + vss->xfer_enabled = true;
> }
> return rc;
> }
[Severity: Medium]
Does overwriting vss->capture_notified_periods in virtsnd_pcm_start_queue()
drop period notifications for RX periods that completed while
vss->xfer_enabled was false?
During SNDRV_PCM_TRIGGER_PAUSE_PUSH, virtsnd_pcm_trigger() clears
vss->xfer_enabled = false before calling virtsnd_pcm_stop_device(). If one or
more RX periods complete in virtsnd_pcm_msg_complete() while
vss->xfer_enabled is false (either before STOP takes effect or when STOP is
rejected by the device), vss->capture_copied_total advances across a period
boundary without scheduling vss->elapsed_period.
When virtsnd_pcm_start_queue() subsequently runs on rejected PAUSE_PUSH
recovery or on SNDRV_PCM_TRIGGER_PAUSE_RELEASE, it overwrites
vss->capture_notified_periods with div64_ul(vss->capture_copied_total,
period) before virtsnd_pcm_period_elapsed() executes. When
virtsnd_pcm_period_elapsed() then runs:
sound/virtio/virtio_pcm.c:virtsnd_pcm_period_elapsed() {
...
partial = vss->xfer_xrun ||
period_floor <= vss->capture_notified_periods;
if (!partial)
vss->capture_notified_periods = period_floor;
}
period_floor <= vss->capture_notified_periods is forced to true (partial =
true), so it calls snd_pcm_progress_under_stream_lock() instead of
snd_pcm_period_elapsed_under_stream_lock(), losing the period notification,
runtime->hw_ptr_interrupt update, and PCM timer tick for the completed
periods.
> @@ -377,72 +1099,226 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
[ ... ]
> 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) {
> + if (virtsnd_pcm_start_queue(vss, epoch)) {
> + guard(spinlock_irqsave)(&vss->lock);
> + if (vss->reset_epoch == epoch && !vss->resetting) {
> + vss->xfer_xrun = true;
> + vss->stopped = true;
> + substream->runtime->stop_operating = true;
> + virtsnd_pcm_schedule_period_elapsed(vss);
> + }
> + } else if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) {
> + virtsnd_pcm_schedule_period_elapsed(vss);
> + }
> }
> + return rc;
[Severity: Medium]
If virtsnd_pcm_stop_device() fails during SNDRV_PCM_TRIGGER_STOP on a capture
stream, should vss->xfer_xrun also be latched here?
When userspace invokes SNDRV_PCM_IOCTL_DRAIN on a running capture stream with
unread frames, snd_pcm_do_drain_init() calls snd_pcm_do_stop(substream,
SNDRV_PCM_STATE_DRAINING), which ignores the return value of
substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP) and sets
runtime->state to SNDRV_PCM_STATE_DRAINING.
When virtsnd_pcm_stop_device() fails, vss->device_running remains true.
Unlike virtsnd_pcm_pause_restart_stop(), which calls
virtsnd_pcm_pause_restart_failed() to set vss->xfer_xrun = true, normal-mode
virtsnd_pcm_trigger() only handles rc != 0 for SNDRV_PCM_TRIGGER_PAUSE_PUSH
and leaves vss->xfer_xrun false. Because both virtsnd_pcm_capture_staged_ack()
and virtsnd_pcm_cp_ack_locked() allow ACK in SNDRV_PCM_STATE_DRAINING when
vss->stopped is true unless vss->xfer_xrun is set, draining capture reads and
ACKs can proceed while the device is still running and DMA-writing into the
capture buffer.
[ ... ]
> @@ -606,17 +1560,134 @@ static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream)
> return 0;
> }
>
> -static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
> +/* The PCM lock is held; exclude every member's buffer reconfiguration. */
> +static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream)
> +{
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + int rc;
> +
> + if (!mutex_trylock(&runtime->buffer_mutex))
> + return -EBADFD;
> + rc = virtsnd_pcm_pb_ack_locked(substream);
> + mutex_unlock(&runtime->buffer_mutex);
> + return rc;
> +}
[Severity: High]
Can failing mutex_trylock(&runtime->buffer_mutex) in virtsnd_pcm_pb_ack() and
virtsnd_pcm_cp_ack() corrupt vss->pcm_indirect when another thread transiently
holds runtime->buffer_mutex on a running stream?
In sound/core/pcm_native.c, snd_pcm_hw_refine_with_lock(),
snd_pcm_hw_params(), snd_pcm_hw_free(), and snd_pcm_action_nonatomic()
acquire runtime->buffer_mutex via snd_pcm_buffer_access_lock() (or
mutex_lock_nested() on linked group members in snd_pcm_action_group()) before
acquiring the PCM stream lock to check whether runtime->state is
SNDRV_PCM_STATE_RUNNING. Meanwhile, virtsnd_pcm_period_elapsed(),
snd_pcm_status64(), and snd_pcm_sync_ptr() hold the PCM stream lock without
incrementing runtime->buffer_accessing.
If virtsnd_pcm_period_elapsed() or snd_pcm_status64() runs while another
thread holds runtime->buffer_mutex waiting for the PCM stream lock,
virtsnd_pcm_pb_pointer() or virtsnd_pcm_cp_pointer() calls
snd_pcm_indirect_playback_pointer() or snd_pcm_indirect_capture_pointer(),
which first mutates vss->pcm_indirect in place (updating hw_io, hw_ready, and
sw_io) and then invokes substream->ops->ack(substream).
When mutex_trylock(&runtime->buffer_mutex) fails and returns -EBADFD,
snd_pcm_indirect_playback_pointer() and snd_pcm_indirect_capture_pointer()
ignore the error (only checking err == -EPIPE), leaving vss->pcm_indirect
half-updated without running snd_pcm_indirect_playback_transfer() or
snd_pcm_indirect_capture_transfer(). Concurrent SNDRV_PCM_IOCTL_SYNC_PTR
calls without HWSYNC will also fail with -EBADFD whenever another thread is
in the middle of an ioctl that locks buffer_mutex before checking stream
state.
> +
> +static int virtsnd_pcm_capture_staged_ack(struct virtio_pcm_substream *vss,
> + struct snd_pcm_runtime *runtime)
> +{
> + struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
> + int rc = 0;
> +
> + /*
> + * The RX callback holds the queue lock while draining completions.
> + * Account ALSA's read under the substream lock alone so completions later
> + * in that drain observe the newly released ring space.
> + */
> + scoped_guard(spinlock_irqsave, &vss->lock) {
> + snd_pcm_uframes_t appl_ptr;
> + snd_pcm_sframes_t diff;
> + size_t bytes;
> + u64 outstanding;
> + u64 next_acked_total;
> + bool free_running;
> +
> + if (!vss->runtime_active || !runtime || !runtime->control ||
> + !runtime->dma_area || vss->suspended)
> + return -EBADFD;
> + 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;
> + if (vss->xfer_xrun)
> + return -EPIPE;
[Severity: Medium]
Should virtsnd_pcm_capture_staged_ack() also check vss->stopped (or verify
that runtime->state permits acknowledgments), similar to
virtsnd_pcm_cp_ack_locked()?
When ALSA transitions a running staged capture substream to
SNDRV_PCM_STATE_XRUN without the virtio driver setting vss->xfer_xrun (for
example, when snd_pcm_update_state() detects avail >= runtime->stop_threshold,
or when userspace issues SNDRV_PCM_IOCTL_XRUN via snd_pcm_xrun()),
__snd_pcm_xrun() calls snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN), which
invokes virtsnd_pcm_trigger(substream, SNDRV_PCM_TRIGGER_STOP) without
calling snd_pcm_sync_stop(). That sets vss->stopped = true and
vss->xfer_enabled = false while leaving vss->prepared = true and
vss->xfer_xrun = false. Similarly, if SNDRV_PCM_IOCTL_DROP is called while in
SNDRV_PCM_STATE_PREPARED, snd_pcm_do_stop() transitions runtime->state to
SNDRV_PCM_STATE_SETUP without setting stop_operating, leaving vss->prepared =
true.
If userspace then invokes SNDRV_PCM_IOCTL_SYNC_PTR without
SNDRV_PCM_SYNC_PTR_HWSYNC or SNDRV_PCM_SYNC_PTR_APPL, snd_pcm_sync_ptr()
calls pcm_lib_apply_appl_ptr() -> virtsnd_pcm_cp_ack() ->
virtsnd_pcm_capture_staged_ack(). Because virtsnd_pcm_capture_staged_ack()
does not check (vss->stopped && runtime->state != SNDRV_PCM_STATE_DRAINING),
it accepts the ACK and advances vss->capture_acked_total, vss->capture_ready,
and vss->pcm_indirect.appl_ptr while the stream is stopped in
SNDRV_PCM_STATE_XRUN or SNDRV_PCM_STATE_SETUP.
[ ... ]
> @@ -641,6 +1712,50 @@ static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
> return 0;
> }
>
> +/*
> + * Linked prepare locks each member's buffer_mutex, while buffer_accessing is
> + * negative only on its initiating member. ACK must exclude every prepare.
> + * The PCM lock is already held, so a failed trylock must return immediately.
> + */
> +static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
> +{
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + int rc;
> +
> + if (!mutex_trylock(&runtime->buffer_mutex))
> + return -EBADFD;
> + rc = virtsnd_pcm_cp_ack_locked(substream);
> + mutex_unlock(&runtime->buffer_mutex);
> + return rc;
> +}
[Severity: High]
Won't mutex_trylock(&runtime->buffer_mutex) in virtsnd_pcm_pb_ack() and
virtsnd_pcm_cp_ack() always fail during SNDRV_PCM_IOCTL_RESET on a running
stream?
When userspace invokes SNDRV_PCM_IOCTL_RESET, snd_pcm_reset() calls
snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream,
ACTION_ARG_IGNORE), which acquires substream->runtime->buffer_mutex via
snd_pcm_buffer_access_lock() (and locks runtime->buffer_mutex on all linked
group members in snd_pcm_action_group()).
While holding runtime->buffer_mutex on the same thread, snd_pcm_do_reset()
calls snd_pcm_lib_ioctl_reset() (or snd_pcm_lib_reset()). When the stream is
running, snd_pcm_lib_ioctl_reset() acquires the PCM stream lock and calls
snd_pcm_update_hw_ptr(substream) -> substream->ops->pointer(substream).
For playback (virtsnd_pcm_pb_pointer()) and ordinary capture
(virtsnd_pcm_cp_pointer()), snd_pcm_indirect_playback_pointer() and
snd_pcm_indirect_capture_pointer() first mutate vss->pcm_indirect in place
(updating hw_io, hw_ready, and sw_io) and then invoke
substream->ops->ack(substream). Because the calling thread already holds
runtime->buffer_mutex, mutex_trylock(&runtime->buffer_mutex) in
virtsnd_pcm_pb_ack() and virtsnd_pcm_cp_ack() is guaranteed to fail and
return -EBADFD.
snd_pcm_indirect_playback_pointer() and snd_pcm_indirect_capture_pointer()
ignore -EBADFD (only checking err == -EPIPE) and skip
snd_pcm_indirect_playback_transfer() / snd_pcm_indirect_capture_transfer()
after vss->pcm_indirect has already been modified. Immediately afterwards,
snd_pcm_post_reset() sets runtime->control->appl_ptr =
runtime->status->hw_ptr without updating vss->pcm_indirect.appl_ptr, causing
the next playback ACK or period completion to compute a negative diff =
runtime->control->appl_ptr - rec->appl_ptr and abort the stream with -EPIPE.
Later commits in this series ("ALSA: pcm: add an optional reset operation
under the stream lock", "ALSA: virtio: synchronize staged capture accounting
during RESET", and "ALSA: virtio: rebase ordinary capture accounting on
RESET") add .reset callbacks for capture streams, but playback streams in
virtsnd_pcm_ops[0] still have no .reset callback or bypass in
virtsnd_pcm_pb_ack(), leaving SNDRV_PCM_IOCTL_RESET broken on running
playback streams.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261031020855.2402-1-mynameisjune111@gmail.com?part=9
next prev parent reply other threads:[~2026-10-31 2:51 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 2:08 [PATCH v11 00/14] ALSA: virtio: staged capture recovery and PCM RESET synchronization Joonho Lee
2026-10-31 2:08 ` [PATCH v11 01/14] ALSA: virtio: Propagate PCM send failures through ACK Joonho Lee
2026-10-31 2:29 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 02/14] ALSA: virtio: Recover PCM state after a failed start Joonho Lee
2026-10-31 2:31 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 03/14] ALSA: virtio: Retain queued messages through device reset Joonho Lee
2026-10-31 2:40 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 04/14] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Joonho Lee
2026-10-31 2:33 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 05/14] ALSA: virtio: Keep control payloads alive through DMA completion Joonho Lee
2026-10-31 2:20 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 06/14] ALSA: virtio: wait through signals for PCM teardown Joonho Lee
2026-10-31 2:27 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 07/14] ALSA: pcm: mark stop-threshold accesses for capture policy sampling Joonho Lee
2026-10-31 2:14 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 08/14] ALSA: pcm: report partial progress under the stream lock Joonho Lee
2026-10-31 2:13 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 09/14] ALSA: virtio: add staged capture accounting and lifecycle recovery Joonho Lee
2026-10-31 2:51 ` sashiko-bot [this message]
2026-10-31 2:08 ` [PATCH v11 10/14] ALSA: virtio: prioritize staged capture period work Joonho Lee
2026-10-31 2:15 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 11/14] docs: describe virtio-sound capture options Joonho Lee
2026-10-31 2:16 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 12/14] ALSA: pcm: add an optional reset operation under the stream lock Joonho Lee
2026-10-31 2:20 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 13/14] ALSA: virtio: synchronize staged capture accounting during RESET Joonho Lee
2026-10-31 2:42 ` sashiko-bot
2026-10-31 2:08 ` [PATCH v11 14/14] ALSA: virtio: rebase ordinary capture accounting on RESET Joonho Lee
2026-10-31 2:40 ` 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-191718@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.