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 v13 10/12] ALSA: virtio: add staged capture and serialize PCM lifecycle accounting
Date: Tue, 03 Nov 2026 11:05:29 +0000	[thread overview]
Message-ID: <sashiko-outbox-195105@kernel.org> (raw)
In-Reply-To: <20261103080821.16983-11-mynameisjune111@gmail.com>

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [Medium] ALSA: virtio: missing stop_operating arming on -EPIPE in ACK and start paths
- [Medium] ALSA: virtio: skipped NOMMU post-STOP copy in virtsnd_pcm_msg_complete()
- [Medium] ALSA: virtio: cancel_work_sync() in virtsnd_reset_vqs() stalls ctl cleanup
- [Medium] ALSA: virtio: missing stop_operating on XRUN in pause_restart trigger
- [Medium] ALSA: virtio: delayed preparing gate and stale release_required in prepare
- [Medium] ALSA: virtio: virtsnd_pcm_pause_restart_stop() skips setting suspended
- [Medium] ALSA: virtio: virtsnd_pcm_indirect_reset() skips cursor rebase on error
- [Medium] ALSA: virtio: virtsnd_pcm_capture_staged_ack() rejects sub-byte carryover
- [Medium] ALSA: virtio: unaligned byte ACK in virtsnd_pcm_capture_staged_ack()
--

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

ALSA: virtio: add staged capture and serialize PCM lifecycle accounting

This commit introduces request-owned staged capture buffers with separate
produced, observed, and consumed position tracking, and serializes PCM
lifecycle transitions and ACK accounting using lifecycle_lock and the PCM
stream lock. It also adds reset callbacks for playback and capture streams
and refines XRUN and teardown handling across device resets and pause
transitions.

> diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c
> index aec4959c973ca..f32e9fb9e8b1d 100644
> --- a/sound/virtio/virtio_card.c
> +++ b/sound/virtio/virtio_card.c
> @@ -382,8 +399,16 @@ static void virtsnd_reset_vqs(struct virtio_snd *snd)
>  		queue->vqueue = NULL;
>  		spin_unlock_irqrestore(&queue->lock, flags);
>  	}
> -
>  	vdev->config->del_vqs(vdev);
> +	for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) {
> +		struct virtio_pcm_substream *vss = &snd->substreams[i];
> +
> +		cancel_work_sync(&vss->elapsed_period);

[Severity: Medium]
Can calling cancel_work_sync(&vss->elapsed_period) inside virtsnd_reset_vqs()
stall freeze or remove for the full virtsnd_msg_timeout_ms duration?

In virtsnd_freeze() and virtsnd_remove():

  virtsnd_ctl_msg_cancel_all(snd);
  virtsnd_reset_vqs(snd);
  virtsnd_ctl_msg_cleanup(snd);

virtsnd_ctl_msg_cancel_all() marks pending control messages with
msg->cancelled = true, but does not complete msg->notify; waking the waiters
only happens later in virtsnd_ctl_msg_cleanup() after virtsnd_reset_vqs()
returns.

Because vpcm->pcm->nonatomic is true, virtsnd_pcm_period_elapsed() acquires
the sleeping PCM stream lock via guard(pcm_stream_lock_irqsave)(substream).
If another thread holds that PCM stream lock while waiting in
virtsnd_ctl_msg_send() on an already-submitted control message (such as in
virtsnd_pcm_trigger()), or if virtsnd_pcm_period_elapsed() itself entered
snd_pcm_stop() -> virtsnd_pcm_trigger(SNDRV_PCM_TRIGGER_STOP) ->
virtsnd_ctl_msg_send_sync() just before virtsnd_reset_vqs() set
vss->resetting = true, the device has already been reset and will never
complete that control message.

Waiting in cancel_work_sync(&vss->elapsed_period) before
virtsnd_ctl_msg_cleanup(snd) prevents msg->notify from being completed until
wait_for_completion_timeout(&msg->notify, js) expires. Should control message
waiters be woken before synchronously canceling elapsed_period work?

[ ... ]

> diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c
> index b94ab24f5ffef..d6510d813f3ca 100644
> --- a/sound/virtio/virtio_pcm_msg.c
> +++ b/sound/virtio/virtio_pcm_msg.c
> @@ -540,19 +880,52 @@ static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
>  				     size_t written_bytes)
>  {
>  	struct virtio_pcm_substream *vss = msg->substream;
> +	size_t completed_bytes;
> +	u32 latency_bytes;
>  
> +	if (vss->direction == SNDRV_PCM_STREAM_CAPTURE &&
> +	    vss->capture_staging) {
> +		virtsnd_pcm_capture_complete(msg, written_bytes);
> +		return;
> +	}
> +
> +	/* Only completed TX requests and valid RX audio advance the pointer. */
>  	guard(spinlock)(&vss->lock);
> +	/* Empty STOP returns retire ownership without inventing captured data. */
> +	if (vss->direction == SNDRV_PCM_STREAM_CAPTURE &&
> +	    (!vss->prepared ||
> +	     (vss->stopped && written_bytes <= sizeof(*msg->status)))) {
> +		msg->length = 0;
> +		vss->msg_count--;
> +		if (!vss->msg_count)
> +			wake_up_all(&vss->msg_empty);
> +		return;
> +	}
> +	completed_bytes = msg->length;
> +	latency_bytes = le32_to_cpu(msg->status->latency_bytes);
> +	if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) {
> +		struct snd_pcm_runtime *runtime = vss->substream->runtime;
> +		size_t frame_bytes = runtime->frame_bits / gcd(runtime->frame_bits, 8);
> +
> +		if (virtsnd_pcm_capture_status(msg, written_bytes, frame_bytes,
> +					       &completed_bytes, &latency_bytes) ||
> +		    !completed_bytes) {
> +			vss->xfer_xrun = true;
> +			msg->length = 0;
> +			vss->msg_count--;
> +			if (!vss->msg_count)
> +				wake_up_all(&vss->msg_empty);
> +			virtsnd_pcm_schedule_period_elapsed(vss);
> +			return;
> +		}
> +	}
>  #ifndef CONFIG_MMU
>  	if (vss->direction == SNDRV_PCM_STREAM_CAPTURE &&
> -	    vss->xfer_enabled && written_bytes > sizeof(msg->status)) {
> +	    vss->prepared && !vss->stopped &&
> +	    written_bytes > sizeof(*msg->status)) {
>  		struct snd_pcm_runtime *runtime =
>  			vss->substream ? vss->substream->runtime : NULL;
> -		size_t reported_bytes = written_bytes - sizeof(msg->status);
> +		size_t reported_bytes = written_bytes - sizeof(*msg->status);
>  		size_t data_bytes = min_t(size_t, reported_bytes, msg->length);
>  
>  		if (!runtime || !runtime->dma_area || !vss->owned_dma_area ||
> @@ -568,26 +941,16 @@ static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
>  			       vss->owned_dma_area + msg->data_offset, data_bytes);
>  	}
>  #endif
> +	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) ||

[Severity: Medium]
On !CONFIG_MMU systems, does checking !vss->stopped before copying from
vss->owned_dma_area leave stale data in runtime->dma_area on post-STOP
capture completions?

At the top of virtsnd_pcm_msg_complete(), post-STOP capture completions with
written_bytes > sizeof(*msg->status) are intentionally kept so that in-flight
payloads (such as during capture SNDRV_PCM_STATE_DRAINING after
virtsnd_pcm_trigger(SNDRV_PCM_TRIGGER_STOP) sets vss->stopped = true) advance
vss->hw_ptr and vss->capture_copied_total by completed_bytes.

However, the #ifndef CONFIG_MMU bounce-buffer copy block is gated on
vss->prepared && !vss->stopped. When vss->stopped is true, memcpy() into
runtime->dma_area is skipped even though vss->hw_ptr and
vss->capture_copied_total are advanced, exposing stale or zeroed buffer
contents to userspace. Should the !vss->stopped check be removed from the
!CONFIG_MMU copy condition (and data_bytes use completed_bytes)?

[ ... ]

> diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
> index f4b45bddfb09c..05b38d8a67d7a 100644
> --- a/sound/virtio/virtio_pcm_ops.c
> +++ b/sound/virtio/virtio_pcm_ops.c
> @@ -288,28 +622,95 @@ static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,
[ ... ]
>  static int virtsnd_pcm_prepare_locked(struct snd_pcm_substream *substream)
>  {
>  	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
>  	struct virtio_device *vdev = vss->snd->vdev;
> -	struct virtio_snd_msg *msg;
> -	bool stopped;
> +	bool pause_restart = virtsnd_pcm_pause_restart_enabled(vss);
> +	bool free_running;
>  	int rc;
> +	bool stopped;
> +	bool suspended;
> +	u64 epoch;
> +
> +	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		if (vss->resetting)
> +			return -ECANCELED;
> +		epoch = vss->reset_epoch;
> +		stopped = vss->stopped || vss->release_required || vss->msg_count;
> +	}
> +	if (stopped) {
> +		rc = virtsnd_pcm_sync_stop_locked(substream);
> +		if (rc)
> +			return rc;
> +	}
> +	scoped_guard(pcm_stream_lock_irqsave, substream) {
> +		guard(spinlock_irqsave)(&vss->lock);
> +		if (vss->resetting || vss->reset_epoch != epoch)
> +			return -ECANCELED;
> +		vss->prepared = false;
> +		vss->preparing = true;
> +		suspended = vss->suspended;
> +	}

[Severity: Medium]
When re-preparing a substream that is already in SNDRV_PCM_STATE_PREPARED,
can a concurrent SNDRV_PCM_IOCTL_SYNC_PTR ioctl race with
virtsnd_pcm_sync_stop_locked() before vss->preparing is set?

When snd_pcm_prepare() is called on a prepared substream (where
vss->release_required is true), ALSA core holds runtime->buffer_mutex but not
the PCM stream lock, and runtime->state remains SNDRV_PCM_STATE_PREPARED
until snd_pcm_post_prepare().

In virtsnd_pcm_prepare_locked(), virtsnd_pcm_sync_stop_locked() runs before
the PCM stream lock is taken to set vss->preparing = true and
vss->prepared = false. Inside virtsnd_pcm_sync_stop_locked() (and
virtsnd_pcm_pause_restart_sync_stop()), vss->stopped is set to true while
vss->prepared remains true across the sleeping VIRTIO_SND_R_PCM_RELEASE
request:

  virtsnd_pcm_prepare_locked()
    virtsnd_pcm_sync_stop_locked()
      vss->stopped = true;
      /* vss->prepared is still true, vss->preparing is still false */
      virtsnd_pcm_send_sync(vss, msg); /* sleeps */

If another thread calls snd_pcm_sync_ptr() -> pcm_lib_apply_appl_ptr() during
this window, it acquires the PCM stream lock, passes virtsnd_pcm_ack_state(),
sees vss->preparing == false, vss->prepared == true, and vss->stopped == true
in virtsnd_pcm_pb_ack_locked() or virtsnd_pcm_cp_ack_locked(), and returns
-EPIPE, causing pcm_lib_apply_appl_ptr() to call __snd_pcm_xrun(substream)
mid-prepare.

Additionally, virtsnd_pcm_prepare_locked() sets vss->release_required = true
even when pause_restart is enabled, but neither virtsnd_pcm_release_wait()
nor virtsnd_pcm_pause_restart_sync_stop() clears vss->release_required,
causing stopped to be true on every subsequent prepare. Should vss->preparing
be armed under the PCM stream lock before calling
virtsnd_pcm_sync_stop_locked(), and should vss->release_required be cleared
when pause_restart releases the stream?

[ ... ]

> +static int virtsnd_pcm_pause_restart_start(struct virtio_pcm_substream *vss,
> +					   bool resume)
> +{
> +	struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
>  	struct virtio_snd_msg *msg;
> +	enum virtio_pcm_pause_restart_state state;
> +	bool reset_required;
> +	bool invalid_state;
> +	bool xrun;
>  	int rc = 0;
> +	u64 epoch;
>  
> +	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		if (vss->resetting)
> +			return -ECANCELED;
> +		epoch = vss->reset_epoch;
> +		state = vss->pause_restart_state;
> +		xrun = vss->xfer_xrun;
[ ... ]
> +	}
> +	if (reset_required) {
> +		virtsnd_pcm_pause_restart_fail(vss, true, epoch);
> +		return -EIO;
> +	}
> +	if (xrun)
> +		return -EPIPE;
[ ... ]
> +static int virtsnd_pcm_pause_restart_pause(struct virtio_pcm_substream *vss)
> +{
> +	struct virtio_snd_msg *msg;
> +	enum virtio_pcm_pause_restart_state state;
> +	int rc;
> +	u64 epoch;
> +
> +	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		if (vss->resetting)
> +			return -ECANCELED;
> +		epoch = vss->reset_epoch;
> +		state = vss->pause_restart_state;
> +		if (state == VIRTSND_PCM_PAUSE_RESTART_RELEASED) {
> +			if (vss->xfer_xrun)
> +				virtsnd_pcm_schedule_period_elapsed(vss);
> +			return vss->xfer_xrun ? -EPIPE : 0;
> +		}

[Severity: Medium]
In virtsnd_pcm_pause_restart_start() and virtsnd_pcm_pause_restart_pause(),
does returning -EPIPE on a latched XRUN without arming vss->stopped and
runtime->stop_operating prevent subsequent DROP or prepare teardown from
running sync_stop?

In virtsnd_pcm_pause_restart_start(), if xrun is true on entry, it returns
-EPIPE directly without calling virtsnd_pcm_pause_restart_failed(vss, epoch)
(unlike the later vss->xfer_xrun check when queueing capture buffers, and
unlike the native trigger path which calls virtsnd_pcm_start_failed()). When
SNDRV_PCM_TRIGGER_START fails with -EPIPE on a linked substream,
snd_pcm_do_start() sets runtime->state to SNDRV_PCM_STATE_XRUN and
snd_pcm_action_group() only runs undo_action on group members preceding the
failed stream, leaving vss->stopped and runtime->stop_operating false on the
failed stream so SNDRV_PCM_IOCTL_DROP will not release the prepared stream on
the device.

Similarly, if a paused stream has vss->xfer_xrun latched and snd_pcm_drop()
or snd_pcm_prepare() calls snd_pcm_pause(substream, false) under the PCM
stream lock, virtsnd_pcm_pause_restart_start() returns -EPIPE and
snd_pcm_undo_pause() -> virtsnd_pcm_pause_restart_pause() also returns -EPIPE
in the VIRTSND_PCM_PAUSE_RESTART_RELEASED branch without setting
runtime->stop_operating = true (unlike the
VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT branch just below it). Because
runtime->state remains SNDRV_PCM_STATE_PAUSED and runtime->stop_operating is
false, both snd_pcm_do_stop() and snd_pcm_sync_stop() skip teardown.

Should both of these -EPIPE paths call virtsnd_pcm_pause_restart_failed(vss,
epoch) (or arm vss->stopped and runtime->stop_operating)?

[ ... ]

> +static int virtsnd_pcm_pause_restart_stop(struct virtio_pcm_substream *vss,
> +					  bool suspend)
> +{
> +	struct virtio_snd_msg *msg;
> +	enum virtio_pcm_pause_restart_state state;
> +	int rc;
> +	u64 epoch;
> +
> +	scoped_guard(spinlock_irqsave, &vss->lock) {
> +		if (vss->resetting)
> +			return -ECANCELED;
> +		epoch = vss->reset_epoch;
> +		if (!vss->prepared && !vss->msg_count)
> +			return 0;
> +		if (suspend)
> +			vss->suspended = true;

[Severity: Medium]
In virtsnd_pcm_pause_restart_stop(), if suspend is true while !vss->prepared
&& !vss->msg_count, won't returning early skip setting vss->suspended = true?

In the native trigger path in virtsnd_pcm_trigger(), SNDRV_PCM_TRIGGER_SUSPEND
sets vss->suspended = true before checking if (!vss->prepared) return 0. Here
in virtsnd_pcm_pause_restart_stop(), if (!vss->prepared && !vss->msg_count)
returns 0 before setting vss->suspended = true. If a stream in pause_restart
mode has vss->prepared cleared and messages drained (for example, while paused
in VIRTSND_PCM_PAUSE_RESTART_RELEASED or after a failed pause transition) and
then receives SNDRV_PCM_TRIGGER_SUSPEND, vss->suspended remains false, so
staged pointer/ACK callbacks will not observe the suspended state and a
subsequent prepare will skip re-sending VIRTIO_SND_R_PCM_SET_PARAMS. Should
if (suspend) vss->suspended = true precede the early return check?

[ ... ]

> -static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream)
> +static int virtsnd_pcm_pb_ack_locked(struct snd_pcm_substream *substream)
>  {
>  	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
>  	struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
>  	struct virtsnd_pcm_transfer transfer = {};
>  	int rc;
>  
> -	rc = virtsnd_pcm_ack_state(substream);
> -	if (rc)
> -		return rc;
> -
>  	scoped_guard(spinlock_irqsave, &queue->lock) {
>  		guard(spinlock)(&vss->lock);
> +		if (vss->preparing)
> +			return -EBADFD;
> +		if (vss->xfer_xrun)
> +			return -EPIPE;
> +		if (!vss->prepared)
> +			return -EBADFD;
>  		transfer.indirect = vss->pcm_indirect;
>  		if (vss->stopped) {
>  			rc = -EPIPE;
> @@ -626,21 +1609,163 @@ static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream)
>  	return 0;
>  }
[ ... ]
> +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;
[ ... ]
> +		appl_ptr = runtime->control->appl_ptr;
> +		diff = appl_ptr - vss->pcm_indirect.appl_ptr;
> +		if (diff < -(snd_pcm_sframes_t)(runtime->boundary / 2))
> +			diff += runtime->boundary;
> +		if (diff < 0 ||
> +		    (snd_pcm_uframes_t)diff > runtime->buffer_size) {
> +			vss->xfer_xrun = true;
> +			virtsnd_pcm_schedule_period_elapsed(vss);
> +			return -EPIPE;
> +		}

[Severity: Medium]
Can carrying over an unconsumed sub-byte frame in vss->pcm_indirect.appl_ptr
cause a subsequent full-buffer ACK to fail this diff > runtime->buffer_size
check with a false -EPIPE?

For a sub-byte frame format such as 1-channel SNDRV_PCM_FORMAT_IMA_ADPCM
(where frame_bits == 4), acknowledging 1 frame yields
bytes = frames_to_bytes(runtime, 1) = 0, leaving vss->pcm_indirect.appl_ptr
lagging runtime->control->appl_ptr (old_appl_ptr) by 1 frame.

If the application then advances appl_ptr by runtime->buffer_size frames in
free-running mode (which pcm_lib_apply_appl_ptr() allows because
appl_ptr - old_appl_ptr == runtime->buffer_size, and which equals
vss->buffer_bytes in bytes), diff = appl_ptr - vss->pcm_indirect.appl_ptr
becomes runtime->buffer_size + 1. Checking
(snd_pcm_uframes_t)diff > runtime->buffer_size then rejects the ACK with
-EPIPE and latches vss->xfer_xrun before frames_to_bytes(runtime, diff) is
computed.

> +
> +		/* ACK includes any subperiod tail already copied to the ring. */
> +		bytes = frames_to_bytes(runtime, diff);
> +		outstanding = vss->capture_copied_total -
> +			      vss->capture_acked_total;
> +		if ((!free_running && bytes > vss->capture_ready) ||
> +		    (free_running &&
> +		     (bytes > vss->buffer_bytes || bytes > outstanding)) ||
> +		    check_add_overflow(vss->capture_acked_total, (u64)bytes,
> +				       &next_acked_total) ||
> +		    next_acked_total > vss->capture_pointer_seen_total) {
> +			vss->xfer_xrun = true;
> +			virtsnd_pcm_schedule_period_elapsed(vss);
> +			return -EPIPE;
> +		}
> +
> +		vss->capture_acked_total = next_acked_total;
> +		if (!free_running)
> +			vss->capture_ready -= bytes;
> +		/* Keep an unconsumed sub-byte frame for the next acknowledgment. */
> +		vss->pcm_indirect.appl_ptr += bytes_to_frames(runtime, bytes);

[Severity: Medium]
What happens here when an odd number of frames is acknowledged on a
multi-channel fractional-byte format (such as 3-, 5-, or 7-channel
SNDRV_PCM_FORMAT_IMA_ADPCM where frame_bits > 8 and frame_bits % 8 != 0)?

For example, with 3-channel IMA_ADPCM (frame_bits == 12, 2 frames == 3 bytes),
if diff == 1 frame, frames_to_bytes(runtime, 1) is 1 byte (or if diff == 3
frames, bytes is 4 bytes). That unaligned byte count is committed to
vss->capture_acked_total and subtracted from vss->capture_ready, but
bytes_to_frames(runtime, bytes) then truncates 1 byte to 0 frames (or 4 bytes
to 2 frames == 3 bytes) when advancing vss->pcm_indirect.appl_ptr.

Because vss->pcm_indirect.appl_ptr does not advance for that partial frame's
byte, the next ACK will include that frame again in diff and double-count the
byte in vss->capture_acked_total, desynchronizing vss->capture_acked_total
from vss->pcm_indirect.appl_ptr and triggering a spurious -EPIPE XRUN when
next_acked_total exceeds vss->capture_pointer_seen_total or bytes exceeds
vss->capture_ready. Should bytes be rounded down to a whole number of frames
(for instance, frames_to_bytes(runtime, bytes_to_frames(runtime, bytes)))
before updating vss->capture_acked_total and vss->capture_ready?

[ ... ]

> +static int virtsnd_pcm_cp_ack_locked(struct snd_pcm_substream *substream)
> +{
> +	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
> +	struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +	struct virtsnd_pcm_transfer transfer = {};
> +	int rc;
> +
> +	if (vss->capture_staging) {
> +		rc = virtsnd_pcm_capture_staged_ack(vss, runtime);
> +		if (rc == -EPIPE) {
> +			guard(spinlock_irqsave)(&vss->lock);
> +			virtsnd_pcm_capture_arm_paused_stop(vss);
> +		}
> +		return rc;
> +	}
> +
>  	scoped_guard(spinlock_irqsave, &queue->lock) {
>  		guard(spinlock)(&vss->lock);
> +		if (vss->preparing)
> +			return -EBADFD;
> +		if (vss->xfer_xrun)
> +			return -EPIPE;
> +		if (!vss->prepared)
> +			return -EBADFD;
>  		transfer.indirect = vss->pcm_indirect;
> -		if (vss->stopped) {
> +		if (vss->xfer_xrun ||
> +		    (vss->stopped && runtime->state != SNDRV_PCM_STATE_DRAINING)) {
>  			rc = -EPIPE;
>  		} else {
>  			rc = snd_pcm_indirect_capture_transfer(substream,

[Severity: Medium]
When virtsnd_pcm_pb_ack_locked() or virtsnd_pcm_cp_ack_locked() returns
-EPIPE on a non-running substream (such as in SNDRV_PCM_STATE_PAUSED or
SNDRV_PCM_STATE_PREPARED), can leaving vss->stopped and
runtime->stop_operating unset cause a subsequent SNDRV_PCM_IOCTL_DROP to skip
virtsnd_pcm_sync_stop()?

In ALSA core, when pcm_lib_apply_appl_ptr() receives -EPIPE from ops->ack(),
it calls __snd_pcm_xrun(substream) -> snd_pcm_stop(substream,
SNDRV_PCM_STATE_XRUN). In snd_pcm_do_stop(), ALSA only invokes
ops->trigger(SNDRV_PCM_TRIGGER_STOP) and sets runtime->stop_operating = true
when snd_pcm_running(substream) is true. Once runtime->state becomes
SNDRV_PCM_STATE_XRUN, virtsnd_pcm_period_elapsed() also sees
runtime->state != SNDRV_PCM_STATE_PAUSED and does not call
virtsnd_pcm_capture_arm_paused_stop().

There are two places where -EPIPE is returned from ACK without arming
vss->stopped and runtime->stop_operating:

1. In both virtsnd_pcm_pb_ack_locked() and virtsnd_pcm_cp_ack_locked(), the
   early check:

     if (vss->xfer_xrun)
             return -EPIPE;

   returns before reaching virtsnd_pcm_transfer_failed(vss) (which also makes
   the second vss->xfer_xrun check in virtsnd_pcm_cp_ack_locked() unreachable).

2. In virtsnd_pcm_cp_ack_locked() for staged capture, when
   virtsnd_pcm_capture_staged_ack() returns -EPIPE,
   virtsnd_pcm_capture_arm_paused_stop() only sets vss->stopped = true and
   runtime->stop_operating = true when runtime->state == SNDRV_PCM_STATE_PAUSED,
   leaving SNDRV_PCM_STATE_PREPARED (and capture SNDRV_PCM_STATE_DRAINING)
   unarmed.

If the application then calls SNDRV_PCM_IOCTL_DROP, snd_pcm_drop() sees
runtime->state == SNDRV_PCM_STATE_XRUN with runtime->stop_operating == false
and transitions to SNDRV_PCM_STATE_SETUP without calling snd_pcm_sync_stop(),
leaving the device stream prepared/active and any queued descriptors
unreleased until prepare or hw_free. Should these -EPIPE paths always arm
vss->stopped and runtime->stop_operating via virtsnd_pcm_transfer_failed()?

[ ... ]

> +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 = {};
> +	snd_pcm_uframes_t paused_ptr = 0;
> +	bool paused = runtime->state == SNDRV_PCM_STATE_PAUSED;
> +	int rc;
> +
> +	/* Only PCM-locked callbacks access this observation flag. */
> +	vss->indirect_reset_observing = true;
> +	rc = snd_pcm_lib_reset(substream);
> +	vss->indirect_reset_observing = false;
> +	if (rc)
> +		return rc;
> +	guard(spinlock_irqsave)(&queue->lock);
> +	guard(spinlock)(&vss->lock);
> +	vss->preparing = false;
> +	if (vss->resetting || vss->xfer_xrun) {
> +		virtsnd_pcm_capture_reset_error(vss, runtime);
> +		return 0;
> +	}
> +	if (!snd_pcm_running(substream) && !paused) {
> +		vss->pcm_indirect.appl_ptr = runtime->status->hw_ptr;
> +		vss->pcm_indirect.sw_ready = 0;
> +		return 0;
> +	}
> +	if (!vss->prepared || vss->stopped) {
> +		virtsnd_pcm_transfer_failed(vss);
> +		vss->xfer_xrun = true;
> +		virtsnd_pcm_schedule_period_elapsed(vss);
> +		return 0;
> +	}
> +
> +	transfer.indirect = vss->pcm_indirect;
> +	if (paused) {
> +		/* Observe and reclaim paused completions in one queue transaction. */
> +		vss->capture_pointer_seen_total = vss->capture_copied_total;
> +		vss->indirect_reset_observing = true;
> +		paused_ptr = snd_pcm_indirect_capture_pointer(substream,
> +							      &transfer.indirect, vss->hw_ptr);
> +		vss->indirect_reset_observing = false;
> +		if (paused_ptr == SNDRV_PCM_POS_XRUN) {
> +			virtsnd_pcm_transfer_failed(vss);
> +			vss->xfer_xrun = true;
> +			virtsnd_pcm_schedule_period_elapsed(vss);
> +			return 0;
> +		}
> +		runtime->status->hw_ptr = paused_ptr;
> +	}
> +	transfer.indirect.sw_ready = 0;
> +	/* 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: Medium]
In virtsnd_pcm_indirect_reset(), shouldn't the three error paths after
snd_pcm_lib_reset() also rebase vss->pcm_indirect.appl_ptr and clear
vss->pcm_indirect.sw_ready?

The commit message notes:

  Complete capture's private cursor rebase on RESET error paths while
  retaining the XRUN latch and notification. This permits the core's
  matching appl_ptr update without claiming stream recovery.

While the first error branch (vss->resetting || vss->xfer_xrun) calls
virtsnd_pcm_capture_reset_error(vss, runtime) to set
vss->pcm_indirect.appl_ptr = runtime->status->hw_ptr and
vss->pcm_indirect.sw_ready = 0, the other three error branches
(!vss->prepared || vss->stopped, paused_ptr == SNDRV_PCM_POS_XRUN, and rc != 0
from snd_pcm_indirect_capture_transfer()) return 0 to snd_pcm_reset_substream()
without updating vss->pcm_indirect.appl_ptr or vss->pcm_indirect.sw_ready,
leaving vss->pcm_indirect desynchronized from runtime->control->appl_ptr.
Should those three branches also call virtsnd_pcm_capture_reset_error(vss,
runtime) after setting vss->xfer_xrun = true?

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

  reply	other threads:[~2026-11-03 11:05 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03  8:08 [PATCH v13 00/12] ALSA: virtio: staged capture recovery and PCM RESET synchronization Joonho Lee
2026-11-03  8:08 ` [PATCH v13 01/12] ALSA: virtio: Propagate PCM send failures through ACK Joonho Lee
2026-11-03  8:33   ` sashiko-bot
2026-11-03  8:08 ` [PATCH v13 02/12] ALSA: virtio: Recover PCM state after a failed start Joonho Lee
2026-11-03  8:08 ` [PATCH v13 03/12] ALSA: virtio: Retain queued messages through device reset Joonho Lee
2026-11-03  9:25   ` sashiko-bot
2026-11-03  8:08 ` [PATCH v13 04/12] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Joonho Lee
2026-11-03  8:08 ` [PATCH v13 05/12] ALSA: virtio: Keep control payloads alive through DMA completion Joonho Lee
2026-11-03  8:08 ` [PATCH v13 06/12] ALSA: virtio: wait through signals for PCM teardown Joonho Lee
2026-11-03  8:08 ` [PATCH v13 07/12] ALSA: pcm: mark stop-threshold accesses for capture policy sampling Joonho Lee
2026-11-03  8:08 ` [PATCH v13 08/12] ALSA: pcm: report partial progress under the stream lock Joonho Lee
2026-11-03  8:08 ` [PATCH v13 09/12] ALSA: pcm: add an optional reset operation " Joonho Lee
2026-11-03  8:08 ` [PATCH v13 10/12] ALSA: virtio: add staged capture and serialize PCM lifecycle accounting Joonho Lee
2026-11-03 11:05   ` sashiko-bot [this message]
2026-11-03  8:08 ` [PATCH v13 11/12] ALSA: virtio: prioritize staged capture period work Joonho Lee
2026-11-03  8:08 ` [PATCH v13 12/12] docs: describe virtio-sound capture options Joonho Lee
2026-11-03 11:27   ` 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-195105@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