Linux Documentation
 help / color / mirror / Atom feed
From: Joonho Lee <mynameisjune111@gmail.com>
To: linux-sound@vger.kernel.org
Cc: tiwai@suse.de, perex@perex.cz, anton.yakovlev@opensynergy.com,
	mst@redhat.com, virtualization@lists.linux.dev,
	linux-kernel@vger.kernel.org, corbet@lwn.net,
	skhan@linuxfoundation.org, rdunlap@infradead.org,
	linux-doc@vger.kernel.org, Joonho Lee <mynameisjune111@gmail.com>
Subject: [PATCH v7 16/16] ALSA: virtio: serialize capture lifecycle and reset state
Date: Wed, 28 Oct 2026 18:41:32 +0900	[thread overview]
Message-ID: <20261028094132.20364-17-mynameisjune111@gmail.com> (raw)
In-Reply-To: <20261028094132.20364-1-mynameisjune111@gmail.com>

The staged capture paths can lose pending period notifications across
pause, keep counters based on the old ALSA position after RESET, and race
prepare with synchronous release. A RELEASE_SENT retry can also cancel
period work while holding the PCM lock, causing a lockdep cycle and hang.

Schedule pending period work after successful start or resume, rebase the
staged counters on RESET, and serialize process-context lifecycle calls
with a per-stream mutex. Keep trigger outside that mutex and wait for RX
without cancelling period work from the RELEASE_SENT trigger retry.
Notify an active runtime of resume failure even after transfer shutdown.

Invalidate late control responses with a reset generation. Revoke device
PREPARE permission at prepare entry and RELEASE completion. Allow cached
tails in DRAINING and released PAUSED capture without refilling RX. Remove
the successful paused-ACK threshold prediction; the preceding core change
marks synchronous cleanup on the actual stop transition.

Move runtime buffer allocation into hw_params after old RX cleanup under
the lifecycle mutex. Revoke transfer and copy access in hw_free before
synchronous cleanup and mapping release. Preserve request-owned DMA and
bounce-buffer lifetime protection.

Targeted QEMU tests cover pending notifications, RESET, paused forward and
wrapped reads, RELEASE retry, concurrent prepare, failed resume, late
control completion across bus freeze/restore, early prepare failure and
non-staged recovery. KASAN and lockdep report no errors in corrected runs.
NOMMU runtime and physical VZ/MOTU validation have not been repeated.

One-vCPU TCG contention still produces dsnoop client-threshold XRUNs on
both v6 and this candidate while the slave remains RUNNING. Both pass the
hardware-accelerated comparison. The cover letter records the failure
counts and the unresolved TCG worker wall-time difference.

Assisted-by: LLM
Signed-off-by: Joonho Lee <mynameisjune111@gmail.com>
---
diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c
index 852cc27..2fd8595 100644
--- a/sound/virtio/virtio_card.c
+++ b/sound/virtio/virtio_card.c
@@ -354,6 +354,7 @@ static void virtsnd_reset_vqs(struct virtio_snd *snd)
 
 		guard(spinlock_irqsave)(&queue->lock);
 		guard(spinlock)(&vss->lock);
+		vss->reset_epoch++;
 		vss->prepared = false;
 		vss->xfer_enabled = false;
 		vss->suspended = true;
diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c
index b62c181..1fe8c50 100644
--- a/sound/virtio/virtio_pcm.c
+++ b/sound/virtio/virtio_pcm.c
@@ -443,6 +443,7 @@ int virtsnd_pcm_parse_cfg(struct virtio_snd *snd)
 		INIT_WORK(&vss->elapsed_period, virtsnd_pcm_period_elapsed);
 		init_waitqueue_head(&vss->msg_empty);
 		spin_lock_init(&vss->lock);
+		mutex_init(&vss->lifecycle_lock);
 	}
 
 	info = kzalloc_objs(*info, snd->nsubstreams);
@@ -576,7 +577,7 @@ int virtsnd_pcm_build_devs(struct virtio_snd *snd)
 			snd_pcm_set_ops(vpcm->pcm, i, &virtsnd_pcm_ops[i]);
 		}
 
-		snd_pcm_set_managed_buffer_all(vpcm->pcm,
+		snd_pcm_lib_preallocate_pages_for_all(vpcm->pcm,
 					       SNDRV_DMA_TYPE_VMALLOC, NULL,
 					       0, 0);
 	}
diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h
index 1da8f09..1140b4b 100644
--- a/sound/virtio/virtio_pcm.h
+++ b/sound/virtio/virtio_pcm.h
@@ -7,6 +7,7 @@
 #define VIRTIO_SND_PCM_H
 
 #include <linux/atomic.h>
+#include <linux/mutex.h>
 #include <linux/virtio_config.h>
 #include <linux/workqueue.h>
 #include <sound/pcm.h>
@@ -43,6 +44,8 @@ enum virtio_pcm_pause_restart_state {
  * @pcm_indirect: Kernel indirect pcm structure.
  * @hw: Kernel ALSA substream hardware descriptor.
  * @elapsed_period: Kernel work to handle the elapsed period state.
+ * @lifecycle_lock: Serializes prepare with synchronous release, outside trigger.
+ * @reset_epoch: Invalidates control replies from before device reset.
  * @lock: Spinlock that protects fields shared by interrupt handlers and
  *        substream operators, including @stopped.
  * @buffer_bytes: Current buffer size in bytes.
@@ -89,6 +92,8 @@ struct virtio_pcm_substream {
 	struct snd_pcm_hardware hw;
 	struct work_struct elapsed_period;
 	spinlock_t lock;
+	struct mutex lifecycle_lock;
+	u64 reset_epoch;
 	size_t buffer_bytes;
 	size_t hw_ptr;
 	size_t capture_ready;
diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
index 0685a00..573fab6 100644
--- a/sound/virtio/virtio_pcm_ops.c
+++ b/sound/virtio/virtio_pcm_ops.c
@@ -128,11 +128,14 @@ static const struct virtsnd_a2v_rate g_a2v_rate_map[] = {
 };
 
 static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream);
+static int virtsnd_pcm_sync_stop_locked(struct snd_pcm_substream *substream);
 static int virtsnd_pcm_pause_restart_sync_stop(struct snd_pcm_substream *substream);
 
 static void virtsnd_pcm_start_failed(struct virtio_pcm_substream *vss)
 {
 	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (vss->suspended)
+			return;
 		vss->xfer_enabled = false;
 		vss->stopped = true;
 	}
@@ -160,6 +163,22 @@ static bool virtsnd_pcm_pause_restart_transitioning(enum virtio_pcm_pause_restar
 	}
 }
 
+static int virtsnd_pcm_send_sync(struct virtio_pcm_substream *vss,
+				 struct virtio_snd_msg *msg)
+{
+	u64 epoch;
+	int rc;
+
+	scoped_guard(spinlock_irqsave, &vss->lock)
+		epoch = vss->reset_epoch;
+	rc = virtsnd_ctl_msg_send_sync(vss->snd, msg);
+	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (epoch != vss->reset_epoch)
+			return -ECANCELED;
+	}
+	return rc;
+}
+
 static int
 virtsnd_pcm_pause_restart_send(struct virtio_pcm_substream *vss,
 			       struct virtio_snd_msg *msg,
@@ -169,12 +188,14 @@ virtsnd_pcm_pause_restart_send(struct virtio_pcm_substream *vss,
 {
 	enum virtio_pcm_pause_restart_state state;
 	bool uncertain;
+	u64 epoch;
 	int rc;
 
 	if (!virtsnd_pcm_pause_restart_enabled(vss))
-		return virtsnd_ctl_msg_send_sync(vss->snd, msg);
+		return virtsnd_pcm_send_sync(vss, msg);
 
 	scoped_guard(spinlock_irqsave, &vss->lock) {
+		epoch = vss->reset_epoch;
 		state = vss->pause_restart_state;
 		if (state == VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED) {
 			rc = -EIO;
@@ -200,6 +221,8 @@ virtsnd_pcm_pause_restart_send(struct virtio_pcm_substream *vss,
 	uncertain = virtsnd_ctl_msg_uncertain(msg, rc);
 	virtsnd_ctl_msg_unref(msg);
 	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (vss->reset_epoch != epoch)
+			return -ECANCELED;
 		if ((rc && uncertain) || vss->pause_restart_state != submitted_state) {
 			vss->pause_restart_state =
 				VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED;
@@ -227,8 +250,9 @@ virtsnd_pcm_pause_restart_fail(struct virtio_pcm_substream *vss,
 	bool notify_xrun;
 
 	scoped_guard(spinlock_irqsave, &vss->lock) {
-		notify_xrun = vss->runtime_active && vss->xfer_enabled &&
-			      !vss->xfer_xrun;
+		if (vss->suspended)
+			return;
+		notify_xrun = vss->runtime_active;
 		vss->xfer_enabled = false;
 		vss->stopped = true;
 		vss->xfer_xrun = true;
@@ -310,6 +334,7 @@ virtsnd_pcm_pause_release_flush(struct virtio_pcm_substream *vss,
 			    VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT) {
 			rc = -ETIMEDOUT;
 		} else {
+			vss->prepared = false;
 			vss->pause_restart_state =
 				VIRTSND_PCM_PAUSE_RESTART_RELEASED;
 		}
@@ -353,7 +378,6 @@ static int virtsnd_pcm_open(struct snd_pcm_substream *substream)
 
 	scoped_guard(spinlock_irqsave, &vss->lock)
 		vss->stopped |= !!vss->msg_count;
-	vss->suspended = false;
 
 	/*
 	 * If the substream has already been used, then the I/O queue may be in
@@ -478,6 +502,11 @@ static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,
 	bool stopped;
 	int rc;
 
+	guard(mutex)(&vss->lifecycle_lock);
+	/* Drain the previous buffer before ALSA can replace its mapping. */
+	rc = virtsnd_pcm_sync_stop_locked(substream);
+	if (rc)
+		return rc;
 	scoped_guard(spinlock_irqsave, &vss->lock)
 		vss->prepared = false;
 
@@ -505,6 +534,11 @@ static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,
 		return -EBADFD;
 	}
 
+	rc = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+	if (rc < 0)
+		return rc;
+	substream->runtime->buffer_changed = rc > 0;
+
 	rc = virtsnd_pcm_dev_set_params(vss, params_buffer_bytes(hw_params),
 					params_period_bytes(hw_params),
 					params_channels(hw_params),
@@ -539,9 +573,16 @@ static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,
 static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream)
 {
 	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
+	int rc;
 
-	scoped_guard(spinlock_irqsave, &vss->lock)
+	guard(mutex)(&vss->lifecycle_lock);
+	scoped_guard(spinlock_irqsave, &vss->lock) {
+		vss->stopped |= vss->prepared;
 		vss->prepared = false;
+		vss->runtime_active = false;
+		vss->xfer_enabled = false;
+	}
+	rc = virtsnd_pcm_sync_stop_locked(substream);
 
 	if (vss->capture_staging) {
 		scoped_guard(spinlock_irqsave, &vss->lock) {
@@ -567,17 +608,18 @@ static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream)
 	if (!virtsnd_pcm_msg_pending_num(vss))
 		virtsnd_pcm_msg_free(vss);
 
-	return 0;
+	snd_pcm_lib_free_pages(substream);
+	return rc;
 }
 
 /**
- * virtsnd_pcm_prepare() - Prepare the PCM substream.
+ * virtsnd_pcm_prepare_locked() - Prepare the PCM substream.
  * @substream: Kernel ALSA substream.
  *
  * Context: Process context.
  * Return: 0 on success, -errno on failure.
  */
-static int virtsnd_pcm_prepare(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;
@@ -585,6 +627,14 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
 	bool free_running;
 	int rc;
 	bool stopped;
+	bool suspended;
+	u64 epoch;
+
+	scoped_guard(spinlock_irqsave, &vss->lock) {
+		vss->prepared = false;
+		epoch = vss->reset_epoch;
+		suspended = vss->suspended;
+	}
 
 	if (pause_restart) {
 		enum virtio_pcm_pause_restart_state state;
@@ -607,7 +657,7 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
 		return -EBADFD;
 	}
 
-	if (!vss->suspended) {
+	if (!suspended) {
 		if (virtsnd_pcm_msg_pending_num(vss)) {
 			dev_err(&vdev->dev, "SID %u: invalid I/O queue state\n",
 				vss->sid);
@@ -630,6 +680,8 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
 	}
 
 	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (epoch != vss->reset_epoch)
+			return -ECANCELED;
 		vss->prepared = false;
 		vss->buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
 		vss->hw_ptr = 0;
@@ -651,10 +703,15 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
 	}
 
 	rc = virtsnd_pcm_dev_prepare(vss);
-	scoped_guard(spinlock_irqsave, &vss->lock)
+	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (epoch != vss->reset_epoch)
+			return -ECANCELED;
 		vss->prepared = !rc;
+	}
 	if (pause_restart) {
 		if (rc) {
+			if (rc == -ECANCELED)
+				return rc;
 			virtsnd_pcm_pause_restart_fail(vss, false);
 		} else {
 			scoped_guard(spinlock_irqsave, &vss->lock) {
@@ -667,6 +724,14 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
 	return rc;
 }
 
+static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
+
+	guard(mutex)(&vss->lifecycle_lock);
+	return virtsnd_pcm_prepare_locked(substream);
+}
+
 static int virtsnd_pcm_pause_restart_start(struct virtio_pcm_substream *vss,
 					   bool resume)
 {
@@ -704,6 +769,8 @@ static int virtsnd_pcm_pause_restart_start(struct virtio_pcm_substream *vss,
 	if (resume) {
 		rc = virtsnd_pcm_pause_prepare(vss);
 		if (rc) {
+			if (rc == -ECANCELED)
+				return rc;
 			virtsnd_pcm_pause_restart_failed(vss);
 			return rc;
 		}
@@ -715,11 +782,14 @@ static int virtsnd_pcm_pause_restart_start(struct virtio_pcm_substream *vss,
 				rc = -EBADFD;
 			} else {
 				/* Preserve ring accounting, restarting only startup grace. */
+				vss->prepared = true;
 				vss->capture_started = false;
 				vss->capture_empty = 0;
 			}
 		}
 		if (rc) {
+			if (rc == -ECANCELED)
+				return rc;
 			virtsnd_pcm_pause_restart_failed(vss);
 			return rc;
 		}
@@ -738,6 +808,8 @@ static int virtsnd_pcm_pause_restart_start(struct virtio_pcm_substream *vss,
 		}
 	}
 	if (rc) {
+		if (rc == -ECANCELED)
+			return rc;
 		virtsnd_pcm_pause_restart_failed(vss);
 		return rc;
 	}
@@ -753,12 +825,18 @@ static int virtsnd_pcm_pause_restart_start(struct virtio_pcm_substream *vss,
 					    VIRTSND_PCM_PAUSE_RESTART_START_SUBMITTED,
 					    VIRTSND_PCM_PAUSE_RESTART_RUNNING);
 	if (rc) {
+		if (rc == -ECANCELED)
+			return rc;
 		virtsnd_pcm_pause_restart_failed(vss);
 		return rc;
 	}
 
-	scoped_guard(spinlock_irqsave, &vss->lock)
+	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (vss->suspended)
+			return -ECANCELED;
 		vss->stopped = false;
+	}
+	virtsnd_pcm_schedule_period_elapsed(vss);
 
 	return 0;
 }
@@ -771,20 +849,31 @@ static int virtsnd_pcm_pause_restart_pause(struct virtio_pcm_substream *vss)
 
 	scoped_guard(spinlock_irqsave, &vss->lock) {
 		state = vss->pause_restart_state;
-		if (state == VIRTSND_PCM_PAUSE_RESTART_RELEASED)
+		if (state == VIRTSND_PCM_PAUSE_RESTART_RELEASED) {
+			if (vss->xfer_xrun)
+				virtsnd_pcm_schedule_period_elapsed(vss);
 			return vss->xfer_xrun ? -EPIPE : 0;
+		}
 		vss->xfer_enabled = false;
 		vss->stopped = true;
 	}
 
 	if (state == VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT) {
-		rc = virtsnd_pcm_pause_restart_sync_stop(vss->substream);
-		if (!rc) {
-			guard(spinlock_irqsave)(&vss->lock);
-			vss->stopped = true;
-			if (vss->xfer_xrun)
-				rc = -EPIPE;
+		unsigned int js = msecs_to_jiffies(virtsnd_msg_timeout_ms);
+
+		/* Trigger holds the PCM lock: never cancel the period worker here. */
+		if (!wait_event_timeout(vss->msg_empty,
+				       !virtsnd_pcm_msg_pending_num(vss), js))
+			return -ETIMEDOUT;
+		scoped_guard(spinlock_irqsave, &vss->lock) {
+			if (vss->suspended)
+				return -ECANCELED;
+			vss->prepared = false;
+			vss->pause_restart_state = VIRTSND_PCM_PAUSE_RESTART_RELEASED;
+			rc = vss->xfer_xrun ? -EPIPE : 0;
 		}
+		if (rc)
+			virtsnd_pcm_pause_restart_failed(vss);
 		return rc;
 	}
 
@@ -807,6 +896,8 @@ static int virtsnd_pcm_pause_restart_pause(struct virtio_pcm_substream *vss)
 						    VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED,
 					    VIRTSND_PCM_PAUSE_RESTART_STOPPED);
 		if (rc) {
+			if (rc == -ECANCELED)
+				return rc;
 			virtsnd_pcm_pause_restart_failed(vss);
 			return rc;
 		}
@@ -821,6 +912,8 @@ static int virtsnd_pcm_pause_restart_pause(struct virtio_pcm_substream *vss)
 					     BIT(VIRTSND_PCM_PAUSE_RESTART_STOPPED) |
 					     BIT(VIRTSND_PCM_PAUSE_RESTART_PREPARED));
 	if (rc) {
+		if (rc == -ECANCELED)
+			return rc;
 		virtsnd_pcm_pause_restart_failed(vss);
 		return rc;
 	}
@@ -836,6 +929,8 @@ static int virtsnd_pcm_pause_restart_stop(struct virtio_pcm_substream *vss,
 	int rc;
 
 	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (!vss->prepared && !vss->msg_count)
+			return 0;
 		if (suspend)
 			vss->suspended = true;
 		vss->xfer_enabled = false;
@@ -861,7 +956,7 @@ static int virtsnd_pcm_pause_restart_stop(struct virtio_pcm_substream *vss,
 					    BIT(VIRTSND_PCM_PAUSE_RESTART_RUNNING),
 					    VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED,
 					    VIRTSND_PCM_PAUSE_RESTART_STOPPED);
-	if (rc)
+	if (rc && rc != -ECANCELED)
 		virtsnd_pcm_pause_restart_failed(vss);
 
 	return rc;
@@ -879,7 +974,6 @@ static int virtsnd_pcm_pause_restart_stop(struct virtio_pcm_substream *vss,
 static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
 {
 	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
-	struct virtio_snd *snd = vss->snd;
 	struct virtio_snd_queue *queue;
 	struct virtio_snd_msg *msg;
 	int rc = 0;
@@ -945,10 +1039,12 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
 			return -ENOMEM;
 		}
 
-		rc = virtsnd_ctl_msg_send_sync(snd, msg);
-		if (rc)
+		rc = virtsnd_pcm_send_sync(vss, msg);
+		if (rc && rc != -ECANCELED)
 			virtsnd_pcm_start_failed(vss);
 
+		if (!rc && vss->capture_staging)
+			virtsnd_pcm_schedule_period_elapsed(vss);
 		return rc;
 	case SNDRV_PCM_TRIGGER_SUSPEND:
 		scoped_guard(spinlock_irqsave, &vss->lock)
@@ -957,6 +1053,8 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
 	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;
@@ -967,7 +1065,7 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
 		if (!msg)
 			return -ENOMEM;
 
-		return virtsnd_ctl_msg_send_sync(snd, msg);
+		return virtsnd_pcm_send_sync(vss, msg);
 	default:
 		return -EINVAL;
 	}
@@ -1023,6 +1121,8 @@ virtsnd_pcm_pause_restart_sync_stop(struct snd_pcm_substream *substream)
 						    VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED,
 						    VIRTSND_PCM_PAUSE_RESTART_STOPPED);
 		if (rc) {
+			if (rc == -ECANCELED)
+				return rc;
 			virtsnd_pcm_pause_restart_fail(vss, false);
 			return rc;
 		}
@@ -1039,11 +1139,14 @@ virtsnd_pcm_pause_restart_sync_stop(struct snd_pcm_substream *substream)
 			return rc;
 		}
 		scoped_guard(spinlock_irqsave, &vss->lock) {
+			if (vss->suspended)
+				return -ECANCELED;
 			if (vss->msg_count) {
 				rc = -ETIMEDOUT;
 			} else {
 				vss->pause_restart_state =
 					VIRTSND_PCM_PAUSE_RESTART_RELEASED;
+				vss->prepared = false;
 				vss->stopped = false;
 				rc = 0;
 			}
@@ -1063,18 +1166,24 @@ virtsnd_pcm_pause_restart_sync_stop(struct snd_pcm_substream *substream)
 					     BIT(VIRTSND_PCM_PAUSE_RESTART_PREPARED) |
 					     BIT(VIRTSND_PCM_PAUSE_RESTART_STOPPED));
 	if (rc) {
+		if (rc == -ECANCELED)
+			return rc;
 		virtsnd_pcm_pause_restart_fail(vss, false);
 		return rc;
 	}
 
-	scoped_guard(spinlock_irqsave, &vss->lock)
+	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (vss->suspended)
+			return -ECANCELED;
+		vss->prepared = false;
 		vss->stopped = false;
+	}
 
 	return 0;
 }
 
 /**
- * virtsnd_pcm_sync_stop() - Synchronous PCM substream stop.
+ * virtsnd_pcm_sync_stop_locked() - Synchronous PCM substream stop.
  * @substream: Kernel ALSA substream.
  *
  * The function can be called both from the upper level or from the driver
@@ -1083,7 +1192,7 @@ virtsnd_pcm_pause_restart_sync_stop(struct snd_pcm_substream *substream)
  * Context: Process context. Takes and releases the VirtIO substream spinlock.
  * Return: 0 on success, -errno on failure.
  */
-static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
+static int virtsnd_pcm_sync_stop_locked(struct snd_pcm_substream *substream)
 {
 	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
 	struct virtio_snd *snd = vss->snd;
@@ -1097,7 +1206,7 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
 
 	scoped_guard(spinlock_irqsave, &vss->lock) {
 		vss->xfer_enabled = false;
-		need_release = vss->stopped || vss->msg_count;
+		need_release = vss->prepared || vss->stopped || vss->msg_count;
 		if (need_release)
 			vss->stopped = true;
 	}
@@ -1112,7 +1221,7 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
 		return -ENOMEM;
 	}
 
-	rc = virtsnd_ctl_msg_send_sync(snd, msg);
+	rc = virtsnd_pcm_send_sync(vss, msg);
 	if (rc) {
 		substream->runtime->stop_operating = true;
 		return rc;
@@ -1135,8 +1244,12 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
 		return !rc ? -ETIMEDOUT : rc;
 	}
 
-	scoped_guard(spinlock_irqsave, &vss->lock)
+	scoped_guard(spinlock_irqsave, &vss->lock) {
+		if (vss->suspended)
+			return -ECANCELED;
+		vss->prepared = false;
 		vss->stopped = false;
+	}
 
 	return 0;
 }
@@ -1299,8 +1412,13 @@ static int virtsnd_pcm_capture_staged_ack(struct virtio_pcm_substream *vss,
 		u64 next_acked_total;
 		bool free_running;
 
-		if (!vss->prepared || !vss->runtime_active || !runtime || !runtime->control ||
-		    !runtime->dma_area)
+		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;
@@ -1376,9 +1494,7 @@ static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
 
 	if (vss->capture_staging) {
 		rc = virtsnd_pcm_capture_staged_ack(vss, runtime);
-		if (rc == -EPIPE ||
-		    (!rc && runtime->state == SNDRV_PCM_STATE_PAUSED &&
-		     snd_pcm_capture_avail(runtime) >= runtime->stop_threshold)) {
+		if (rc == -EPIPE) {
 			guard(spinlock_irqsave)(&vss->lock);
 			virtsnd_pcm_capture_arm_paused_stop(vss);
 		}
@@ -1387,7 +1503,7 @@ static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
 
 	scoped_guard(spinlock_irqsave, &queue->lock) {
 		guard(spinlock)(&vss->lock);
-		if (!vss->prepared)
+		if (!vss->prepared && runtime->state != SNDRV_PCM_STATE_DRAINING)
 			return -EBADFD;
 		transfer.indirect = vss->pcm_indirect;
 		if (vss->stopped && runtime->state != SNDRV_PCM_STATE_DRAINING) {
@@ -1415,12 +1531,52 @@ static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream)
 	return 0;
 }
 
+static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
+{
+	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
+
+	guard(mutex)(&vss->lifecycle_lock);
+	return virtsnd_pcm_sync_stop_locked(substream);
+}
+
+static int virtsnd_pcm_ioctl(struct snd_pcm_substream *substream,
+			    unsigned int cmd, void *arg)
+{
+	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	size_t position;
+	size_t period;
+	int rc;
+
+	if (cmd != SNDRV_PCM_IOCTL1_RESET || !vss->capture_staging)
+		return snd_pcm_lib_ioctl(substream, cmd, arg);
+
+	rc = snd_pcm_lib_ioctl(substream, cmd, arg);
+	if (rc)
+		return rc;
+	guard(pcm_stream_lock_irqsave)(substream);
+	runtime->status->hw_ptr %= runtime->buffer_size;
+	position = frames_to_bytes(runtime, runtime->status->hw_ptr);
+	period = snd_pcm_lib_period_bytes(substream);
+	guard(spinlock_irqsave)(&vss->lock);
+	vss->hw_ptr = position;
+	vss->capture_copied_total = position;
+	vss->capture_acked_total = position;
+	vss->capture_pointer_seen_total = position;
+	vss->capture_ready = 0;
+	vss->capture_notified_periods = period ? position / period : 0;
+	vss->capture_callback_snapshot = 0;
+	vss->capture_callback_snapshot_active = false;
+	vss->pcm_indirect.appl_ptr = runtime->status->hw_ptr;
+	return 0;
+}
+
 /* PCM substream operators map. */
 const struct snd_pcm_ops virtsnd_pcm_ops[] = {
 	{
 		.open = virtsnd_pcm_open,
 		.close = virtsnd_pcm_close,
-		.ioctl = snd_pcm_lib_ioctl,
+		.ioctl = virtsnd_pcm_ioctl,
 		.hw_params = virtsnd_pcm_hw_params,
 		.hw_free = virtsnd_pcm_hw_free,
 		.prepare = virtsnd_pcm_prepare,
@@ -1432,7 +1588,7 @@ const struct snd_pcm_ops virtsnd_pcm_ops[] = {
 	{
 		.open = virtsnd_pcm_open,
 		.close = virtsnd_pcm_close,
-		.ioctl = snd_pcm_lib_ioctl,
+		.ioctl = virtsnd_pcm_ioctl,
 		.hw_params = virtsnd_pcm_hw_params,
 		.hw_free = virtsnd_pcm_hw_free,
 		.prepare = virtsnd_pcm_prepare,

      parent reply	other threads:[~2026-10-28  9:42 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28  9:41 [PATCH v7 00/16] ALSA: virtio: staged capture and dsnoop recovery Joonho Lee
2026-10-28  9:41 ` [PATCH v7 01/16] ALSA: virtio: Propagate PCM send failures through ACK Joonho Lee
2026-10-28  9:41 ` [PATCH v7 02/16] ALSA: virtio: Recover PCM state after a failed start Joonho Lee
2026-10-28  9:41 ` [PATCH v7 03/16] ALSA: virtio: Retain queued messages through device reset Joonho Lee
2026-10-28  9:41 ` [PATCH v7 04/16] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Joonho Lee
2026-10-28  9:41 ` [PATCH v7 05/16] ALSA: virtio: Keep control payloads alive through DMA completion Joonho Lee
2026-10-28  9:41 ` [PATCH v7 06/16] ALSA: virtio: wait through signals for PCM teardown Joonho Lee
2026-10-28  9:41 ` [PATCH v7 07/16] ALSA: virtio: stage VZ capture and restart paused streams Joonho Lee
2026-10-28  9:41 ` [PATCH v7 08/16] ALSA: virtio: account staged capture reads before RX refill Joonho Lee
2026-10-28  9:41 ` [PATCH v7 09/16] ALSA: virtio: prioritize staged capture period work Joonho Lee
2026-10-28  9:41 ` [PATCH v7 10/16] ALSA: pcm: mark stop-threshold accesses for capture policy sampling Joonho Lee
2026-10-28  9:41 ` [PATCH v7 11/16] ALSA: virtio-snd: fix dsnoop capture with staged buffers Joonho Lee
2026-10-28  9:41 ` [PATCH v7 12/16] ALSA: virtio: complete control transitions and preserve drain tail Joonho Lee
2026-10-28  9:41 ` [PATCH v7 13/16] ALSA: virtio: fence staged capture state and recovery Joonho Lee
2026-10-28  9:41 ` [PATCH v7 14/16] docs: describe virtio-sound capture options Joonho Lee
2026-10-28  9:41 ` [PATCH v7 15/16] ALSA: pcm: synchronize stopped paused streams Joonho Lee
2026-10-28  9:41 ` 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=20261028094132.20364-17-mynameisjune111@gmail.com \
    --to=mynameisjune111@gmail.com \
    --cc=anton.yakovlev@opensynergy.com \
    --cc=corbet@lwn.net \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=mst@redhat.com \
    --cc=perex@perex.cz \
    --cc=rdunlap@infradead.org \
    --cc=skhan@linuxfoundation.org \
    --cc=tiwai@suse.de \
    --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