From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=74.125.82.50 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794444491; b=um73QzU4BpAMiFQSwgAL05ydtx9YesWrLGLNAOsQXJwIuN8wJUX4DGYW167xUxI57kAY pf53p8847P8Y9N44eM92QbHjlVhYaw6TNbcYqFz5NUm426XQLVmjR8kLc0MYsy7+8i39/ PspHCJfBsIi0aA1Bdkyh6Mh6/dak/skdW19hjO/3e+k6WmELRhdDq6xB5ETF+g8+YhN+R x/gYKesAgyRwKV7Ym9C7JwEL20JlQat8uaSKO2ArrWd8+uVSD22ii43bz1zl7gJOuy2CO VoXB3CIvUg4o1hDPqjLRDeDPXbz1fMWeMtzXpBIy8AtsqMVp6iFnUc0hapw1G7vkXBQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794444491; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Cc:Subject:Date:Message-ID:X-Mailer:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding; bh=GvZRz7pMJTTG+4G9hbnVw4N7YnGzeqtiFAM1IrYyU/w=; b=oY33glgH4GFpzjufKFkZjKR7TWLzcyFl3cmhs3zgpkQ2HV1PioHpmfaGwABtckqYOVf3 zLsunI/d3y2a6G1IYkUbudflFdwWMc+E/sZ3xkzkqraDsbMSa8GMmaxUbwHRwzlWSfCeI 6olcGe7xnDA2lJB1hYjYEfoP+VrcVBujb5boTQKWV+6pbUuMS+H++XEYXd3dcOYhPhw21 64dfWqHy/m8+yzYaGDaWxQHdFsqIfgiyghZ93hcIh6IXZxPV4KfRVXOb38YAuXN4mqMUJ Y2qnISvWVq5OPk5q5STpDyRwKhv4XFmprgvvvpf+aLmQ6eajyXQnOr7si23ZOLft11w== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b="UKd/ZpZU"; arc=none smtp.remote-ip=74.125.82.50 Received: from mail-dl1-f50.google.com (mail-dl1-f50.google.com [74.125.82.50]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9127F606C751 for ; Thu, 12 Nov 2026 00:48:10 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 9127F606C751 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=UKd/ZpZU Received: by mail-dl1-f50.google.com with SMTP id a92af1059eb24-1931ff67a29so6592938c88.1 for ; Wed, 11 Nov 2026 16:48:10 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1794444490; x=1795049290; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=GvZRz7pMJTTG+4G9hbnVw4N7YnGzeqtiFAM1IrYyU/w=; b=UKd/ZpZUqa6G2Itc7DH9TGaexCb90QLHlnwtcHXH0NLss5z1zdam3O68ucwX7wQz0x EtwS1nxxJCZMsjxe6vGuhSv9LGCTFj2Pa2OkKrhbxeYwCd6hUVvDF9i3rsMRpI1lS+Fp c46kMc/WJ6kfm7fKMx6nCT9MO1nVJi3bOsgaxfv2VYWq/U/pMuBKIcWDQRbjECrgM4lT y7RejjOrJTMF+k87yR2gMxJH4xJm3ik3jHZZdA03GTPsrnrsenZ5z+cmgu2PAn/XObRm H9xEsAR6gmdZdNqswyHUpNUWfE3jp77HtuEdpQF4qRJ5rRDsxIWe4jhNk3FJmMlvQJk2 t42g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794444490; x=1795049290; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=GvZRz7pMJTTG+4G9hbnVw4N7YnGzeqtiFAM1IrYyU/w=; b=VYWIwU/tC97MF37v/dGOehwEPJecnCtpi/n9qSb2H2mNE5QUUFqWocZn0uoq9PsorI F2BVM9+vjas4x6NxMWoLdxyZ6GToBM0w8UPU2s68uRsLCx7JYurYZ5Qy97/Plc5EiKUN WStOOwFjctoU58Rf4YZn9mgR31y6PmdEv+43aOm4bNHOUb/VxjAonB6AspY048vAPhse V8nvZuiM1tdQ8dXYH1l3M3naOcfeE2nI0P5Taqg3ImnJ0OuvH38Or+h6jy9RW3CHzacQ K08V0GBEtUkShFJVGhqPUoy+17MCjCgcsfprR69r9Wmlfiub1BfvX0gAhDuzyi8JlFUZ 1RmQ== X-Forwarded-Encrypted: i=1; AL+hErEa9+P3aHP9hDcCcXxjyZq95OT0qozjivNyeTH4SPI6C8T0p+HuyamE533smHHxHSoPghTz3W2MXDWzoOI=@vger.kernel.org X-Gm-Message-State: APuU4Z+hqsFTTYhmNePMurxFJOk0MVZLI+/VyB+Oz4QMuRnw4i+6MEt3 NsATCUxTOFIuSq4CKanuADv1XhNAJooFPNNo+hlw/ll5sDnOLe4Mh/nH X-Gm-Gg: AYBFou2esqkZnWmYKzWMSBKUixJw2K3a8wgZQ+/QwAFfhCPBHWT/FCA8m0HT82Nfjku vEwnCLN1a8bLCI+haaGbKQ7yNQlpdvZAPqVGLvo3MDZwn4DUUnYM7aM7oMfv5pBEM2ryaFOAEhJ RXn83FOyd2zc35CA8jWcMWc5iyBTpOrsXBoo2jJAwIwbCL10bCKnw9eErRNCkiyniOUBjdtnU2r W56VbPr1Z79E0y+F8tLv0ISYgG+xzXZeIHliDvsRURhCrNwztlHyL8LhT/gF3Mt1RbE7871DcEO 70WgDD68gQYOtUxM2c0vm1w7oIpSP7PjqCW88+nqE3DmOaXz1UJV6eoEv4tYlYROPZC0UCHUunO +KH3Ps323Xuk+54Io0NgQ/ARPh3VE3vAPpFev8RzYOiEsTvfltu0Q7wggL1qhXdzSWO3wlts2oc gaE7qr59su0xU/RkjWr9YlUgzOsMkYUQO5wuRBxWPD252c0qFmfslEqFMtDfuHxOwanImqdw16f MeoWAzAT49kQmA2lU4I3yiN5zft44gPDUuFJIs7LbvMVU38SX9tnF/PQHfXbZrHu+OLV68BQ9h2 vSW46iIiWzjAWPMLSZ046vZil2KQrO25xzA+7cCgVuG2ZA3UzEQmvnhetHvg63TfZCnPJgnxE3n WaruMDxoaFDDUTMC+jmaquzl5fdOFGBIEqJ64aHvHQTvkNzgnNqIBDfPU0XVysnaZMG2h X-Received: by 2002:a05:7022:1218:b0:1a9:c52f:655c with SMTP id a92af1059eb24-1b3c034f0admr3148386c88.36.1794444489496; Wed, 11 Nov 2026 16:48:09 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-1b3b5b2eeffsm20672163c88.9.2026.11.11.16.48.06 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 11 Nov 2026 16:48:09 -0800 (PST) From: Joonho Lee To: linux-sound@vger.kernel.org Cc: tiwai@suse.com, 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 Subject: [PATCH v27 17/17] ALSA: virtio: preserve playback progress across pause and buffer access Date: Thu, 12 Nov 2026 09:47:04 +0900 Message-ID: <20261112004704.49758-18-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261112004704.49758-1-mynameisjune111@gmail.com> References: <20261112004704.49758-1-mynameisjune111@gmail.com> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit TX completions can arrive after PAUSE closes transfer admission. Preserve that progress and schedule catch-up when the stream resumes or a rejected pause restores RUNNING. Cache device latency in stream-owned state so a late completion does not need to dereference the ALSA runtime. Clear both the cache and runtime delay on prepare and before RESET observes the hardware pointer. A playback pointer callback can also invoke ACK while the core has closed buffer-access admission, after the pointer helper has already accounted for completed hardware bytes. Track that callback context and refill only previously committed data using pcm_indirect.appl_ptr. Keep rejecting other ACKs while the gate is closed, and use the explicit-pointer helper from patch 16 for the refill. If runtime->control is absent, route the condition through the common playback validator and XRUN error path rather than returning before transfer failure handling. Use the existing indirect hw_queue_size to keep outstanding TX below one physical ring: buffer_bytes - period_bytes. Equal modulo cursors then mean no unobserved completion, and a non-interrupt HWSYNC cannot consume a hidden full-ring wrap before deferred period work. Continue using the ordinary ALSA pointer update for silence, drain, stop-threshold and wakeup policy. Check for pending progress or a latched transfer error again in the period worker under the PCM stream lock. A queued notification can outlive a paused RESET or an earlier pointer observation. Calling the period helper without new progress after resume can turn equal cursors into a false wrap when the core has backdated its pointer timestamp. This reduces queued buffering by one period. With two periods, only one period can be queued. Worst-case scheduling tolerance needs qualification. The final candidate was tested on ARM64 QEMU with KASAN and lockdep. RW/MMAP boundary pause, release, HWSYNC/poll, drop and reopen passed with two- and eight-period rings. RW/MMAP also passed accepted RESET from PREPARED followed by DRAIN and reopen, and accepted RESET while RUNNING after the full 2,048-frame buffer became available. A separate paired RW/MMAP gate fixture reproduced the baseline failure. The generic ALSA indirect helper invokes ACK for each pointer callback but masks its internal -EBADFD. Once hw_ptr == hw_io with no completion outstanding, virtio period-work progress suppresses further callbacks, so there is no ACK to retry the refill. Baseline stopped after 3,072 completed bytes with 1,024 bytes in sw_ready. The candidate drained sw_ready and completed 4,096 bytes without any further client ACK. A test-only hook changed appl_ptr to 2,560 while only 2,048 frames were committed; the candidate refilled only committed data. The hostile mutation used a test-only kernel hook because ARM64 control-page mmap is unsupported. KASAN and lockdep had no diagnostics. KCSAN and physical audio were not tested. The tree through patch 15 was inspected but was not run as a separate control. Full-series runtime is not claimed here. Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_pcm.c | 3 ++ sound/virtio/virtio_pcm.h | 13 ++++++++ sound/virtio/virtio_pcm_msg.c | 8 ++--- sound/virtio/virtio_pcm_ops.c | 69 ++++++++++++++++++++++++++++++----------- 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/sound/virtio/virtio_pcm.c b/sound/virtio/virtio_pcm.c --- a/sound/virtio/virtio_pcm.c +++ b/sound/virtio/virtio_pcm.c @@ -338,6 +338,9 @@ } if (vss->direction != SNDRV_PCM_STREAM_CAPTURE) { + /* Ignore stale work after RESET rebases the playback cursors. */ + if (!virtsnd_pcm_playback_progress_pending(vss)) + return; snd_pcm_period_elapsed_under_stream_lock(substream); return; } 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 @@ -53,6 +53,7 @@ * substream operators, including @stopped. * @buffer_bytes: Current buffer size in bytes. * @hw_ptr: Substream hardware pointer value in bytes [0 ... buffer_bytes). + * @playback_latency_bytes: Device delay cached independently of the ALSA runtime. * @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. @@ -84,6 +85,8 @@ * @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. + * @playback_pointer_active: Internal playback ACK context; 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. @@ -113,6 +116,7 @@ bool resetting; size_t buffer_bytes; size_t hw_ptr; + u32 playback_latency_bytes; size_t capture_ready; snd_pcm_uframes_t capture_boundary; u64 capture_copied_total; @@ -140,6 +144,7 @@ bool control_uncertain; bool capture_reset_observing; bool indirect_reset_observing; + bool playback_pointer_active; bool suspended; enum virtio_pcm_pause_restart_state pause_restart_state; struct virtio_pcm_msg **msgs; @@ -179,6 +184,14 @@ return schedule_work(&vss->elapsed_period); } +/* Caller holds the PCM stream lock so RESET cannot rebase these cursors. */ +static inline bool +virtsnd_pcm_playback_progress_pending(struct virtio_pcm_substream *vss) +{ + guard(spinlock_irqsave)(&vss->lock); + return vss->xfer_xrun || vss->hw_ptr != vss->pcm_indirect.hw_io; +} + /* * ALSA may observe a subperiod tail before its period callback. Keep actual * unread occupancy separate from pointer observations and notification state; 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 @@ -1002,12 +1002,10 @@ /* Keep latched errors until prepare establishes a fresh epoch. */ vss->msg_count--; - if (vss->xfer_enabled) { - struct snd_pcm_runtime *runtime = vss->substream->runtime; - - runtime->delay = bytes_to_frames(runtime, latency_bytes); + /* A queued request can outlive the ALSA runtime after hw_free. */ + vss->playback_latency_bytes = latency_bytes; + if (vss->prepared && !vss->preparing) virtsnd_pcm_schedule_period_elapsed(vss); - } if (!vss->msg_count) wake_up_all(&vss->msg_empty); } 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 @@ -770,6 +770,13 @@ memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect)); vss->pcm_indirect.sw_buffer_size = vss->buffer_bytes; vss->pcm_indirect.hw_buffer_size = vss->buffer_bytes; + if (vss->direction == SNDRV_PCM_STREAM_PLAYBACK) { + /* Keep unobserved completions below one physical ring. */ + vss->pcm_indirect.hw_queue_size = vss->buffer_bytes - + snd_pcm_lib_period_bytes(substream); + vss->playback_latency_bytes = 0; + substream->runtime->delay = 0; + } if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) { vss->capture_boundary = substream->runtime->boundary; rc = virtsnd_pcm_capture_refresh_mode_locked(vss, @@ -1197,7 +1204,8 @@ } if (running) { - if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) + if (vss->direction == SNDRV_PCM_STREAM_CAPTURE || + virtsnd_pcm_playback_progress_pending(vss)) virtsnd_pcm_schedule_period_elapsed(vss); return 0; } @@ -1219,7 +1227,8 @@ return -ECANCELED; vss->device_running = true; } - if (!rc && (vss->direction == SNDRV_PCM_STREAM_CAPTURE)) + if (!rc && (vss->direction == SNDRV_PCM_STREAM_CAPTURE || + virtsnd_pcm_playback_progress_pending(vss))) virtsnd_pcm_schedule_period_elapsed(vss); return rc; case SNDRV_PCM_TRIGGER_SUSPEND: @@ -1260,7 +1269,8 @@ virtsnd_pcm_trigger_failed(vss, epoch); } else if (virtsnd_pcm_start_queue(vss, epoch)) { virtsnd_pcm_trigger_failed(vss, epoch); - } else if (vss->direction == SNDRV_PCM_STREAM_CAPTURE) { + } else if (vss->direction == SNDRV_PCM_STREAM_CAPTURE || + virtsnd_pcm_playback_progress_pending(vss)) { virtsnd_pcm_schedule_period_elapsed(vss); } } @@ -1593,6 +1603,7 @@ { struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t position; size_t hw_ptr; bool paused_pointer = false; bool defer_work = false; @@ -1610,6 +1621,7 @@ } if (!paused_pointer) hw_ptr = vss->hw_ptr; + runtime->delay = bytes_to_frames(runtime, vss->playback_latency_bytes); } if (paused_pointer) { if (defer_work) @@ -1617,8 +1629,11 @@ return virtsnd_pcm_cached_pointer(runtime); } - return snd_pcm_indirect_playback_pointer(substream, - &vss->pcm_indirect, hw_ptr); + vss->playback_pointer_active = true; + position = snd_pcm_indirect_playback_pointer(substream, + &vss->pcm_indirect, hw_ptr); + vss->playback_pointer_active = false; + return position; } /* Caller holds vss->lock. */ @@ -1731,9 +1746,9 @@ /* Caller holds @vss->lock and the PCM stream lock. */ static int virtsnd_pcm_playback_ack_validate(struct virtio_pcm_substream *vss, struct snd_pcm_runtime *runtime, - const struct snd_pcm_indirect *rec) -{ - snd_pcm_uframes_t appl_ptr; + const struct snd_pcm_indirect *rec, + snd_pcm_uframes_t appl_ptr) +{ snd_pcm_uframes_t appl_diff; snd_pcm_uframes_t signed_max; snd_pcm_uframes_t back; @@ -1747,7 +1762,7 @@ unsigned int hw_queue_size; if (!runtime->control || !runtime->boundary || - runtime->control->appl_ptr >= runtime->boundary || + appl_ptr >= runtime->boundary || rec->appl_ptr >= runtime->boundary) return -EPIPE; @@ -1762,7 +1777,6 @@ return -EPIPE; /* Match the indirect helper's appl_ptr boundary-wrap calculation. */ - appl_ptr = runtime->control->appl_ptr; if (appl_ptr >= rec->appl_ptr) { appl_diff = appl_ptr - rec->appl_ptr; } else { @@ -1854,7 +1868,8 @@ return -EPIPE; } -static int virtsnd_pcm_pb_ack_locked(struct snd_pcm_substream *substream) +static int virtsnd_pcm_pb_ack_locked(struct snd_pcm_substream *substream, + snd_pcm_uframes_t appl_ptr) { struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; @@ -1878,13 +1893,13 @@ return -EBADFD; transfer.indirect = vss->pcm_indirect; rc = virtsnd_pcm_playback_ack_validate(vss, runtime, - &transfer.indirect); + &transfer.indirect, appl_ptr); if (rc == -EBADFD) return -EBADFD; if (!rc) - rc = snd_pcm_indirect_playback_transfer(substream, - &transfer.indirect, - virtsnd_pcm_trans_copy); + rc = snd_pcm_indirect_playback_transfer_appl_ptr( + substream, &transfer.indirect, + virtsnd_pcm_trans_copy, appl_ptr); } if (!rc) rc = transfer.error; @@ -1923,8 +1938,6 @@ case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: case SNDRV_PCM_STATE_DRAINING: - if (atomic_read(&substream->runtime->buffer_accessing) < 0) - return -EBADFD; return 0; default: return -EBADFD; @@ -1935,6 +1948,8 @@ static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr; int rc; if (vss->indirect_reset_observing) @@ -1942,7 +1957,21 @@ rc = virtsnd_pcm_ack_state(substream); if (rc) return rc; - return virtsnd_pcm_pb_ack_locked(substream); + if (atomic_read(&runtime->buffer_accessing) < 0) { + if (!vss->playback_pointer_active) + return -EBADFD; + /* + * Pointer accounting already consumed completed hardware bytes. + * Refill committed data without admitting a new application + * pointer while a buffer operation has closed ACK admission. + */ + appl_ptr = vss->pcm_indirect.appl_ptr; + } else if (runtime->control) { + appl_ptr = READ_ONCE(runtime->control->appl_ptr); + } else { + appl_ptr = 0; + } + return virtsnd_pcm_pb_ack_locked(substream, appl_ptr); } static int virtsnd_pcm_capture_ack(struct virtio_pcm_substream *vss, @@ -2073,6 +2102,8 @@ rc = virtsnd_pcm_ack_state(substream); if (rc) return rc; + if (atomic_read(&substream->runtime->buffer_accessing) < 0) + return -EBADFD; return virtsnd_pcm_cp_ack_locked(substream); } @@ -2213,6 +2244,8 @@ guard(spinlock)(&vss->lock); if (vss->msg_count) return -EBUSY; + vss->playback_latency_bytes = 0; + runtime->delay = 0; } vss->indirect_reset_observing = true; rc = snd_pcm_lib_reset(substream);