From: Joonho Lee <mynameisjune111@gmail.com>
To: linux-sound@vger.kernel.org
Cc: perex@perex.cz, tiwai@suse.com, anton.yakovlev@opensynergy.com,
mst@redhat.com, virtualization@lists.linux.dev,
linux-kernel@vger.kernel.org,
Joonho Lee <mynameisjune111@gmail.com>
Subject: [PATCH 2/2] ALSA: virtio-snd: fix dsnoop capture with staged buffers
Date: Sat, 24 Oct 2026 11:32:32 +0900 [thread overview]
Message-ID: <20261024023232.84448-3-mynameisjune111@gmail.com> (raw)
In-Reply-To: <20261024023232.84448-1-mynameisjune111@gmail.com>
dsnoop runs its hardware slave with stop_threshold == boundary and can
consume the mmap capture ring without advancing the hardware application
pointer. Staged capture currently treats every stream as a bounded
consumer: capture_ready grows until the completion capacity check fails.
With a 48 kHz mono slave, 960-frame periods and a 7680-frame buffer, the
baseline dsnoop reproduction stops after 6720 frames with -EPIPE and a
ring_capacity rejection. Direct capture continues to work.
Cache the prepared boundary and recognize the free-running policy under
the substream lock. Keep RX requests supplied in that mode, while retaining
payload, frame alignment, counter and pointer-window checks. Sample the
policy once per completion so validation, copying and refill agree.
Refresh it separately in the ACK accounting and refill critical sections.
On a return to bounded capture, reconstruct unread occupancy from the
copied and acknowledged totals. If outstanding data fills the ring, the
policy helper returns -EPIPE and preserves the cached mode and counters.
The detecting RX, pointer or ACK path reports XRUN through its existing
error path; this does not make sw_params fail synchronously.
Reset cached policy state during prepare and teardown.
In MOTU/VZ comparisons with one and two vCPUs, the baseline failed all
32 shared-client captures. The candidate passed 48 capture cases and eight
recovery diagnostics, including 70-second CPU-saturated shared captures.
Synthetic QEMU dsnoop data matched 2160000 reference frames across 12
files. Forced policy transitions passed KASAN/KCSAN checks. Aggressive
KCSAN still triggered dsnoop client-threshold XRUNs with a 160 ms buffer.
The physical tests checked frame counts and nonzero mono input; exact
sample continuity was checked with the synthetic source.
Assisted-by: LLM
Signed-off-by: Joonho Lee <mynameisjune111@gmail.com>
---
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
@@ -47,7 +47,9 @@
* substream operators, including @stopped.
* @buffer_bytes: Current buffer size in bytes.
* @hw_ptr: Substream hardware pointer value in bytes [0 ... buffer_bytes).
- * @capture_ready: Captured bytes copied but not yet acknowledged by ALSA.
+ * @capture_ready: Managed-mode bytes copied but not yet acknowledged by ALSA;
+ * always zero in free-running mode.
+ * @capture_boundary: ALSA boundary captured for this prepared epoch.
* @capture_copied_total: Monotonic bytes copied into the capture ring.
* @capture_acked_total: Monotonic bytes acknowledged by the capture client.
* @capture_pointer_seen_total: Last capture position sampled by ALSA.
@@ -58,6 +60,7 @@
* @capture_staging: Capture uses request-owned payload buffers.
* @capture_started: Non-empty capture data was received since prepare.
* @capture_empty: Consecutive empty startup completions.
+ * @capture_free_running: Cached staged-capture policy, protected by @lock.
* @xfer_enabled: Data transfer state (0 - off, 1 - on).
* @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun).
* @stopped: True if the substream is stopped and must be released on the device
@@ -88,6 +91,7 @@
size_t buffer_bytes;
size_t hw_ptr;
size_t capture_ready;
+ snd_pcm_uframes_t capture_boundary;
u64 capture_copied_total;
u64 capture_acked_total;
u64 capture_pointer_seen_total;
@@ -97,6 +101,7 @@
bool runtime_active;
bool capture_staging;
bool capture_started;
+ bool capture_free_running;
unsigned int capture_empty;
bool xfer_enabled;
bool xfer_xrun;
@@ -128,23 +133,29 @@
/*
* ALSA may observe a subperiod tail before its period callback. Keep actual
- * unread occupancy separate from pointer observations and notification state.
+ * unread occupancy separate from pointer observations and notification state;
+ * free-running streams account occupancy from monotonic totals instead.
*/
static inline bool
virtsnd_pcm_capture_counters_valid(const struct virtio_pcm_substream *vss)
{
- return vss->buffer_bytes &&
- vss->capture_copied_total >= vss->capture_acked_total &&
- vss->capture_copied_total - vss->capture_acked_total ==
- (u64)vss->capture_ready &&
- vss->capture_ready < vss->buffer_bytes &&
- vss->capture_pointer_seen_total <= vss->capture_copied_total &&
- vss->hw_ptr == vss->capture_copied_total % vss->buffer_bytes &&
- (!vss->capture_callback_snapshot_active ||
- (vss->capture_callback_snapshot >=
- vss->capture_pointer_seen_total &&
- vss->capture_callback_snapshot <=
- vss->capture_copied_total));
+ u64 outstanding;
+
+ if (!vss->buffer_bytes ||
+ vss->capture_copied_total < vss->capture_acked_total ||
+ vss->capture_pointer_seen_total > vss->capture_copied_total ||
+ vss->hw_ptr != vss->capture_copied_total % vss->buffer_bytes ||
+ (vss->capture_callback_snapshot_active &&
+ (vss->capture_callback_snapshot < vss->capture_pointer_seen_total ||
+ vss->capture_callback_snapshot > vss->capture_copied_total)))
+ return false;
+
+ if (vss->capture_free_running)
+ return !vss->capture_ready;
+
+ outstanding = vss->capture_copied_total - vss->capture_acked_total;
+ return outstanding == (u64)vss->capture_ready &&
+ vss->capture_ready < vss->buffer_bytes;
}
/**
@@ -207,6 +218,9 @@
int virtsnd_pcm_capture_send(struct virtio_pcm_substream *vss);
+int virtsnd_pcm_capture_refresh_mode_locked(struct virtio_pcm_substream *vss,
+ bool *free_running);
+
void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss);
void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss);
diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c
--- a/sound/virtio/virtio_pcm_msg.c
+++ b/sound/virtio/virtio_pcm_msg.c
@@ -3,6 +3,7 @@
* virtio-snd: Virtio sound device
* Copyright (C) 2021 OpenSynergy GmbH
*/
+#include <linux/compiler.h>
#include <linux/limits.h>
#include <linux/mm.h>
#include <linux/overflow.h>
@@ -384,6 +385,8 @@
vss->capture_notified_periods = 0;
vss->capture_callback_snapshot = 0;
vss->capture_callback_snapshot_active = false;
+ vss->capture_boundary = 0;
+ vss->capture_free_running = false;
vss->capture_empty = 0;
vss->capture_started = false;
}
@@ -508,24 +511,54 @@
return 0;
}
-/**
- * virtsnd_pcm_capture_send() - Queue staged capture requests.
- * @vss: VirtIO PCM substream.
- *
- * Queue full-period requests while the outstanding requests fit the remaining
- * ALSA ring capacity. The final request may exceed that capacity by less than
- * one period; completion validates the returned payload before copying it.
- *
- * Context: Expects the RX queue and substream spinlocks to be held by caller.
- * Return: 0 on success, -errno on failure.
- */
-int virtsnd_pcm_capture_send(struct virtio_pcm_substream *vss)
+/* Caller holds vss->lock. */
+int virtsnd_pcm_capture_refresh_mode_locked(struct virtio_pcm_substream *vss,
+ bool *free_running)
+{
+ struct snd_pcm_runtime *runtime;
+ snd_pcm_uframes_t stop_threshold;
+ u64 outstanding;
+ bool new_free_running;
+
+ if (!vss->capture_staging || !vss->runtime_active || !vss->substream)
+ return -EBADFD;
+
+ runtime = vss->substream->runtime;
+ if (!runtime || !vss->buffer_bytes || !vss->capture_boundary)
+ return -EBADFD;
+
+ /* Validate the currently committed policy before observing a transition. */
+ if (!virtsnd_pcm_capture_counters_valid(vss))
+ return -EIO;
+
+ stop_threshold = READ_ONCE(runtime->stop_threshold);
+ new_free_running = stop_threshold == vss->capture_boundary;
+ if (new_free_running != vss->capture_free_running) {
+ outstanding = vss->capture_copied_total -
+ vss->capture_acked_total;
+ if (new_free_running) {
+ vss->capture_ready = 0;
+ } else {
+ if (outstanding >= vss->buffer_bytes)
+ return -EPIPE;
+ vss->capture_ready = outstanding;
+ }
+ vss->capture_free_running = new_free_running;
+ }
+
+ *free_running = new_free_running;
+ return 0;
+}
+
+/* Caller holds the RX queue and vss locks and has sampled the policy. */
+static int virtsnd_pcm_capture_enqueue(struct virtio_pcm_substream *vss,
+ bool free_running)
{
struct virtio_snd_queue *queue = virtsnd_pcm_queue(vss);
struct virtqueue *vqueue = queue->vqueue;
unsigned long period_bytes;
size_t free_bytes;
- size_t request_limit;
+ size_t request_limit = vss->nmsgs;
unsigned int i;
bool added = false;
@@ -535,16 +568,17 @@
period_bytes = snd_pcm_lib_period_bytes(vss->substream);
if (!period_bytes || !vss->buffer_bytes ||
period_bytes >= vss->buffer_bytes ||
+ free_running != vss->capture_free_running ||
!virtsnd_pcm_capture_counters_valid(vss) ||
vss->msg_count > vss->nmsgs)
return -EIO;
- free_bytes = vss->buffer_bytes - vss->capture_ready;
- request_limit = free_bytes / period_bytes +
- !!(free_bytes % period_bytes);
- request_limit = min_t(size_t, request_limit, vss->nmsgs);
- if (vss->msg_count > request_limit)
- return -EIO;
+ if (!free_running) {
+ free_bytes = vss->buffer_bytes - vss->capture_ready;
+ request_limit = free_bytes / period_bytes +
+ !!(free_bytes % period_bytes);
+ request_limit = min_t(size_t, request_limit, vss->nmsgs);
+ }
for (i = 0; i < vss->nmsgs && vss->msg_count < request_limit; ++i) {
struct virtio_pcm_msg *msg = vss->msgs[i];
@@ -585,6 +619,25 @@
return 0;
}
+/**
+ * virtsnd_pcm_capture_send() - Refresh policy and queue staged requests.
+ * @vss: VirtIO PCM substream.
+ *
+ * Context: Expects the RX queue and substream spinlocks to be held by caller.
+ * Return: 0 on success, -errno on failure.
+ */
+int virtsnd_pcm_capture_send(struct virtio_pcm_substream *vss)
+{
+ bool free_running;
+ int rc;
+
+ rc = virtsnd_pcm_capture_refresh_mode_locked(vss, &free_running);
+ if (rc)
+ return rc;
+
+ return virtsnd_pcm_capture_enqueue(vss, free_running);
+}
+
static int virtsnd_pcm_capture_status(const struct virtio_pcm_msg *msg,
size_t written_bytes,
size_t frame_bytes,
@@ -638,6 +691,7 @@
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);
@@ -651,6 +705,9 @@
/* A full ring is modulo-ambiguous, so fail closed before copying. */
if (!runtime || !runtime->dma_area)
invalid_reason = "runtime";
+ else if (virtsnd_pcm_capture_refresh_mode_locked(vss,
+ &free_running))
+ invalid_reason = "policy";
else if (!virtsnd_pcm_capture_counters_valid(vss))
invalid_reason = "counters";
else if (vss->hw_ptr !=
@@ -660,18 +717,28 @@
frame_bytes, &audio_bytes,
&latency_bytes))
invalid_reason = "status";
- else if (audio_bytes >=
- vss->buffer_bytes - vss->capture_ready)
- invalid_reason = "ring_capacity";
- else if (check_add_overflow(vss->capture_ready, audio_bytes,
- &next_ready))
- invalid_reason = "ready_overflow";
- else if (next_ready >= vss->buffer_bytes)
- invalid_reason = "ring_full";
else if (check_add_overflow(vss->capture_copied_total,
(u64)audio_bytes,
&next_copied_total))
invalid_reason = "total_overflow";
+ else if (free_running && audio_bytes >= vss->buffer_bytes)
+ invalid_reason = "payload_exceeds_ring";
+ else if (!free_running &&
+ audio_bytes >= vss->buffer_bytes - vss->capture_ready)
+ invalid_reason = "ring_capacity";
+ else if (!free_running &&
+ check_add_overflow(vss->capture_ready, audio_bytes,
+ &next_ready))
+ invalid_reason = "ready_overflow";
+ else if (!free_running && next_ready >= vss->buffer_bytes)
+ invalid_reason = "ring_full";
+ else if (free_running &&
+ (next_copied_total < vss->capture_pointer_seen_total ||
+ next_copied_total - vss->capture_pointer_seen_total >=
+ vss->buffer_bytes))
+ invalid_reason = "pointer_window";
+ else if (free_running)
+ next_ready = 0;
if (invalid_reason) {
virtsnd_pcm_capture_xrun_locked(vss);
@@ -713,7 +780,8 @@
msg->length = 0;
if (vss->runtime_active && vss->xfer_enabled) {
- if (!vss->xfer_xrun && virtsnd_pcm_capture_send(vss)) {
+ if (!vss->xfer_xrun &&
+ virtsnd_pcm_capture_enqueue(vss, free_running)) {
dev_warn_ratelimited(&vss->snd->vdev->dev,
"SID %u: failed to refill capture queue\n",
vss->sid);
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
@@ -543,6 +543,8 @@
vss->capture_notified_periods = 0;
vss->capture_callback_snapshot = 0;
vss->capture_callback_snapshot_active = false;
+ vss->capture_boundary = 0;
+ vss->capture_free_running = false;
}
}
@@ -565,6 +567,7 @@
struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
struct virtio_device *vdev = vss->snd->vdev;
bool pause_restart = virtsnd_pcm_pause_restart_enabled(vss);
+ bool free_running;
int rc;
bool stopped;
@@ -622,6 +625,15 @@
vss->suspended = false;
vss->msg_count = 0;
virtsnd_pcm_msg_reset(vss);
+ if (vss->capture_staging) {
+ scoped_guard(spinlock_irqsave, &vss->lock) {
+ vss->capture_boundary = substream->runtime->boundary;
+ rc = virtsnd_pcm_capture_refresh_mode_locked(vss,
+ &free_running);
+ }
+ if (rc)
+ return rc;
+ }
memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect));
vss->pcm_indirect.sw_buffer_size =
@@ -1132,12 +1144,18 @@
struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
size_t hw_ptr = 0;
u64 total;
+ bool free_running;
bool xrun;
if (vss->capture_staging) {
scoped_guard(spinlock_irqsave, &vss->lock) {
- xrun = vss->xfer_xrun ||
- !virtsnd_pcm_capture_counters_valid(vss);
+ xrun = vss->xfer_xrun;
+ if (!xrun &&
+ virtsnd_pcm_capture_refresh_mode_locked(vss,
+ &free_running))
+ xrun = true;
+ if (!xrun && !virtsnd_pcm_capture_counters_valid(vss))
+ xrun = true;
if (!xrun) {
total = vss->capture_callback_snapshot_active ?
vss->capture_callback_snapshot :
@@ -1244,14 +1262,18 @@
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)
return -EBADFD;
if (vss->xfer_xrun)
return -EPIPE;
- if (!virtsnd_pcm_capture_counters_valid(vss)) {
+ rc = virtsnd_pcm_capture_refresh_mode_locked(vss,
+ &free_running);
+ if (rc || !virtsnd_pcm_capture_counters_valid(vss)) {
vss->xfer_xrun = true;
virtsnd_pcm_schedule_period_elapsed(vss);
return -EPIPE;
@@ -1270,7 +1292,11 @@
/* ACK includes any subperiod tail already copied to the ring. */
bytes = frames_to_bytes(runtime, diff);
- if (bytes > vss->capture_ready ||
+ 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_copied_total) {
@@ -1280,7 +1306,8 @@
}
vss->capture_acked_total = next_acked_total;
- vss->capture_ready -= bytes;
+ if (!free_running)
+ vss->capture_ready -= bytes;
vss->pcm_indirect.appl_ptr = appl_ptr;
}
prev parent reply other threads:[~2026-10-24 2:32 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-24 2:32 [PATCH 0/2] " Joonho Lee
2026-10-24 2:32 ` [PATCH 1/2] ALSA: pcm: mark stop-threshold accesses for capture policy sampling Joonho Lee
2026-10-24 2:32 ` Joonho Lee [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=20261024023232.84448-3-mynameisjune111@gmail.com \
--to=mynameisjune111@gmail.com \
--cc=anton.yakovlev@opensynergy.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=mst@redhat.com \
--cc=perex@perex.cz \
--cc=tiwai@suse.com \
--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
all inboxes | Powered by JetHome®