Ordinary capture's indirect positions must follow the common RESET
observation while preserving the device's fixed DMA ring offsets. Select
its reset callback at open alongside the staged capture callback.

Suppress nested ACK refills during snd_pcm_lib_reset(). For paused
capture, hold the RX queue and substream locks while checking lifecycle
state and observing the latest completed position. Update a local copy
of the indirect state, return discarded RX space, and commit on success.
This also keeps the observation boundary consistent with a late RX
completion. Data arriving after the committed observation remains unread.

Honor an existing XRUN before changing private paused accounting or
refilling descriptors. On a refill failure, retain the error and complete
the core rebase; the worker then reports XRUN. RESET can return zero while
the error notification is pending. Prepare or reopen recovers the stream.
Device reset suppresses fresh notifications until a usable queue exists.

Set the ordinary notification floor to the RESET observation so queued
work cannot report a discarded period as new progress. Native resume
preserves the RX ring ownership established by the earlier patches.

Fault tests covered late completions, existing XRUN, nested refill and
refill failures with recovery and reopen. Ordinary and staged 70-second
RW and MMAP captures matched the deterministic sequence, including
PAUSE, RESET, resume and the intended discard boundary. The paused
interleaving test verifies the atomic observation boundary; it does not
establish sample loss in the original implementation.

Link: 
https://sashiko.dev/#/patchset/20261030052629.27742-1-mynameisjune111%40gmail.com?part=14
Assisted-by: LLM
Signed-off-by: Joonho Lee <[email protected]>

---
diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h
--- a/sound/virtio/virtio_pcm.h
+++ b/sound/virtio/virtio_pcm.h
@@ -76,6 +76,8 @@
  * @release_required: Successful PREPARE still needs a successful RELEASE.
  * @control_uncertain: Native control result requires device reset before 
reuse.
  * @capture_reset_observing: Reclaim staged samples during RESET pointer 
capture.
+ * @indirect_reset_observing: Suppress ordinary ACK refill during RESET pointer
+ *                            capture; protected by the PCM stream lock.
  * @suspended: True if the substream is suspended and must be reconfigured on
  *             the device side at resume.
  * @pause_restart_state: Device-side state for opt-in pause compatibility.
@@ -127,6 +129,7 @@
        bool release_required;
        bool control_uncertain;
        bool capture_reset_observing;
+       bool indirect_reset_observing;
        bool suspended;
        enum virtio_pcm_pause_restart_state pause_restart_state;
        struct virtio_pcm_msg **msgs;
diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
--- a/sound/virtio/virtio_pcm_ops.c
+++ b/sound/virtio/virtio_pcm_ops.c
@@ -10,6 +10,7 @@
 #include "virtio_card.h"
 
 static const struct snd_pcm_ops virtsnd_pcm_capture_staged_ops;
+static const struct snd_pcm_ops virtsnd_pcm_capture_indirect_ops;
 
 bool virtsnd_capture_staging;
 module_param_named(capture_staging, virtsnd_capture_staging, bool, 0444);
@@ -398,6 +399,9 @@
                &virtsnd_pcm_capture_staged_ops :
                &virtsnd_pcm_ops[substream->stream];
 
+       if (vss->direction == SNDRV_PCM_STREAM_CAPTURE && !vss->capture_staging)
+               substream->ops = &virtsnd_pcm_capture_indirect_ops;
+
        snd_pcm_hw_constraint_integer(substream->runtime,
                                      SNDRV_PCM_HW_PARAM_PERIODS);
 
@@ -1724,9 +1728,13 @@
  */
 static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
 {
+       struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
        struct snd_pcm_runtime *runtime = substream->runtime;
        int rc;
 
+       /* RESET observes the pointer before returning discarded RX space. */
+       if (vss->indirect_reset_observing)
+               return 0;
        if (!mutex_trylock(&runtime->buffer_mutex))
                return -EBADFD;
        rc = virtsnd_pcm_cp_ack_locked(substream);
@@ -1759,6 +1767,89 @@
                        return 0;
        }
        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 = {};
+       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);
+       if (vss->resetting)
+               return 0;
+       if (vss->xfer_xrun) {
+               virtsnd_pcm_transfer_failed(vss);
+               virtsnd_pcm_schedule_period_elapsed(vss);
+               return 0;
+       }
+       if (!snd_pcm_running(substream) && !paused)
+               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;
+       }
+
+       /* RESET discards all samples through the observed software pointer. */
+       transfer.indirect.sw_ready = 0;
+       transfer.indirect.appl_ptr = runtime->status->hw_ptr;
+       vss->pcm_indirect = transfer.indirect;
+       {
+               size_t period = snd_pcm_lib_period_bytes(substream);
+
+               vss->capture_notified_periods = period ?
+                       div64_ul(vss->capture_pointer_seen_total, period) : 0;
+       }
+       return 0;
 }
 
 /* The core holds the stream lock through the matching appl_ptr reset. */
@@ -1858,3 +1949,17 @@
        .pointer = virtsnd_pcm_cp_pointer,
        .ack = virtsnd_pcm_cp_ack,
 };
+
+static const struct snd_pcm_ops virtsnd_pcm_capture_indirect_ops = {
+       .open = virtsnd_pcm_open,
+       .close = virtsnd_pcm_close,
+       .ioctl = snd_pcm_lib_ioctl,
+       .reset = virtsnd_pcm_indirect_reset,
+       .hw_params = virtsnd_pcm_hw_params,
+       .hw_free = virtsnd_pcm_hw_free,
+       .prepare = virtsnd_pcm_prepare,
+       .trigger = virtsnd_pcm_trigger,
+       .sync_stop = virtsnd_pcm_sync_stop,
+       .pointer = virtsnd_pcm_cp_pointer,
+       .ack = virtsnd_pcm_cp_ack,
+};

Reply via email to