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 <[email protected]> --- 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,
