From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.210.171 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794466240; b=vXjBUGDoBj2WR5p+f6vXvK5ykw68WmDTSkzZ7srD16Tkh4uhnGxz/0aNQqbii4u/XxRH qo9qNdobZzXka5sdfTS8fZwX26eVDX5IXZ8bsETSTAikhc/mU5FFs/0OvKJnv90OZwe5+ xUnx7F8nTVIWp3cv8wr7sGUfaLJVuXJ26ErWmGF8eu/k4Ltj2EYe//8rnlm0QcFmWL1H3 i5PJRCzXidKwZiOIswsq4VKCjjS3TTWaK57fMLw2dRRBM65Qrp+nUnI+noSsqIp47sSXk hbunD5YGyYv3SPmF3TBzWh24zzyMBAuYciRI/rD/dSmrmrVS4K+Wu5cQm8X4yVktG0A== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794466240; 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=uPxW1S4UHNDLdnByVIOJaevcL/z+avNTgvw30iqDb9iiYlpHrGIYCZDTN+Pv/acGBLFo gzbol6cIGDINor5HjVV2Z/fp0utszM9CzkyFrIChJQBrUxRsYSLHX6W1SD40JU03msGEr A3NjzvnvyDZGhGgyUXD3rG6nieWqk0MvERPL8+MpkbXfk+OKXkFVfmmJRre45tXsFTexQ fkAQzBqQaQGPGg+FJYw1Q1/OV86Nhrtnun/RQ5/2vF/1sAIckCfxB34HjRppwZafbeiFZ RRrAcxJ0RHx7M3aX9Ksw0+h1rFaVQ1VtDz9IsT7vy0owevRgsEVLs0DjgCTDhhb5t7Q== 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=j3omvLeD; arc=none smtp.remote-ip=209.85.210.171 Received: from mail-pf1-f171.google.com (mail-pf1-f171.google.com [209.85.210.171]) (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 64650606AE55 for ; Thu, 12 Nov 2026 06:50:40 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 64650606AE55 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=j3omvLeD Received: by mail-pf1-f171.google.com with SMTP id d2e1a72fcca58-8b5c3288e6cso1730716b3a.3 for ; Wed, 11 Nov 2026 22:50:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1794466240; x=1795071040; 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=j3omvLeDtUJWcuC+cFDj3ei/lnsIk21zbKKMIBrSpX1TY8r2J5rhpPHtvJEib8JYRc Z+TYiRjY2kgryd543lCAOCznusf34q7NZWNVoArQg6ZnTdAVBvoEHU55xZ9HpY0hKK1U GLmOFz+s2doQNQ2fL1S0WvqhyIY9WTtnCExWgQJ1kTUng4mtR6aNuZmXsGcs+VHHD0tj b/31GSFnTGGV36wL+CNXnO6eFrtNe1D7hO/IEVxm2r4opKTV0G2qs0iDJNmpjiT773pZ xb8O+GfNiLpZm3Un2mAAsfh+oeWEXGiIbbm38SWPrNj8zo1IQqDxPLEXB7wKK3BIiPjl X6dw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794466240; x=1795071040; 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=11JntM3tTGh869mkTkFmtpvSHhO9MxU+E49OMQYCon6gjg7D8DF1+CCixTos3V4qTs vAoPG/DN7q4LF19J5inKDvKBbV66G90xzFEE4KaFASLaIBNdi0CpPQ9f4mVV0+EkZ48E e9VrX/R60YmHab8v8ek+a4x/q02R3TfOmAmgJVbgiblNAVkLqrRQK5wn3n4mJpjrqE0V XYQoOk0FABL1vX/AH0uP01Gw2BI5FmL2cBn6ehtC0ewsc+tmooXAHOFq/CS8qXo4KPpD I/2S+yOmTZfF+e3jLcUo+NqpTl0sWGDNitJocU9WPHsVeG6Z7d+0Qbj1Uj79C833p65E xq/g== X-Forwarded-Encrypted: i=1; AL+hErGo9TiFsv2wN1aItHP5j6r/ae1kDGlNoRVZcCycJRX3dshqYPzBFkLLcqiLpKdHKf+vauwSoan01DGnRbI=@vger.kernel.org X-Gm-Message-State: APuU4Z9bysRQe+K5sAE3iDCv1a9DCs7SFfeX05UDqNU/QtoihnwDPISR v0iO93WUnEKZ2npqEdYx6dLFVcfj9xjXV3PhxDdCdBM7OwXxe0MAghzJ X-Gm-Gg: AYBFou0M9LkGEtbOZpVW64F9uLNtG6vwJpbXHu2L30fxsNPKGXlCtoQtBUsF8c/sktd ndPef5tdaT+05eNw8qlUNTBfdfyM+40hMdQD5tIrRDlI89VGVpn/HweZwQ+0gweO52xNdaH3odg YnwihyXm9HMwVYB1Yx8L+o/5xo+nki96ruC4YespX1dBvicDHqD/eOr3drrm44gnMqOMBfVJ57o vjr1NniCn+c1rw6b2X/loLpkDqKaQ5V5WE36zZaySzJs2vhVs17hL8O8KeUX5E6Hq4nH1ccHvsz saFpvM7x0+tb8eLp5PKUJINpeAfjkiczT/CoeJRsgo3R2eIQSSFyRGtOKw++ENWw+ftEmimj8fk TBYGPp7TuDh5X4QorHvRjLxhmXh89kzcqVvQsEoYZRNZVmDWZ2A3sxrJefsJDHYl+K/1JkcyM+6 t/6PfzGbvM0vRPDRipPT3KmtmYcL1ASpaEKyKwX08tsKUHWEZ2u7OgCg/7/CVUSH7b39eqNrqPn toCb1j4340WL36UMSdcXY+F1zo6duHbq9KHkmOphWTfnG+NfGMut6yAsBWYhUy1RAsDfoChOVp4 1VVEsdnMlshD5GnsEfKjs6AEYcnbfHImMK597OPmt+dRs9qVjhqj+OoFm8I1tTgb8+lFV0onjuo czlM3HcfYqH3yNIXcjwGhe0IN98iGP5IZxwA/+xUMDFXPk3hL X-Received: by 2002:a05:6a00:390b:b0:8a0:7677:c8c9 with SMTP id d2e1a72fcca58-8bc1d81d092mr5076970b3a.51.1794466239616; Wed, 11 Nov 2026 22:50:39 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-8baab1228e2sm3097511b3a.41.2026.11.11.22.50.36 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 11 Nov 2026 22:50:39 -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 v31 17/17] ALSA: virtio: preserve playback progress across pause and buffer access Date: Thu, 12 Nov 2026 15:49:29 +0900 Message-ID: <20261112064929.11211-18-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261112064929.11211-1-mynameisjune111@gmail.com> References: <20261112064929.11211-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 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);