From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.214.177 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794464757; b=ViuD6fuSHZtN7dTLNbVRaYyke13zBhw3xD6892rNqh3idGTkOzhDVLtGRSfJVf6j67sv VbtXRHktLJukFR+IjeaY5Dpj+hDSjO6IV2DdLXQ9KFLYAn5SzvVNm3xbjypm4NZOulZ2h +dmSDsUwEbPNO8CIMAwPu6147GpShh3tDuwtrgP+9nLoGCzL7ri9nyAyJnBMYappDLYzU 7L1nfq/PdSSVuj1fwCMp1FABTs4IOlFH5NaNoSuO9+6vNFljP0lOG37iJOrrGN3YTn3+p F3X5wJdyM6izjO5DE4ISeKfIwOEdjglWY5QzAhsKBzvhDd9pAe0UcvHrB4BZ3uGjGYw== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794464757; 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=0Lrn8/IzG4OPTLUo+SJwEmycT247APyWguHaVOjjaKQ=; b=nehpLtzlUWnTKADjyXLNRCJTBcIIXmV+R/GQWd8j8I+FUJxmNU0PzDG5sXr5T0zKRGwJ lCyDVYN5VKZajzYeKvnAj0KFf8SWW0lE+aowkt8LfLo6tRgU5Id1L+4lCx7Ya6l+/DINI xMxpS0VMUH+JbTL0ftCP6CWfTUUKFKb4bV63wB7o/AcceOjW+YhF4ru9LjANNJtdlihG+ ZM8N9vzamo1I5QRaSRG8LJxK8VpOj9S+12E6Mr8mHZ8uE/jLxUE0pD8PvoAHrcqB/vxzE w2TfpHbUEzvvVy/NBjJjnDKf4VST9k7OYDXxqqewMoEDdsbCtMj4Cnx0GCgDURFcq4w== 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=GhUqZ9ha; arc=none smtp.remote-ip=209.85.214.177 Received: from mail-pl1-f177.google.com (mail-pl1-f177.google.com [209.85.214.177]) (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 CCD7A606A8A3 for ; Thu, 12 Nov 2026 06:25:56 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org CCD7A606A8A3 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=GhUqZ9ha Received: by mail-pl1-f177.google.com with SMTP id d9443c01a7336-2e96502e447so32233325ad.1 for ; Wed, 11 Nov 2026 22:25:56 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1794464756; x=1795069556; 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=0Lrn8/IzG4OPTLUo+SJwEmycT247APyWguHaVOjjaKQ=; b=GhUqZ9haNHgqG19bv1O4RvaGQz3IC3/C/oVeAEpm0LaGjZkz8yeyCWQqV0/InY0q/o yzW2u9uddjiqrq12k6OR4um6L1ElxXM4QeSvx+/sXFhG5i0vo/hzMwaX987rGBQn0/4q TU/vENuUNRIKZg7V0VojwxyA9gwukKrG+xR5e4TQAnaELhRq/2JjGI0OzG7yDRCMWwwA riPJSSstR3+jerR4mEcU980n7zY0IvebrMNNgSRAKolkKlD0mjqoKCNvE9YVmZr+jnVW FTmy38r5Dk3rZRK3VyAC1W05FFJ3xx8wTQYPX12I5rS/qpbRAXqYkS6Hrkk/L0g0qCou WYEQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794464756; x=1795069556; 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=0Lrn8/IzG4OPTLUo+SJwEmycT247APyWguHaVOjjaKQ=; b=Fl1rejUhQXSNQq12oyNGoFPjQq/KOlyTyw2rT/Vd9LHK6YjtpmwpVPRbifnJHdcTWY CWUn0cHDlCmwcqjcMdaCGwrFR2Crs1V25gDQ+ml1LEy3Q+LcTtmcf1Pxr0ja8X/8+lrm vmAj/cTgw95P0lJG1KDdlSmOSXrfIXLdvDteeGLKeDzDXcUZDjxZdfHj0B8VNh6s/JBU iMaXNwuk567wtvgB+UNVT+bno8ed9t7icZto+H8O+4dmHCiisZL1tjo1fg/gvDMOZboV cy42KR3ilSEpz7fDQEPesScFiDeGtdt7PmLp9lFLVldmIrKcLC+AqPcfFGU/ll0mOMGQ afpQ== X-Forwarded-Encrypted: i=1; AL+hErErFq0fuy2xwLy9mdBmSSW4yb5sHVvRlekGRYtZfUdZJuMUGsuTnUc3AFGwgO+5PkBOMJR7ZkECSxk=@vger.kernel.org X-Gm-Message-State: APuU4Z8tQyfV7YdyT7LFiGhkKggI5Awq+A/msNfl4kSABdRjfWcKwCEO mMEHVQiVIlZ4IB9uyMtuY86+njabISWysP3f1FyPK4puuIiQ42OXinId X-Gm-Gg: AYBFou1X0jqlXZbc9GdqyPwGyHfnediP14kUIk4t2kaqFxkabLEp4+ZhORkS2aafVkZ +RClDpWszQRLl4QIYcmSQGniG/bYh1qifeSro/+y+9o8vTZWNSqT6XIgTuntuaSzR9uMu2Ocho5 Zux/Kp5O8dqyklb4QNGUCWzt89wenHRmh7tuXJBzocAwA0wOUpY0NfX3YS2EU1ajNy/qJTyGImj 8U9qo0NwLp9VtHBm8FSUBVYP4SdTnXolTUNkLpkALej40coC6err+WHUqMHXQEV89ypUf/eRXuM G7ZVuLHVBkdvd1uilKQmFeHBYiVLnis9OTno4tv4mZ4C146ZK3narY0JDtpcEdRRHiEI1+8l1ee ibbqWWYPZC9RPwk+DVQcm1NVLCYepuIrMMtm7LS5f35D7F+erJsAtgNYmz0R8l57xa46Bu5YzgQ HkAw2WE0kHTHSGshC4YAjiEeNMZhDzA5aJEru6ZljqYh6Zj6eNRxgJxOVCHmtWMBvE7qM7/W8Kq wSAaO+bEf6EzZdynfRonS6T3gSdqrvNJ14a/6RFpZvMO6ohXCEC4unLj0AYPo5587aAFLFRCuTI 2+gmE/aH1diFuKqhPm0YU6Do+mlVm4nneZiJRqVN4fkHRt81e2MefTABZpiBFv6rZMeHsqGJNMX 3Rz9aQMZEJZh1VJNhVay9gT+wKi2E/5/1vatSY0GJTpo5HtY= X-Received: by 2002:a17:903:3c68:b0:2ed:12a1:c4aa with SMTP id d9443c01a7336-2edd77874b6mr52224775ad.31.1794464756137; Wed, 11 Nov 2026 22:25:56 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2edb6a3e61bsm26979965ad.11.2026.11.11.22.25.52 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 11 Nov 2026 22:25:55 -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 v30 17/17] ALSA: virtio: preserve playback progress across pause and buffer access Date: Thu, 12 Nov 2026 15:24:46 +0900 Message-ID: <20261112062446.159-18-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261112062446.159-1-mynameisjune111@gmail.com> References: <20261112062446.159-1-mynameisjune111@gmail.com> X-Mailing-List: linux-doc@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 snd_pcm_indirect_playback_transfer_appl_ptr() 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. 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 reported no diagnostics. 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 @@ -1033,12 +1033,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, @@ -1200,7 +1207,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; } @@ -1222,7 +1230,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: @@ -1272,7 +1281,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); } } @@ -1605,6 +1615,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; @@ -1622,6 +1633,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) @@ -1629,8 +1641,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. */ @@ -1745,9 +1760,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; @@ -1761,7 +1776,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; @@ -1776,7 +1791,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 { @@ -1868,7 +1882,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; @@ -1892,13 +1907,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; @@ -1937,8 +1952,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; @@ -1949,6 +1962,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) @@ -1956,7 +1971,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, @@ -2087,6 +2116,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); } @@ -2229,6 +2260,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);