From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=74.125.82.47 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794461755; b=IC1/1Qd2mpaEvhMAoduiMFbcuOWdZTsEPH7rkVx0kCe942KwMwYSGXbn2O2r7KIhbO4Z KCw+Ioqi9AOSGcA/63neuMH/F2HB5d0L63Y2bjVHQIM/y2eWPAei+kvGejWaxbx2nofAt BqQiIUMoIl21gT6S5YdRGHz+VkM9xJ7J4y7CVp1K7hKsX83XcGZcVvVYt7dkfvznOIrH5 LLbWi/2hM4QnWQzIOOABAXYN686Lpt1p/S22osgJxbnslP03JxN+276LiQcLadTQw9vSh SxbBU6J6TT1tBySXeeYktlPKr4Nx0vxvn3Mm5B3ClvpltIQNlCSKGxHz6ksPvDEudjA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794461755; 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=STeEkttv3YJNNONebll50Y3FQZPr4qX7dcO5WrAPVr0=; b=VqsJeEomXHjgn1C3STCFWhvrX8eEADJJWhHEe7kkEngtYJPXm0zWNzQ7xrQIYDNw7yDl Cfn3XxuvBfZhVCNFCZykaWs14Gu3YbUwmeEi1lIOzOIHJDoU/6s+fV0W0xP4+hv1Yt0zt HPnDQR2w5yXX8BKhxA8UrkNhs1TGFjy0ofIzCn+0ISSPqeMG7ddnGfpv5g3dnjuIUAd3T /3o1RkJNHqe8bSllaBKumkq5Zy2GaStDPoo2GSdozz0to9oTDVF45DzvnPPkfUzw8BR0i oHvLJEHvPrEwA9AeyElZ/hlCFEHZvQPiCYGvbtBfDquS4S9FyRr8mvlHbIZ1/vzXyHg== 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=dTD9mcL2; arc=none smtp.remote-ip=74.125.82.47 Received: from mail-dl1-f47.google.com (mail-dl1-f47.google.com [74.125.82.47]) (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 2B68960A6F6F for ; Thu, 12 Nov 2026 05:35:55 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 2B68960A6F6F 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=dTD9mcL2 Received: by mail-dl1-f47.google.com with SMTP id a92af1059eb24-1aefd1bf74aso425016c88.1 for ; Wed, 11 Nov 2026 21:35:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1794461754; x=1795066554; 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=STeEkttv3YJNNONebll50Y3FQZPr4qX7dcO5WrAPVr0=; b=dTD9mcL2E+aDZRfUFaxjhSIezw3M12F+t8GPXLDQyXqw+71PWQwPknPMRRqjt4kaS0 PHmAxR+E+8uRlNd3lOm2cgA6fj+vysay9S42kFGk6ppNqShxAgLzCQTPY2G7+T4LKY74 bMF0fcJbvch2XC2WdtCf9L8ZgUrenrtq0a29XzImLTedwIIEvOww0M5B9Ar8Db9sDQEe 6eRFWH+xCIzFOcp6pEHVlvLFDgOzw+9eUrV0ShBB8KAUv4UqfNnT1gIYgiKadOtKYtJQ XNr9Ar3DgDey2c6k5R9svJ/jI/Vocs40rtLkTDBmolNTR50tfvWobJd4aMP23tVlOUc7 KlQQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794461754; x=1795066554; 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=STeEkttv3YJNNONebll50Y3FQZPr4qX7dcO5WrAPVr0=; b=NR1hk5wZqUf6lWNO+QeyGut7qckO+X9TKLnNRhjDEYX8ugX9RRZ+Izr9AatBWmIWYO dGUb881V9dgUjOcI94uUSu3Ce22/+a1mWL24Ae1egX2mZEMu2FF8RSmAtr3/s7lc5ciS C60hjJVHP3Od8AMKdrfVWGcnPyObPFJSkJyNgaC1PvkSKlu7AXkpr+JrTI3Xck6wzGAt bGEA/feGFKSkjybECrN1y+pFNfSzskYDf3NXD+YzCgADFKCB1yqXfkRHmN69XWdgLx65 lGwmWqeEdqPIVAknNRdR4OIAgCFmJAP7f4FguJSjr+02UWVjcqfS5Qy1hbYiA9T0I2+m R2oQ== X-Forwarded-Encrypted: i=1; AL+hErHLH6CZ0bTLJtfls9Zn93GOduCO5KVKZ8r0jYGDJwmojOvdX3DvT9x5lqDw9ySl/RayaDup4zMUKt37sco=@vger.kernel.org X-Gm-Message-State: APuU4Z+b1/b8pkNPHV+J0WCZMbcfqdFA/8J35QUEpCXJqjNdVGYIsqg5 2AP/MKMCUtP+PBzWyH+7c38j8M4nGZQc03etTkeWk0WrZq+wNJ/00e2C6pskqw== X-Gm-Gg: AYBFou1ulmhmeRlpkiMxmTiMb4hBMJ8H/PJmnfX9I4YKjmhmv6PnN0DtVwjkoB4UMCF Mnp8I74noCG+3JpV1HPk6HfgHn+wYPqE8bYehIPW8MFcptxfs43AwdP6oy2zoAUN1iBw2teHZgA TlOTOVgVBHIxv2BNpUU52MDLVk2u0jKlB5yGSq27cLBiM9mgjGaF3VAp36v+XxQrtsMl9/TWe3h lyqlLL8GV5sNZ8SWONqVv8Wv2/qKWb9FwrHFIio4XCvgDfp32mcaA25opuZ+NL6zuoH3erpBN4i /Zl8s8ctsLCBOD6Vk/Ymr6UWAaARpbxpIH3myVAq1SuOkrJrQtd4CTZMLQZY2vvcuBRg5W5vB5F E8sT2GEbDm2CYq2D6jUH8DVlP+2C3awjAzAGSfnjAc+9dHxWVaDEYWZtiAbCgT0C1dWSlssh2WB OTez3yXic8mcBhBW1nrIyleGxsgKuUIHWRo2At6p8i42M4lCzWx9uERG/Y1IBDZmGy0jvV/dfhx gMk4/YR7mKs/8rslSkLrdmaoeJ9GwwnRDrtlJafRKWqhae+VV/cEAIuGmXsuFVbaH1aa5TbeVWa Y309u8oNXG73fsbB8EG8HaBFPXGEl3MZS0826DL3rynhNabarR4DeUjDC3yiQGhieSLxMLjlZ3E LjJ3qsqU9Jb61cO46inzXSzOdQ2lhUVRZ9QZxUbm7M7GH1Vs= X-Received: by 2002:a05:7022:b0c5:b0:181:6b8:31f1 with SMTP id a92af1059eb24-1b4f5f3475fmr1360633c88.10.1794461754170; Wed, 11 Nov 2026 21:35:54 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-1b3b5890c0esm21953240c88.1.2026.11.11.21.35.50 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 11 Nov 2026 21:35:53 -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 v29 17/17] ALSA: virtio: preserve playback progress across pause and buffer access Date: Thu, 12 Nov 2026 14:34:46 +0900 Message-ID: <20261112053446.73504-18-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261112053446.73504-1-mynameisjune111@gmail.com> References: <20261112053446.73504-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 @@ -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);