From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.215.171 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845912; b=hayqOBq+ovdj9nh51Ch7pjnoV2ERMJxOZVNPjw44C8vJNM3vRNTOMm3Oy0PQqPOke2CN 1bYSqlizTQXNbL0lLRmur4Dxw4IuHzXu5JN7LhRokbt+3S1TSTVzz+0NhnMUV4ujoryCw Ps6D1fWe10APx5XYB8y+u9D+KT1FhQfzi3NKgcTMo4ixZLvhD9vWVamQvjIMe1cgt6XAg jENYXIQKm+RV+ID2EXm021iS3KCQdNoa5zRrtaZLBbJmSroBgcxZuLW/jnelQwRpaXIAo iVdAZLMgJzCpSLvdVvvxI9bfXH9xmDPcfLL5jkFhDg/0Ifu7TNISL0BlSABaZgyPkfg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845912; 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=BWMu6D/YafB12/Y+mikOxNZXkHRbVUuoM1lrNSU8E/Y=; b=Pbfo6rKqHQJmoGw1b6BJNfy6I8kVn/rPvckABaFnWdshXsZ+hhFrlIf/TB7bDjiweoF9 eZcYUfO4xLQL0K3AE4k8SqD5uaDJb4WdCqMKQLFxG6ahZbC0CG7QyKXoIFbLPkBFJOEsF MVKhPP21PHGYGA4FRuNGKoA+qgb0bgx4MkHOlaH1d5AcZcPj4n67CSj4dSXtB+ken2DoK EOPLAU4qfWVdcw9CLDJr+/k19uLW59ByE6UdWXzlfW1oSncrWUNrnpwA9Rg78Q7Qi6JZ5 boq0I0UDY7/1c+Jh9MAkuVjnxBhAo/FbGLOGHDM3npVvCYIdYlv0nRi4M6s+GbI2GLg== 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="ERWZzjP/"; arc=none smtp.remote-ip=209.85.215.171 Received: from mail-pg1-f171.google.com (mail-pg1-f171.google.com [209.85.215.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 8E844606A32F for ; Thu, 5 Nov 2026 02:31:51 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 8E844606A32F 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=ERWZzjP/ Received: by mail-pg1-f171.google.com with SMTP id 41be03b00d2f7-cdab8873d9dso586729a12.1 for ; Wed, 04 Nov 2026 18:31:51 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845911; x=1794450711; 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=BWMu6D/YafB12/Y+mikOxNZXkHRbVUuoM1lrNSU8E/Y=; b=ERWZzjP/Jgq+vdv/PRd4mYf6G0EIVbYd65eH1Xsd5vkwNTW/SwBNjvM9nRTjZq5xGx pNMlhQO8KmtfdU6vuY3zT9PAmamJe4yREYs7MNabfqW3IHknPM/oeb/D/Pcy573qB+xV 7+39y+aYqlCIAToZ9bjPxA/i3AoOZ6TzQ/aWdY0XH2f9NKv8qUwHYpJFqutcYAuJj7u/ 4S6+d/if2rnINHVd//HZexiH5bWOECeG6xh5fHzoFA42Xpdv19icKnVyllQMbyejOvbY SgQ2u96rwzE/FKBgMBsZwBj7SY+yA+hSL1chQ5wkC/+lu703bezXl0//LLyinjn1Vazi axZQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845911; x=1794450711; 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=BWMu6D/YafB12/Y+mikOxNZXkHRbVUuoM1lrNSU8E/Y=; b=pNOJ9HExLAEBbX6S7oN/WoBLm6k5YxlZ4hr6RI2zd0EWGtRAdutNcOxkV4c0D4fMo7 rrH0ztlx22ez6oxBCNptR63dVO1acAjRi98rMavDyg+7LEI2T1/cRFwpvBYry17pZ0x/ LQXvR/VSQIf68poBoyE+H/LEvLBivi8wS6xiX9pKnww1HmaQu1daXQwTLZQR19CSY0kg B7BvHu18Wn6KozUXtP0TZPzpLuTnSu3q5zjvqkHlNq2hkwfRDoEljZu/MW3hK82oKoN4 hXABwRXnl8KXQTvyEn4HfttmZCyaEyAIZoxJVCMdM8Lr8TOCQ0po1JkiphGij8+Oq9+o PdPQ== X-Forwarded-Encrypted: i=1; AKwUvBxAVjEh1NIFLLU83SLnXkbFbbzs41vfc2hb0vXRkgA7Ct8REPTQfAkYVdy6dOBAoVXdRBzmrQD6jSMQdoI=@vger.kernel.org X-Gm-Message-State: APuU4Z+gUA3jCFk3xDrhaO+i0OZq+WvI5y1jb4CKzJgp9H9aLxkCr+Ry BiE3B/m2ow6NW+OltAFVkIKWvP9eii44K5SpeX2d5dQGdsYIBbFX8Kh0 X-Gm-Gg: AYBFou2k9j87kgrjzpetc81OyP8Ni172qooQAfMTi91iNdnkHbKMSbBNFiQd5z016MH ew0zY7bXprgnrtPysH2hZ0D+rnfPEy25xViOEgQYMZVx22x/RbvDzd45Zd7w4fpX8Q0X4uboG01 7GzOaYIrYBluzERBcIU6O1GBoGFN/swAjkk6lsHqahpQSRBXNBpOCcT3aSJZXULQSxG+M9pBkDj Cyc1uoch6ObG+CCGs210BXe3h72OjNLJZZGuze+j/1UfWIgiPowLl1tOcvaVpIdy2pgXcok6VPt TFQsjaSfTxn1gJu6A2T1RgOJ/EWd+9YhWQCB2uSy/oIRhKlKXAPrjaAApOX6R+oDuAzfjb8PK+w lwkRfYoZ5g/KSDxRLnjhBSsgDAAqUiVgjZNA0UlpUsRrXYBPsSwDupb1OomT6FTeKMLqjsUg2Oj 1fKBik4Z+h8p+0Gucq9umTBmkPN6W07KWjVXvwEdw+TTwU7OXrPmNDNd4G4TcPsE4RIMZDIf4/E bANKHgdH1X3BLx/VwmFnTVI0eEcTwDXrRjIhHRafNr7E1xMNH2/Oauq25qHrkMsTMEGyZsE48FZ nNmT18s3RQxR0EcpzcNIvqI4GUGszSGidIwuCD+y6O9xNQvzj5b/GXiNle/C+g4ZwFWdAelxrJ4 z3Q8kxVQQT79FhrAaqPQ1MUD1wejZtm+qNl2nKUrZRQyLqQU= X-Received: by 2002:a17:90b:3c08:b0:3ab:17b6:9a93 with SMTP id 98e67ed59e1d1-3ae897a64b7mr15530386a91.52.1793845910857; Wed, 04 Nov 2026 18:31:50 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.31.47 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:31:50 -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 v16 01/12] ALSA: pcm: mark stop-threshold accesses for capture policy sampling Date: Thu, 5 Nov 2026 11:31:29 +0900 Message-ID: <20261105023140.18373-2-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" The staged virtio-snd capture path needs to sample stop_threshold from completion context to distinguish free-running capture from bounded capture. Software parameters can update this field while capture runs. Mark the runtime stop-threshold assignments with WRITE_ONCE and the proc status read with READ_ONCE. This pairs the assignments with the driver's READ_ONCE policy sampling in patch 9. These annotations do not provide a transaction for the entire sw_params update. The driver serializes its cached policy and accounting with its own substream lock. Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/core/oss/pcm_oss.c | 2 +- sound/core/pcm.c | 3 ++- sound/core/pcm_native.c | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -2958,7 +2958,7 @@ runtime->oss.mmap_bytes); #endif /* In mmap mode we never stop */ - runtime->stop_threshold =3D runtime->boundary; + WRITE_ONCE(runtime->stop_threshold, runtime->boundary); =20 return 0; } diff --git a/sound/core/pcm.c b/sound/core/pcm.c --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -422,7 +422,8 @@ snd_iprintf(buffer, "period_step: %u\n", runtime->period_step); snd_iprintf(buffer, "avail_min: %lu\n", runtime->control->avail_min); snd_iprintf(buffer, "start_threshold: %lu\n", runtime->start_threshold); - snd_iprintf(buffer, "stop_threshold: %lu\n", runtime->stop_threshold); + snd_iprintf(buffer, "stop_threshold: %lu\n", + READ_ONCE(runtime->stop_threshold)); snd_iprintf(buffer, "silence_threshold: %lu\n", runtime->silence_threshol= d); snd_iprintf(buffer, "silence_size: %lu\n", runtime->silence_size); snd_iprintf(buffer, "boundary: %lu\n", runtime->boundary); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -871,7 +871,7 @@ runtime->period_step =3D 1; runtime->control->avail_min =3D runtime->period_size; runtime->start_threshold =3D 1; - runtime->stop_threshold =3D runtime->buffer_size; + WRITE_ONCE(runtime->stop_threshold, runtime->buffer_size); runtime->silence_threshold =3D 0; runtime->silence_size =3D 0; runtime->boundary =3D runtime->buffer_size; @@ -1062,7 +1062,8 @@ runtime->period_step =3D params->period_step; runtime->control->avail_min =3D params->avail_min; runtime->start_threshold =3D params->start_threshold; - runtime->stop_threshold =3D params->stop_threshold; + /* Drivers may sample the stop policy from completion context. */ + WRITE_ONCE(runtime->stop_threshold, params->stop_threshold); runtime->silence_threshold =3D params->silence_threshold; runtime->silence_size =3D params->silence_size; params->boundary =3D runtime->boundary; From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.216.49 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845916; b=GznPwc01I06LU6LfAMqU03E/3Ch1+se80foncmkCe28Ykl+qkfcV88qRrZUcFX3oUvf6 rxcHuVBbBmS5bFA3cIBxXPvptL2I5L94vN9yM6VrgT64kxDQLV1gVrIUFLeYMO+iqhQeL YM1TI3VctrSDSpF7BE7b0z0DanprRs919xg5xH3IiQoZZ7By78p509Tzecs9LRjRNDjNj Go5Wr6IsjqWqDQUCM+FLznnWCmRK5yJtworZFYRwpeEDfo1Rl6zFkF1+733uA7h+iSRTA CQ+hE37P+E5WhSNQ89Nq8Veqntot/MU5uvu2N7tNRYzhLVKJOtZ4huVX39Uzzh8kXQw== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845916; 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=8Unix0qGxH+DyR8DqZn4KwW8PFLSBTwhIEWxnFX5DnY=; b=Kbb2Oj44aOjOSrSiSHpr6gzXdLBU7KIbHZQbIYWkaQD5kOd3FCcj7ypmg0ytNSsfKq+j aYfZKQXjvNvY0bpUPROVs1WLHQWBrIbGw5S29M05gf20Cjjg5z8xiwvASxR6RERogTr5D /Pah+ARzzW3DAqeBblwgtLnIzyrA04Z/67ua7lFoZrpHyh/GvyPA86DBjpu8PxTesbXQl PpKOoJ8fxe3WQ0aiUCPCDps4uKyGiH7wFX++AfjeRSXioDRLpQ28ZlA2aap1CL5pZcyAC yS0vES9eepGW+xVirgo5U2CqJ4XhBdnK9C7Bq6gs/t74vaUUUzYvQxga6vlkL1L11+Q== 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="R/SGdesM"; arc=none smtp.remote-ip=209.85.216.49 Received: from mail-pj1-f49.google.com (mail-pj1-f49.google.com [209.85.216.49]) (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 5A5A0606D803 for ; Thu, 5 Nov 2026 02:31:55 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 5A5A0606D803 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=R/SGdesM Received: by mail-pj1-f49.google.com with SMTP id 98e67ed59e1d1-3b0b3438540so255979a91.0 for ; Wed, 04 Nov 2026 18:31:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845915; x=1794450715; 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=8Unix0qGxH+DyR8DqZn4KwW8PFLSBTwhIEWxnFX5DnY=; b=R/SGdesM3DebQzW9+cGzkYGOuzqdbT6BM7x3Dac20tzZzrZosVSoKhj3erZhF+SxuJ pPAw3Svh/hLPDP0B51trfTcMtnJN8FI+KD1QWIPVxZfdLP8FKuxqsR7IN3iiKyP1QXs4 C6OSzfD4L2H/uBLPeyMrZGtqWgl606GnIclDHPQypb97D6rJrRW1dBhaXxx9+4/0ccyC 2iyZzsle+aPmYov33LhU6nIEUF+jZNO0bisFHmFWM8/1/hN8/cOo4t95a3k+5EA5KJx0 L0kpu8dab+ssIZujCwF0QKWSay/yIbvpCRzNo/F+mVTpWTTJF1770iw60esrS+JQxRRh bNFQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845915; x=1794450715; 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=8Unix0qGxH+DyR8DqZn4KwW8PFLSBTwhIEWxnFX5DnY=; b=lxxMVRbOaQEuO5v1PLQqZBiNjFjDQPNAslysWF5jpm4q5lyBz7Mo4Ob5NNXwnfHJSo RjIQmaqjVivzX37duq523Sfk/yoqpMXg3CaUI3QRSMexQaqhaxj76R2IyjsyogOT0OBb EQUyGE7YNe6YoxZ5OBkmtOKzjoggLqFkDdnWvAgTqooUkrvnWT4avN8My3e/aDyUEsSe gqiaghhQeB98ieKmT2ZhRzUmoP+WFiTNxGvCMOOzUNbaeWZ2QOoLnqzSsJWoyOue23xe RhIpeaF7mByLjimLEFUE8GsJ1ufeQD/fdf7R4oKyVAYSLozCSeSqweerfo1C2gINsgio LNyA== X-Forwarded-Encrypted: i=1; AKwUvByuY4huKcMxEbTcKiDNNFUQn8/p5ZDUchXUY8sj8xWD5RDhi1V+P9U+Z2NEGkKxYd4bjIuLIFG5O3fNv5M=@vger.kernel.org X-Gm-Message-State: APuU4Z+MozBmQkbZh5bILHQjShrRTUKnwJ+Yt68WxHBo5QZCOzIVrSDl sWA+b2RlWndE/NpmZhIhpunuy2KCdi+UAE4+n1s+gODiwVKXsVk0sgeHKUACrw== X-Gm-Gg: AYBFou0UZvjF/r742rB77Kkm3EgvOi8n5deU9RA/WQI/RhOtUtCMit9iy29twI1goCq RZ50BM5/N4lI+5ui4OE23dO5QKZ/zUrydJHFEUeXodesU8/r37CN60zufhQZeXHBmwJk9BU7XAy tzGnbo0FXQT+UUrocQ8fFyrG+5yU7JMd9O8lou/ApPMGNkW76PeNi+/ndRFiSzWMx1wJM4n2aVW jUFctosp0dlmkWJQlsXVPwww7C2TXFP+6RwBQ9YMK/6SrQD4KCitdHp22QvrDplyUywmZB/OBJf H+kRny0UT0iyROQmzD/KdPbnxhod17AkeenQ63S991qdq+PDpINeI9FGZ1DrMvhYOV4uzW1aE+2 d7i+HTjnkzvf3GOVBI47yf12HxDbvpQADq2PzO2Qx3AtqlqZkyYvOuArdGegmpdLwNLU5cn2yqV 3j0sz5rszhaXBE70qvSJaOlrcnPwZK1qevDODLodJtfzRLxrljYyJvFEVqGf1r8mtrmy30OSLUA bnlJwnyW1dJjZXyUXCL5Hyabbli1qMJuID/bQ8R+eOIJdek7mG+mwdCJwFTfhu1CfCaBmo0PGB/ Z3vYpId9lnYCu9U1ib/auISGsKqIQU/ypci6se25R1TWSYv5SNetejly7WRYbqXn9BmMEIBDxHX 0xNkDoKGSGEE7gLCmy3tCA02hlUxQYOQHCSl0MXOZL075bVM= X-Received: by 2002:a17:90b:1e47:b0:3b0:a651:a7c3 with SMTP id 98e67ed59e1d1-3b0a651a96emr2175878a91.43.1793845914479; Wed, 04 Nov 2026 18:31:54 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.31.51 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:31: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 v16 02/12] ALSA: pcm: report partial progress under the stream lock Date: Thu, 5 Nov 2026 11:31:30 +0900 Message-ID: <20261105023140.18373-3-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Staged capture can make readable data available before a complete period has elapsed. Skipping notification leaves a blocked reader asleep, while using the period-interrupt path for this event can infer a false ring wrap when the notification is delayed. Add snd_pcm_progress_under_stream_lock() to update the hardware pointer through the non-interrupt path and notify readers and fasync listeners. It runs under the caller's PCM stream lock and does not tick the PCM timer. The driver must release its pointer and trigger locks before calling it. Existing period notification callers keep their current behavior. Atomic and nonatomic synthetic PCM tests reproduced the missing wakeup and false-wrap alternatives. The new helper delivered 64 final frames and reported four available frames after delayed partial progress without XRUN. Assisted-by: LLM Signed-off-by: Joonho Lee --- include/sound/pcm.h | 1 + sound/core/pcm_lib.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/sound/pcm.h b/include/sound/pcm.h --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -1182,6 +1182,7 @@ int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); =20 void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *su= bstream); +void snd_pcm_progress_under_stream_lock(struct snd_pcm_substream *substrea= m); void snd_pcm_period_elapsed(struct snd_pcm_substream *substream); snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, void *buf, bool interleaved, diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -1919,6 +1919,25 @@ EXPORT_SYMBOL(snd_pcm_period_elapsed_under_stream_lock); =20 /** + * snd_pcm_progress_under_stream_lock - Report progress without a period I= RQ + * @substream: PCM substream + * + * Context: PCM stream lock held, with no driver pointer or trigger locks = held. + * Updates the pointer and wakes readers for partial-period transfers. Unl= ike + * a period notification, this does not imply an elapsed period or tick th= e PCM + * timer. The same pointer, trigger and timestamp callbacks may be invoked. + */ +void snd_pcm_progress_under_stream_lock(struct snd_pcm_substream *substrea= m) +{ + if (PCM_RUNTIME_CHECK(substream) || !snd_pcm_running(substream)) + return; + snd_pcm_update_hw_ptr(substream); + snd_kill_fasync(substream->runtime->fasync, SIGIO, POLL_IN); +} +EXPORT_SYMBOL_GPL(snd_pcm_progress_under_stream_lock); + + +/** * snd_pcm_period_elapsed() - update the status of runtime for the next pe= riod by acquiring lock of * PCM substream. * @substream: the instance of PCM substream. From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.215.171 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845919; b=5iP2hIKwSo8RPjcfsQ2Z0i5+bEAIDZ+cgc1MfnZzEBHDuJXjb9zby1mDf8zqmVNKHf+V STsXd0seO/IilXAKi+1kaY570fW3rReOb0FdsDDraBe6snfPlrtAH9A6eYGWyoTsVfEhH sd4+QtWAqkzPjZBuQztvKNRjBN+im/ABjsEVNanLpoXnshcQ3tXpTOVEofv/ZvtqQwC0t XF2XooEcmDdDs+k6o/NHb0xC69ui6rUGiaczo4ZH27EubN0JFJ0L5nf+x9bJv/N5r9c81 M+OqCuCkDiOFS39fVfTc6ZC+gUvWkCXgKd+bZFi5K37DD2OH2mDkdIHw6+SMfkb420A== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845919; 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=8Q2wSKnVNfn+0m2s/5+0NXziIvo2kKlyTlann6b/3WQ=; b=PTDO1woXO8BsPwHij73QKCa2wHfL+ngGsh42sU1RNACKIOxSBxHL9y2J7J5uh4gA5etK JcYE8PgIk1BbAoX4v0vCoXwx0kHTlzJeg1lYyyGUSR44kU0UBE6tnNU8Mjqw7nGKiVrtm kI+ZFg+v/ENtVry8ixwrAvcrQLCWEgQ3jfL6R4ElzXOH94oBd1Rt4S2e+X3qMr2D4NRwl lCUzFxfO5eLYUUqATmGJgPNAW2CGleok9vqOhn0ouqY1tfY4Lt2kTnj7AUM1naYSOcej7 o8ROAczeveF9LcC+sLT/wk5BTfirmtiC7J5KBhzCoq4t642+RA41SCimxL6F2ldMkNQ== 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=yr89pume; arc=none smtp.remote-ip=209.85.215.171 Received: from mail-pg1-f171.google.com (mail-pg1-f171.google.com [209.85.215.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 BE0B4606D813 for ; Thu, 5 Nov 2026 02:31:58 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org BE0B4606D813 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=yr89pume Received: by mail-pg1-f171.google.com with SMTP id 41be03b00d2f7-ccca948203aso607640a12.3 for ; Wed, 04 Nov 2026 18:31:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845918; x=1794450718; 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=8Q2wSKnVNfn+0m2s/5+0NXziIvo2kKlyTlann6b/3WQ=; b=yr89pume9J5Q7b8qEd8sKapl3gSzQOLo3ioTIExp/fFRWBDm87cGGcl9vHOnVI+swp ey2Y9mfR91CcygSJezvhPWk3KmMt2VtuqvfIZyRoZGYqhUbCFN6nDKmIymXS3cEzuiEK sz9j3yqx/96t7J21cqyRD3q90kzmDx8qDsUbQcv04/TiNaQ1GZBDS0wJS7vps9hsv0Wp +EDYOS71RnBNciqKwhhAnmieIQWHDcF8h39WYdUcTPxiNjRIBlsyd+NWdCI89n682vfH daenpWu6ID7R2L5otHCVmikHM9ns/hcY7N9ve2lKABAtg0H8c8873iyWsWPWwsIpn/zE /QwA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845918; x=1794450718; 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=8Q2wSKnVNfn+0m2s/5+0NXziIvo2kKlyTlann6b/3WQ=; b=fMYNMCQI5QhZIkHWMQPa71uvdr5gwRgRSTLKz9nw3xWl1zxsQ5jIvqleDC5RSkOn0O QwfPhdsLVhUwxKXLTO9tp1CwSiBdCfpZzPgxmVYzVVeNFBuMqvPuLZiIJ/NMprOk2JeY fbYJR2NoO+fz2wAattRqT1uQZG76yq9TnLjlNqc3v2msod3QYF1iliqTpmWHKVHx/Guk TT2B1mt7/Avdhg1Arbnx2qqWtFFwnA/qGArLZIuE3fmfiylk96JUV3jUXh7crS+N6uV8 7H4M68cb62/FN+kZknIB6pSPIDUXF2yNvFPnDmINkJopS9Xs3DgXSPZOV3nbQdS33LOp keTQ== X-Forwarded-Encrypted: i=1; AKwUvBxDDDFEDxQpoBGUYI6NeHUWO5B5jOrJlKUqOUHz/JGNeWwMwsN808rr9B/qRHpvCbPT+PseAjJbNrY35YY=@vger.kernel.org X-Gm-Message-State: APuU4Z/2EsQUdGXYjHkpr053b88TVM4fJ4Ox25aUn/qo+3M0W2RfOAn9 d14poaXx4sUX/vTOF2u83FNQ8S4FLIUr8yoMsQBIMnHu9YJmUTwzf8Aq X-Gm-Gg: AYBFou2lNP7Xz21goNIOBX/70bvHkFWdBoJnBlYKZAPC8E6q18moR/qIiySZnoSuRgW JeJLqHGmUbZ2b/zynOidHyiIC5xal8jiLTsXWZI0DYMXlwJSfcoi265AbUxYUQp2DkZ5adatkag Zk59L2v4oUmLv+zgIaSTNg5msZ1aL88jG6ZUFs1ptjSMVF3tYhhGScvdVdOqd2hxqZNxKnti83q C6iU7EJTdvQq8b5PuLFV5gzK0JputGgyHJAIK7zpaD8z+foukzdfaVmdEIu3NiiRXsxmiW9xQyR 40FQACWR/fFOgUtc82cf7VXujZxZtvuf/dsPI+fOQqP0sM4hkH5d2BA2RdOHYLszJMczh3DM9rO XRMoBMVCjjHpcNxF4jb1lFyvCXXMbgpNHG4T/2PZ50exdelilvntJzeWNBJdfUdNdOmOlwMAK0x udTviOSEfA9SlSlc2H8r/TdVwHszXa13f4LFRVwMHCdSgFrPjOXrA/+X2AF/5Ot0ej3vgeiJUSa lkn+eBBDBmvGpx3Cbeqf088KI34j0e/FQDGBeThjkEvKvF8KXQoyYHpuOOG0VG5rDmtcIw2kBQ1 B1KI/wWDLgzRprt70Ivzzx3w8GsvYk3AsGH8hb6svczSkD4pXF7AzpyYtwHcgXZGw6lztFGcBcI sLd10mVoEE+V/QNw3gk387KoFlzgbW/M+ahqKjUoF4xCvnh0= X-Received: by 2002:a17:90b:3e89:b0:3a7:9045:fe29 with SMTP id 98e67ed59e1d1-3ae895362e9mr15653376a91.4.1793845917957; Wed, 04 Nov 2026 18:31:57 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.31.54 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:31:57 -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 v16 03/12] ALSA: pcm: add an optional reset operation under the stream lock Date: Thu, 5 Nov 2026 11:31:31 +0900 Message-ID: <20261105023140.18373-4-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" A driver may need to synchronize private capture accounting with the common RESET pointer observation and the core's position rebase. Add an optional reset operation called under the member's stream lock. Expose snd_pcm_lib_reset() so the callback can retain common pointer and time handling. On zero return the core completes that member's rebase before unlocking it. Drivers without the callback retain the ioctl path. The operation is also used after a successful prepare. Zero permits the position rebase; it does not promise XRUN recovery. A negative return skips the core rebase, so a callback returning an error must leave coherent pointer state even after partial work. Linked RESET and prepare stop at the first error without rollback. Earlier callback members may already have completed. Legacy members keep the existing post_action policy. Applications must recover the group after partial failure before relying on shared positions. This adds an internal kernel operation and no userspace ioctl or layout. Out-of-tree modules using snd_pcm_ops need to be rebuilt. Complete appl_ptr rebasing once in the callback path. A later linked member can sleep in prepare while the earlier member accepts SYNC_PTR; a subsequent unlocked post_prepare assignment would overwrite that acknowledgment. Keep explicit RESET playback silence processing and leave prepare-time filling to the normal START path, preserving samples written and rewound after prepare. Atomic and nonatomic synthetic-driver tests exercised both callback and legacy members, mixed linked groups and later-member failures. The callback retained an acknowledged appl_ptr of 64, and finite-silence playback kept the written prefix while clearing the rewound tail. Assisted-by: LLM Signed-off-by: Joonho Lee --- include/sound/pcm.h | 21 +++++++++++++++++++++ sound/core/pcm_lib.c | 29 ++++++++++++++++++++++++----- sound/core/pcm_native.c | 45 ++++++++++++++++++++++++++++++++++++++++---= -- 3 files changed, 85 insertions(+), 10 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -77,6 +77,26 @@ unsigned long offset); int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *v= ma); int (*ack)(struct snd_pcm_substream *substream); + /* + * Optional RESET operation, also used after a successful prepare. + * Called with this substream's PCM lock held; atomic streams must not + * sleep. Do not reacquire the PCM lock through snd_pcm_lib_ioctl(). + * snd_pcm_lib_reset() provides the common pointer update under this + * lock. The core completes the position and appl_ptr rebase before + * unlocking a successful member. + * + * Return zero to let the core complete the position rebase. As with + * the legacy RESET ioctl, zero does not guarantee recovery from XRUN; + * callers must still handle the stream state and subsequent I/O errors. + * On a negative return the core does not finish the rebase, so the + * callback must leave coherent pointer state even after partial work. + * + * Linked RESET/prepare + * stops at the first error without rollback. Earlier callback members + * retain their completed reset; legacy members retain the existing + * post_action policy. An error does not guarantee unchanged state. + */ + int (*reset)(struct snd_pcm_substream *substream); }; =20 /* @@ -1179,6 +1199,7 @@ { substream->runtime->std_sync_id =3D true; } +int snd_pcm_lib_reset(struct snd_pcm_substream *substream); int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); =20 void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *su= bstream); diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -1760,20 +1760,39 @@ } EXPORT_SYMBOL(snd_pcm_hw_params_bits); =20 -static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream, - void *arg) +/** + * snd_pcm_lib_reset - Reset the pointer while holding the PCM stream lock + * @substream: PCM substream + * + * Context: PCM stream lock held. The caller must not hold driver locks + * acquired by the pointer or XRUN callbacks. + * + * Update a running stream through the ordinary pointer path. Other states + * start at zero. Pointer errors keep the existing RESET return semantics; + * a zero return does not imply recovery from XRUN. + * This helper does not update appl_ptr or acquire the stream lock. + * + * Return: zero. + */ +int snd_pcm_lib_reset(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime =3D substream->runtime; =20 - guard(pcm_stream_lock_irqsave)(substream); - if (snd_pcm_running(substream) && - snd_pcm_update_hw_ptr(substream) >=3D 0) + if (snd_pcm_running(substream) && snd_pcm_update_hw_ptr(substream) >=3D 0) runtime->status->hw_ptr %=3D runtime->buffer_size; else { runtime->status->hw_ptr =3D 0; runtime->hw_ptr_wrap =3D 0; } return 0; +} +EXPORT_SYMBOL(snd_pcm_lib_reset); + +static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream, + void *arg) +{ + guard(pcm_stream_lock_irqsave)(substream); + return snd_pcm_lib_reset(substream); } =20 static int snd_pcm_lib_ioctl_channel_info(struct snd_pcm_substream *substr= eam, diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1984,11 +1984,35 @@ } } =20 -static int snd_pcm_do_reset(struct snd_pcm_substream *substream, - snd_pcm_state_t state) +static int snd_pcm_reset_substream(struct snd_pcm_substream *substream, + bool for_prepare) { struct snd_pcm_runtime *runtime =3D substream->runtime; - int err =3D snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL); + int err; + + if (substream->ops->reset) { + /* + * Complete this member before releasing its PCM lock. A later + * linked member may fail; RESET has no group rollback operation. + */ + guard(pcm_stream_lock_irq)(substream); + + err =3D substream->ops->reset(substream); + if (err < 0) + return err; + runtime->hw_ptr_base =3D 0; + runtime->hw_ptr_interrupt =3D runtime->status->hw_ptr - + runtime->status->hw_ptr % runtime->period_size; + runtime->silence_start =3D runtime->status->hw_ptr; + runtime->silence_filled =3D 0; + runtime->control->appl_ptr =3D runtime->status->hw_ptr; + if (!for_prepare && substream->stream =3D=3D SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + return 0; + } + + err =3D snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL); if (err < 0) return err; guard(pcm_stream_lock_irq)(substream); @@ -2000,10 +2024,19 @@ return 0; } =20 +static int snd_pcm_do_reset(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + return snd_pcm_reset_substream(substream, false); +} + static void snd_pcm_post_reset(struct snd_pcm_substream *substream, snd_pcm_state_t state) { struct snd_pcm_runtime *runtime =3D substream->runtime; + + if (substream->ops->reset) + return; guard(pcm_stream_lock_irq)(substream); runtime->control->appl_ptr =3D runtime->status->hw_ptr; if (substream->stream =3D=3D SNDRV_PCM_STREAM_PLAYBACK && @@ -2053,14 +2086,16 @@ if (err < 0) return err; pcm_qos_req_update(substream, period_to_usecs(substream->runtime)); - return snd_pcm_do_reset(substream, state); + return snd_pcm_reset_substream(substream, true); } =20 static void snd_pcm_post_prepare(struct snd_pcm_substream *substream, snd_pcm_state_t state) { struct snd_pcm_runtime *runtime =3D substream->runtime; - runtime->control->appl_ptr =3D runtime->status->hw_ptr; + /* The reset callback path already completed this rebase under the lock. = */ + if (!substream->ops->reset) + runtime->control->appl_ptr =3D runtime->status->hw_ptr; snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); } From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.216.46 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845923; b=XP4CXAnIhHZsSYLUsjx2S8Mmc33nAYBzBGL48PkOS9wsoIF7o4HBcbNayWeL7YdgzDcL Vba35LVIayEsKJiJzOrZElPwX20qXwocayrr14tHSbv4Lwq4vYCpLPJWIixpuBn/jo4Vx CLQjo3AaxuE0gsYHXXKq1ABGgQkMAEmmpxN3JCiiWDpNYMYhL0ardm5dLmST6i7XxartL J1N8DHOe9yXRqQmBWZdFydeBCDK1lIiekNM5YmK8s3qFo+e7+IevtPuY4xG5Iy1iyvfsR uAlSvCWOZSoFK1AsUGmoW/DaXIjyv2HdacVNFYXHOJzWXc5kCz4wCw7kLAsrw/RmGJQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845923; 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=Xqi2EdRaW2Kq14MddjxCIPAmrVsb7HcK54mnuUSNeMI=; b=KbVaeAtXRIsBCSKRaVurIPLyJIUbpL5rDloJlt88UfXbIKZSqO9jOzhbLN6ka3LbKuBQ pU/ktW7kjvpH8yacbNfeM/zYzVGCqGTv7OPflTJq/k49EfO8KVF4oPLSqH3ueoJRPUGmH NO5p5BkqKlS5nyEndl1QHdJVsfLU7UYDcaxIYllDtPc09lQDSshKAPU0uholNaO8pqSDm kiT6RJ3EqYo4Zu2Tuwp8X+KBEi6Sy4s+99h8Z8sxgZqSTCz3SXppw0eQ6qTH34Ud3je8q 1Zrtynh2HGNAiTfooreydAy4OVLQAxDdwFvhjjBX9fOByI5o4b66isJA4AjXXiaftEQ== 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=rBnrNFU+; arc=none smtp.remote-ip=209.85.216.46 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (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 43B97606A33B for ; Thu, 5 Nov 2026 02:32:02 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 43B97606A33B 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=rBnrNFU+ Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-3b094e649d3so952328a91.0 for ; Wed, 04 Nov 2026 18:32:02 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845921; x=1794450721; 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=Xqi2EdRaW2Kq14MddjxCIPAmrVsb7HcK54mnuUSNeMI=; b=rBnrNFU+QLKoYqkm81IdXGFard5WuEtRJl6MLXDGViNn4k69zOCkaaxOd8ofMOUo+P 0Tw8PSYWBLmmdn5fSpgR0ZkCdqJzjZlWCTShnu2iM6wbx7yLv4pQ3xKwRu7i23pG4AZi PYU45FyahkH56J+0SULUkwaEsbrUoPQj9c1Xx3bDmla9ZUQZ3Wjlu0LJlc7McMT5lvCU ZzjtnVMvwT20tAlGwrnTzo1P0dGt25IJ+VZt1YjfvexLdpbhVSJZ+cerQYNwoTMwXmvO Aj1YWUEEq/TnQH8HwmwQaZ49JTPp2n9Pcc+gHgM6UyFBEr8oHRlO7D+sYv6YxqD6MI8p fFDw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845921; x=1794450721; 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=Xqi2EdRaW2Kq14MddjxCIPAmrVsb7HcK54mnuUSNeMI=; b=oaknvGQ3RGsiXrvYDbMShOWpj5djbBCXSJBX6lkjnycXpvJTC1qzqhLD2mjQkSI642 fxwhaqAywCp9j/xs46L0Vj1r2cUt/1w16YaQNo37Y2WV9HXmW9+YRBKD4TtkRalYrIp9 OJ+oLNXdbiJwc3yoKZh0orqAG7ORlj96PxQ80RCQ4apq63QRvdf1VFTW4hY+KVRuzpmt Sf3CgRafoOnRVtkzjQFKN71uCQgIqPzerETDlbhq2pDZzGdT3b0Esr4g5Z+wQ2Ss7jid iBbc2b7fh0KPxyVYKWj7mF7lm0b1bKLIdCvL5E0R5NmwUGZ1vjxDchfPFYnBtVNRqA5b 8wjA== X-Forwarded-Encrypted: i=1; AKwUvBylSpI7DF2/K8rUx266wrqub6VEgoieJkdKHPBFdDSw8L62mKQWP7imZaRtjV53ExtfQ3zGPoidbNSSD3c=@vger.kernel.org X-Gm-Message-State: APuU4Z+34ls7tP2pbquU96uPdusr9Ow584pPJkrfML/jyqnf4cmAch3s 0F8wnsJVlREAm8x3IIip/ckOD01WkvH3UmSLp5iZ90r90tg2g/2uQJmV X-Gm-Gg: AYBFou0G0bc4rSzrPEwV3gjHadxbfd601wWFsGDY6md1fZShv06Pd3W7Q9iGjXyfbEl dFbD2qij95N9nPYDpr8murJizqzi++LwYXQjta6GAlISXLZC81e+ttV64Z2edIpqh0gavX/yy5u R5mRPfTkCw6YN79zQQCNwgPuMOY56UvNS+voenfd5fVGENI5tz5QWMbKCiG/MEMoFtwQUIsrQQG 8csvcPMukACo59kpnrcSzvF5zWUpu8zB25HQ1/6e+QWzS7e4KTUu1CuywqE2M1MoW0x2B1LTcTB 1cxySWP0efAdkydFpp28CMxZgaat8pRaVNUuQ4zQTIQQ56LlDhml1TaAKiCGSQYG/kCghBWstRf sg0A3Xg376sTLR5aBTbv8OItGKzhxNbhVnvE017TdXBqMmkLigjyfJ6LsOA7eGUy1bfP/l5CLws Y57Q1KWYFVJ4FDGC/EwLTDl/5u9ND902FbF5w4CpI8zT33dnqDpNi6pm5xDMOQrxHkR52uSL5TS BtbaAIMVgU3Li53F4Uw8aAoIOxyEHcMPhnR0wihFO50BI5DSyJGt4aCRNSvCEIzEQ9C4QZFIC9k QLZ7nrp1izyZ2XB5ubRJMnhA0e9tNvhJA8RFhDq6D4pX3K9b9bGvn33e6owGQqD8CPIWIJlLGNQ WEPwmXQSLdaNdaZT/AZAwqKYhRAPoG0EvdVh6hqcWh9N4Ro0= X-Received: by 2002:a17:90b:314d:b0:3ac:eeb:e0ed with SMTP id 98e67ed59e1d1-3ae8983565cmr15300539a91.36.1793845921379; Wed, 04 Nov 2026 18:32:01 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.31.58 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:00 -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 v16 04/12] ALSA: virtio: Propagate PCM send failures through ACK Date: Thu, 5 Nov 2026 11:31:32 +0900 Message-ID: <20261105023140.18373-5-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" virtsnd_pcm_msg_send() can fail while queueing a transfer. Restore the failed message's length and notify the device about earlier messages queued by the same call, so they do not remain stranded. Propagate errors through the indirect transfer callback and commit its private cursor only after successful submission. Genuine transfer errors arm stop_operating and return -EPIPE so ALSA can retire the stream. Close ACK admission under the PCM stream lock before sleeping teardown. Keep the prepared state protected by the substream lock and reject ACK through prepare and parameter changes. Reopen admission in the reset callback while the core holds the PCM lock for its pointer rebase. An ACK racing with teardown returns -EBADFD and preserves stop state. Keep the polling feature path unchanged. Fixes: fe981e67568c ("ALSA: virtio: use ack callback") Reject a stopped playback ACK before the XRUN path can rearm stop_operating. For capture, allow the ordinary stopped tail only during DRAINING and only while no transfer XRUN is latched. Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_pcm.h | 6 + sound/virtio/virtio_pcm_msg.c | 12 ++ sound/virtio/virtio_pcm_ops.c | 249 +++++++++++++++++++++++++++++++++++++= ---- 3 files changed, 241 insertions(+), 26 deletions(-) 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 @@ -33,6 +33,10 @@ * @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun). * @stopped: True if the substream is stopped and must be released on the = device * side. + * @prepared: True after device PREPARE and the core pointer reset succeed; + * protected by @lock. + * @preparing: ACK gate through teardown and prepare's core reset; + * protected by @lock. * @suspended: True if the substream is suspended and must be reconfigured= on * the device side at resume. * @msgs: Allocated I/O messages. @@ -56,6 +60,8 @@ bool xfer_enabled; bool xfer_xrun; bool stopped; + bool prepared; + bool preparing; bool suspended; struct virtio_pcm_msg **msgs; unsigned int nmsgs; 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 @@ -260,6 +260,18 @@ dev_err(&vdev->dev, "SID %u: failed to send I/O message\n", vss->sid); + msg->length -=3D n; + + /* + * Do not leave earlier messages queued without notifying + * the device when a later message could not be added. + */ + if (msg_count !=3D vss->msg_count && + !(vss->features & + (1U << VIRTIO_SND_PCM_F_MSG_POLLING)) && + virtqueue_kick_prepare(vqueue)) + virtqueue_notify(vqueue); + return rc; } =20 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 @@ -97,6 +97,38 @@ static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream); =20 /** + * virtsnd_pcm_close_ack() - Update PCM prepare admission state. + * @substream: Kernel ALSA substream. + * @preparing: True while driver prepare and the core pointer reset are ac= tive. + * + * Context: Process context. + */ +static void virtsnd_pcm_close_ack(struct snd_pcm_substream *substream, + bool preparing) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + + scoped_guard(pcm_stream_lock_irqsave, substream) { + guard(spinlock_irqsave)(&vss->lock); + vss->prepared =3D false; + vss->xfer_enabled =3D false; + vss->preparing =3D preparing; + } +} + +/* Keep completed RX data valid while RELEASE drains the device queue. */ +static void virtsnd_pcm_block_ack(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + + scoped_guard(pcm_stream_lock_irqsave, substream) { + guard(spinlock_irqsave)(&vss->lock); + vss->preparing =3D true; + vss->xfer_enabled =3D false; + } +} + +/** * virtsnd_pcm_open() - Open the PCM substream. * @substream: Kernel ALSA substream. * @@ -115,7 +147,8 @@ snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); =20 - vss->stopped =3D !!virtsnd_pcm_msg_pending_num(vss); + scoped_guard(spinlock_irqsave, &vss->lock) + vss->stopped =3D !!vss->msg_count; vss->suspended =3D false; =20 /* @@ -219,6 +252,8 @@ struct virtio_device *vdev =3D vss->snd->vdev; int rc; =20 + virtsnd_pcm_close_ack(substream, false); + if (virtsnd_pcm_msg_pending_num(vss)) { dev_err(&vdev->dev, "SID %u: invalid I/O queue state\n", vss->sid); @@ -254,6 +289,8 @@ { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); =20 + virtsnd_pcm_close_ack(substream, false); + /* If the queue is flushed, we can safely free the messages here. */ if (!virtsnd_pcm_msg_pending_num(vss)) virtsnd_pcm_msg_free(vss); @@ -262,17 +299,18 @@ } =20 /** - * virtsnd_pcm_prepare() - Prepare the PCM substream. + * virtsnd_pcm_prepare_device() - Prepare the PCM substream on the device. * @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_device(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_device *vdev =3D vss->snd->vdev; struct virtio_snd_msg *msg; + int rc; =20 if (!vss->suspended) { if (virtsnd_pcm_msg_pending_num(vss)) { @@ -287,16 +325,14 @@ struct snd_pcm_runtime *runtime =3D substream->runtime; unsigned int buffer_bytes =3D snd_pcm_lib_buffer_bytes(substream); unsigned int period_bytes =3D snd_pcm_lib_period_bytes(substream); - int rc; =20 rc =3D virtsnd_pcm_dev_set_params(vss, buffer_bytes, period_bytes, - runtime->channels, - runtime->format, runtime->rate); + runtime->channels, + runtime->format, runtime->rate); if (rc) return rc; } =20 - vss->xfer_xrun =3D false; vss->suspended =3D false; vss->msg_count =3D 0; virtsnd_pcm_msg_reset(vss); @@ -311,7 +347,26 @@ if (!msg) return -ENOMEM; =20 - return virtsnd_ctl_msg_send_sync(vss->snd, msg); + rc =3D virtsnd_ctl_msg_send_sync(vss->snd, msg); + if (rc) + return rc; + + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->xfer_xrun =3D false; + } + return 0; +} + +/* Keep ACK closed through the core's locked pointer reset. */ +static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) +{ + int rc; + + virtsnd_pcm_close_ack(substream, true); + rc =3D virtsnd_pcm_prepare_device(substream); + if (rc) + virtsnd_pcm_close_ack(substream, false); + return rc; } =20 /** @@ -338,6 +393,8 @@ =20 scoped_guard(spinlock_irqsave, &queue->lock) { guard(spinlock)(&vss->lock); + if (vss->preparing || !vss->prepared) + return -EBADFD; if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) rc =3D virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes); if (rc) @@ -356,10 +413,12 @@ =20 return virtsnd_ctl_msg_send_sync(snd, msg); case SNDRV_PCM_TRIGGER_SUSPEND: - vss->suspended =3D true; + scoped_guard(spinlock_irqsave, &vss->lock) + vss->suspended =3D true; fallthrough; case SNDRV_PCM_TRIGGER_STOP: - vss->stopped =3D true; + scoped_guard(spinlock_irqsave, &vss->lock) + vss->stopped =3D true; fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: scoped_guard(spinlock_irqsave, &vss->lock) { @@ -393,11 +452,16 @@ struct virtio_snd *snd =3D vss->snd; struct virtio_snd_msg *msg; unsigned int js =3D msecs_to_jiffies(virtsnd_msg_timeout_ms); + bool stopped; int rc; =20 + virtsnd_pcm_block_ack(substream); + cancel_work_sync(&vss->elapsed_period); =20 - if (!vss->stopped) + scoped_guard(spinlock_irqsave, &vss->lock) + stopped =3D vss->stopped; + if (!stopped) return 0; =20 msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_RELEASE, @@ -425,7 +489,8 @@ return !rc ? -ETIMEDOUT : rc; } =20 - vss->stopped =3D false; + scoped_guard(spinlock_irqsave, &vss->lock) + vss->stopped =3D false; =20 return 0; } @@ -442,6 +507,10 @@ virtsnd_pcm_pb_pointer(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irqsave, &vss->lock) + if (vss->xfer_xrun) + return SNDRV_PCM_POS_XRUN; =20 return snd_pcm_indirect_playback_pointer(substream, &vss->pcm_indirect, @@ -461,41 +530,167 @@ { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); =20 + scoped_guard(spinlock_irqsave, &vss->lock) + if (vss->xfer_xrun) + return SNDRV_PCM_POS_XRUN; + return snd_pcm_indirect_capture_pointer(substream, &vss->pcm_indirect, vss->hw_ptr); } =20 +struct virtsnd_pcm_transfer { + struct snd_pcm_indirect indirect; + int error; +}; + static void virtsnd_pcm_trans_copy(struct snd_pcm_substream *substream, struct snd_pcm_indirect *rec, size_t bytes) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - - virtsnd_pcm_msg_send(vss, rec->sw_data, bytes); + struct virtsnd_pcm_transfer *transfer =3D + container_of(rec, struct virtsnd_pcm_transfer, indirect); + + if (transfer->error) + return; + + transfer->error =3D virtsnd_pcm_msg_send(vss, rec->sw_data, bytes); +} + +/* Caller holds vss->lock. */ +static void virtsnd_pcm_transfer_failed(struct virtio_pcm_substream *vss) +{ + vss->xfer_enabled =3D false; + vss->stopped =3D true; + vss->substream->runtime->stop_operating =3D true; +} + +/* The caller holds the PCM lock, also used by DROP and state transitions.= */ +static int virtsnd_pcm_ack_state(struct snd_pcm_substream *substream) +{ + switch (substream->runtime->state) { + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + case SNDRV_PCM_STATE_PREPARED: + 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; + } } =20 static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); - - guard(spinlock_irqsave)(&queue->lock); - guard(spinlock)(&vss->lock); - - return snd_pcm_indirect_playback_transfer(substream, &vss->pcm_indirect, - virtsnd_pcm_trans_copy); + struct virtsnd_pcm_transfer transfer =3D {}; + int rc; + + rc =3D virtsnd_pcm_ack_state(substream); + if (rc) + return rc; + + scoped_guard(spinlock_irqsave, &queue->lock) { + guard(spinlock)(&vss->lock); + if (vss->preparing || !vss->prepared) + return -EBADFD; + if (vss->stopped) + return -EBADFD; + if (vss->xfer_xrun) { + rc =3D -EPIPE; + } else { + transfer.indirect =3D vss->pcm_indirect; + rc =3D snd_pcm_indirect_playback_transfer(substream, + &transfer.indirect, + virtsnd_pcm_trans_copy); + if (!rc) + rc =3D transfer.error; + } + if (rc) + virtsnd_pcm_transfer_failed(vss); + else + vss->pcm_indirect =3D transfer.indirect; + } + if (rc) { + dev_err(&vss->snd->vdev->dev, + "SID %u: playback ACK transfer failed: %d\n", + vss->sid, rc); + return -EPIPE; + } + + return 0; } =20 static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); - - guard(spinlock_irqsave)(&queue->lock); - guard(spinlock)(&vss->lock); - - return snd_pcm_indirect_capture_transfer(substream, &vss->pcm_indirect, - virtsnd_pcm_trans_copy); + struct virtsnd_pcm_transfer transfer =3D {}; + int rc; + + rc =3D virtsnd_pcm_ack_state(substream); + if (rc) + return rc; + + scoped_guard(spinlock_irqsave, &queue->lock) { + guard(spinlock)(&vss->lock); + if (vss->preparing || !vss->prepared) + return -EBADFD; + /* Keep only the post-STOP capture tail available during drain. */ + if (vss->stopped && + (vss->xfer_xrun || + substream->runtime->state !=3D SNDRV_PCM_STATE_DRAINING)) + return -EBADFD; + if (vss->xfer_xrun) { + rc =3D -EPIPE; + } else { + transfer.indirect =3D vss->pcm_indirect; + rc =3D snd_pcm_indirect_capture_transfer(substream, + &transfer.indirect, + virtsnd_pcm_trans_copy); + if (!rc) + rc =3D transfer.error; + } + if (rc) + virtsnd_pcm_transfer_failed(vss); + else + vss->pcm_indirect =3D transfer.indirect; + } + if (rc) { + dev_err(&vss->snd->vdev->dev, + "SID %u: capture ACK transfer failed: %d\n", + vss->sid, rc); + return -EPIPE; + } + + return 0; +} + +/** + * virtsnd_pcm_reset() - Reset PCM pointers and complete prepare admission. + * @substream: Kernel ALSA substream. + * + * Context: PCM stream lock held. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_pcm_reset(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + int rc; + + rc =3D snd_pcm_lib_reset(substream); + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->preparing) { + vss->prepared =3D !rc; + vss->preparing =3D false; + } + } + + return rc; } =20 /* PCM substream operators map. */ @@ -507,6 +702,7 @@ .hw_params =3D virtsnd_pcm_hw_params, .hw_free =3D virtsnd_pcm_hw_free, .prepare =3D virtsnd_pcm_prepare, + .reset =3D virtsnd_pcm_reset, .trigger =3D virtsnd_pcm_trigger, .sync_stop =3D virtsnd_pcm_sync_stop, .pointer =3D virtsnd_pcm_pb_pointer, @@ -519,6 +715,7 @@ .hw_params =3D virtsnd_pcm_hw_params, .hw_free =3D virtsnd_pcm_hw_free, .prepare =3D virtsnd_pcm_prepare, + .reset =3D virtsnd_pcm_reset, .trigger =3D virtsnd_pcm_trigger, .sync_stop =3D virtsnd_pcm_sync_stop, .pointer =3D virtsnd_pcm_cp_pointer, From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.216.46 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845926; b=CrjO6Q2OW5Ufo+nY3/fjVCwkxnv2Gvav2QbK5q6u5bCrJ0GSVdODkVP/ur3yCzwRQrf3 Lz4ccNvBaHqwNtTEMt+tE2IRhX02nMfHJPRB3HQeyOTO+MeHNNzrBJrbXI5nivnhp0rWC 3KOZsDfpenHzO/7SY9Pk3RE1vR+dV6a4wRWZUikYhkr9HGUzhlduvMmib5AwhsjQr3xea aIfJOVCAsjn1wZ/5YM6TLouPf+SRPHn1oadIa1yXMXvTCCywLvypjp2dkQo8OIpjnZBsi yzgfquItVJRA6BmzNhjjUnRUhbLDddK3OoaB+BbtdtjbRBNt91OwSD6aYvJzv7bdI/w== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845926; 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=L2ncasJwCW/aoSrC8F4UjbFCWtDNg2LhwMiWB4wH+wk=; b=pZjP4Z8+QF81ItnfJ9YwnU2DgjncZIffJFPFMJMl22eoIO98fPS8/76V8LmK2NvNoGN/ wZ4yIIXGwaJPoZOnRjJWiFn9r0SJ8OE2pGFjrhUTuxJfiT6rt/K6RcFlSi6GeDr5qjHhr CeledenTyFjDUCGMSDsOHjtP+PVWUCI36jwDEsHB8UoQuFPc/mvT2GLhsUFXdqovPXJSu C7aFQTwZKWq/m1Fcmtp6Oj995CdXFUs2sC+m3CZMwTfxkVIzdEzU+fTT817Duw99SoLp+ 79vCxOpo09QhGJ97K0wALt93lFoi6YVQf622qg6Lhucq+XBUnzzq6Xw/uOLdVVLabzQ== 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=bUfwofwy; arc=none smtp.remote-ip=209.85.216.46 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (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 83019606A33B for ; Thu, 5 Nov 2026 02:32:06 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 83019606A33B 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=bUfwofwy Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-3b094e649d3so952387a91.0 for ; Wed, 04 Nov 2026 18:32:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845926; x=1794450726; 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=L2ncasJwCW/aoSrC8F4UjbFCWtDNg2LhwMiWB4wH+wk=; b=bUfwofwyZ0Nt7dXLxkceyupNBPu23hnfDEPaAeZ1h4UIVqAkFcKRwCiAXMnzqhc5mn OGAEAiGRcXLYWBRMS3sKIxTjNM/j2MrpcUG7imGWAIBRtpZAYueAmjHNVCMfAnz+F86K 7uTHsxox6UX1d+ZPcFvVeGbqVp+TCDIiD71qJTICtTXNK1a93tLiz3LOtWD/Hp+65QZq Y4mesBdUA+6Ls+j5U6+187uV2hHHIHrueORCE1V9XisXAEp5Ia7S2oEeO1Mg1VxUPoxf eMl2rmdhG/RINxTpscD/85ILNuDwbP+shOt6C1nl19XbdBJ7FB/MI+NmcCdDRl53OyH+ O9Cw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845926; x=1794450726; 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=L2ncasJwCW/aoSrC8F4UjbFCWtDNg2LhwMiWB4wH+wk=; b=WwCZyONVLRNgFKm2+NEREPDuBQftwmhfBK8FD09OSl/7ywGxCHotTecFMc8e2r5jx1 RpEIYREeN2/AK/2ct1JT/WtABQrzhZ/beEnGSFoQvIhO0cTBnuGyuWIOxUTCtQGkt5SV CKdMNa7VKWXlWtjcquxyY8MchAB8hndX8uvUQsX992DzggR/vsOz/F2X9KH+dIIwMoAd azFXhyTfIPPFX/ZSA5uG4pUq6klYIrd39kvKR/CthH2US5Rqj/9hzhPOvYLNd5tpGLWK Otpd5+wizxnch/FTjCBmnx3cDqvQ0RGWmzSFVtxLKB80OpYJNFANT+ybFfHSKTheXDbY 0ljw== X-Forwarded-Encrypted: i=1; AKwUvBzKWpYQquvBOuQ/pXSfyBj3ffMfzxHa7gTPyEFx3+k+QGqRvNGQPbtyk4YOMrHwvPAnPjGLy/tofqS+yRw=@vger.kernel.org X-Gm-Message-State: APuU4Z+KeOS8dQwYmhHLP27uu0lbpQUXR4YrdbueWa/xXaSJJmJ7W1LW mh+xeRGJnSb/mRn/JpNPyYpGJKwKQ+MEzWApOI8CQA8aZzyuCIjPGw5p X-Gm-Gg: AYBFou067HQXeXaMSlhAv1cmqZo+yinZNNFY2u6ULlUC39/L9tnO5vKb2IKpZnWSf2M w+aTOmi5CV9dwiCz/IvxJyJQ5QY1vXWMqJbANf/YTwmXtYLanIKiyyDr17HXIWQyuDApE1wfGeY 6ro05wuD5XSvqs0bGaC3o10RC7CQH9K5C/BBINGiC4td+Vovd8cz4wXhDRrb7QYr6LCtHESY+h2 S4PNBPontdtquptl84sA2vRasK1GsJiTBm9o2j0um5Dzh9ANa0rpA+2cx8/c935n1fJ2cUK+nG/ lAGVrNxQk6XXii57G8Zv7dJKuXEuaP0x8NjKC5plEhcD0nDyH0FXrmFZeE1eqaH7Zmyh/lSkYE2 hvsnPS+9B4JLEwhfPfl5s5IUZ2y0zRckpjO/9jrGSxV/Hvpi5HWe8/J4PIYDud4q9aOqrpPEgRU EWRsV8gDNJraa98DjITSUYDNB8MdGj5JPXbDzX8829Jryc6MkcuhScfeBoyvuBwAiL/e5ODPifL a8KB1vKdazDsv4NuIOf42XUMxHbWJdPUPLO0+GL4FKgpAXWKkCpou8GevlOcvuJWbwHAVQaRLNP 76YnhkAsV80bDvyiDAWS7DXZ5Oc/TQY4IGzJv5hOidr5pQsSqazh8BwwcgeQ7KzBnbX5kKe9D7P /ugFQ+hLgtlOv0wpn48otd5d6xt0NMV2Xf468JMwBLSh/vo8BB7ChZJ3MSfEZYCMyoabitQ== X-Received: by 2002:a17:90b:224d:b0:3ab:afca:1d79 with SMTP id 98e67ed59e1d1-3ae897eb33emr15008437a91.21.1793845925764; Wed, 04 Nov 2026 18:32:05 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.01 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:04 -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 v16 05/12] ALSA: virtio: Recover PCM state after a failed start Date: Thu, 5 Nov 2026 11:31:33 +0900 Message-ID: <20261105023140.18373-6-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Capture trigger queues I/O messages before PCM_START. If the START message cannot be allocated or sent, transfers may remain queued, and ALSA does not invoke the action undo callback for the failed trigger. Mark the stream stopped and disable transfers for every failed START path. Arm runtime->stop_operating so sync_stop releases pending I/O and prevents prepare, hw_params, or START retries until release succeeds. The stopped flag is shared with ACK callbacks that can run without the ALSA stream lock. Protect every stopped access with vss->lock. Read the pending message count under the same lock in open and hw_params. In sync_stop, decide whether RELEASE is needed and mark the stream stopped in one locked transition; leave it stopped when release or draining fails. Always cancel elapsed-period work in sync_stop, even when no RELEASE is needed. Record the RELEASE obligation after successful PREPARE and clear it as soon as RELEASE succeeds. If the subsequent RX wait fails, retry that wait without submitting RELEASE again. Fixes: da76e9f3e43a ("ALSA: virtio: PCM substream operators") Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_pcm.h | 6 ++ sound/virtio/virtio_pcm_ops.c | 107 +++++++++++++++++++++++++++++++------= ---- 2 files changed, 85 insertions(+), 28 deletions(-) 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 @@ -26,13 +26,13 @@ * @hw: Kernel ALSA substream hardware descriptor. * @elapsed_period: Kernel work to handle the elapsed period state. * @lock: Spinlock that protects fields shared by interrupt handlers and - * substream operators. + * substream operators, including @stopped. * @buffer_bytes: Current buffer size in bytes. * @hw_ptr: Substream hardware pointer value in bytes [0 ... buffer_bytes). * @xfer_enabled: Data transfer state (0 - off, 1 - on). * @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun). * @stopped: True if the substream is stopped and must be released on the = device - * side. + * side; protected by @lock. * @prepared: True after device PREPARE and the core pointer reset succeed; * protected by @lock. * @preparing: ACK gate through teardown and prepare's core reset; @@ -62,6 +62,8 @@ bool stopped; bool prepared; bool preparing; + /* Successful PREPARE still needs RELEASE; protected by lock. */ + bool release_required; bool suspended; struct virtio_pcm_msg **msgs; unsigned int nmsgs; 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 @@ -116,6 +116,15 @@ } } =20 +static void virtsnd_pcm_start_failed(struct virtio_pcm_substream *vss) +{ + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->xfer_enabled =3D false; + vss->stopped =3D true; + } + vss->substream->runtime->stop_operating =3D true; +} + /* Keep completed RX data valid while RELEASE drains the device queue. */ static void virtsnd_pcm_block_ack(struct snd_pcm_substream *substream) { @@ -148,7 +157,7 @@ SNDRV_PCM_HW_PARAM_PERIODS); =20 scoped_guard(spinlock_irqsave, &vss->lock) - vss->stopped =3D !!vss->msg_count; + vss->stopped |=3D !!vss->msg_count; vss->suspended =3D false; =20 /* @@ -250,13 +259,26 @@ { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_device *vdev =3D vss->snd->vdev; + unsigned int msg_count; + bool stopped; int rc; =20 virtsnd_pcm_close_ack(substream, false); =20 - if (virtsnd_pcm_msg_pending_num(vss)) { + scoped_guard(spinlock_irqsave, &vss->lock) { + msg_count =3D vss->msg_count; + stopped =3D vss->stopped; + } + if (msg_count) { dev_err(&vdev->dev, "SID %u: invalid I/O queue state\n", vss->sid); + return -EBADFD; + } + if (stopped) { + dev_err(&vdev->dev, "SID %u: stream release failed\n", + vss->sid); + scoped_guard(pcm_stream_lock_irqsave, substream) + substream->runtime->stop_operating =3D true; return -EBADFD; } =20 @@ -310,7 +332,18 @@ struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_device *vdev =3D vss->snd->vdev; struct virtio_snd_msg *msg; + bool stopped; int rc; + + scoped_guard(spinlock_irqsave, &vss->lock) + stopped =3D vss->stopped; + if (stopped) { + dev_err(&vdev->dev, "SID %u: stream release failed\n", + vss->sid); + scoped_guard(pcm_stream_lock_irqsave, substream) + substream->runtime->stop_operating =3D true; + return -EBADFD; + } =20 if (!vss->suspended) { if (virtsnd_pcm_msg_pending_num(vss)) { @@ -353,6 +386,7 @@ =20 scoped_guard(spinlock_irqsave, &vss->lock) { vss->xfer_xrun =3D false; + vss->release_required =3D true; } return 0; } @@ -395,33 +429,40 @@ guard(spinlock)(&vss->lock); if (vss->preparing || !vss->prepared) return -EBADFD; + if (vss->stopped) { + substream->runtime->stop_operating =3D true; + return -EBADFD; + } if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) rc =3D virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes); - if (rc) + if (rc) { + vss->stopped =3D true; + substream->runtime->stop_operating =3D true; return rc; + } vss->xfer_enabled =3D true; } =20 msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_START, GFP_KERNEL); if (!msg) { - guard(spinlock_irqsave)(&vss->lock); - vss->xfer_enabled =3D false; - + virtsnd_pcm_start_failed(vss); return -ENOMEM; } =20 - return virtsnd_ctl_msg_send_sync(snd, msg); + rc =3D virtsnd_ctl_msg_send_sync(snd, msg); + if (rc) + virtsnd_pcm_start_failed(vss); + + return rc; case SNDRV_PCM_TRIGGER_SUSPEND: - scoped_guard(spinlock_irqsave, &vss->lock) - vss->suspended =3D true; + vss->suspended =3D true; fallthrough; case SNDRV_PCM_TRIGGER_STOP: - scoped_guard(spinlock_irqsave, &vss->lock) - vss->stopped =3D true; - fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: scoped_guard(spinlock_irqsave, &vss->lock) { + if (command !=3D SNDRV_PCM_TRIGGER_PAUSE_PUSH) + vss->stopped =3D true; vss->xfer_enabled =3D false; } =20 @@ -452,26 +493,38 @@ struct virtio_snd *snd =3D vss->snd; struct virtio_snd_msg *msg; unsigned int js =3D msecs_to_jiffies(virtsnd_msg_timeout_ms); - bool stopped; + bool need_release; int rc; =20 virtsnd_pcm_block_ack(substream); =20 + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->xfer_enabled =3D false; + need_release =3D vss->release_required; + if (need_release) + vss->stopped =3D true; + } cancel_work_sync(&vss->elapsed_period); =20 - scoped_guard(spinlock_irqsave, &vss->lock) - stopped =3D vss->stopped; - if (!stopped) - return 0; - - msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_RELEASE, - GFP_KERNEL); - if (!msg) - return -ENOMEM; - - rc =3D virtsnd_ctl_msg_send_sync(snd, msg); - if (rc) - return rc; + if (need_release) { + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_RELEASE, + GFP_KERNEL); + if (!msg) { + scoped_guard(pcm_stream_lock_irqsave, substream) + substream->runtime->stop_operating =3D true; + return -ENOMEM; + } + + rc =3D virtsnd_ctl_msg_send_sync(snd, msg); + if (rc) { + scoped_guard(pcm_stream_lock_irqsave, substream) + substream->runtime->stop_operating =3D true; + return rc; + } + + scoped_guard(spinlock_irqsave, &vss->lock) + vss->release_required =3D false; + } =20 /* * The spec states that upon receipt of the RELEASE command "the device @@ -485,6 +538,8 @@ if (rc <=3D 0) { dev_warn(&snd->vdev->dev, "SID %u: failed to flush I/O queue\n", vss->sid); + scoped_guard(pcm_stream_lock_irqsave, substream) + substream->runtime->stop_operating =3D true; =20 return !rc ? -ETIMEDOUT : rc; } From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.216.48 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845931; b=0eO5afmweUlmhw9WHOkciWAuy8A/7/jI69QoYdJBuCvXornPXUmhEjorbb+0Ca7grkWj zYgoUodiqdbIr265neiNKFP+6Wm7RT76Od7X2O9reb96m0+R6X65FE9l8L3al1+PO0CcG 4x/6F+8EOFUbzT4OQfRN0Lds1INeVnHOWM3f5I/yDXHqOjL92lZfnvdsVoDS2eYz5FMf9 oPwRykXPTkGZpKDHLgwQB1mAkr6avDgKf6z8MGtCp+kPGynESgdba1cyD+R0nQPrZMPan 4O+Ht5OENNtT6HZiWWlvpc2YV51PgTfQM65k0Y2/4cc4Uw/YTjdEP33Ji1XY1aUDJcA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845931; 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=1jlMIlqruFJ1uritPSLSHJBp9A2j8r7yMkm4OaCEFzg=; b=SF5d5asB8SzHkkYRRtZgN26Mwwl9UoQmt58S5JBD9iOygpyF9/UskjpHCfjJDindP5CR NPcYEwo6dQcy4IRaJ5pWr/2316qj8zzDyBzPYUC0ChFDM6Z+k7nJSH+8QhcK26nYgJ/nw Kfj8H2SnwyuFiGCM+iZOetw4byAcPoqXBYgRYgO0Gwrt1et4bt8y+Y9S7MwgpnClnaOWu IIBh5dGEXmN/snYeMySgcR1aJgxBbZU/9RjlWHAA+7AiIjh1EBjKCk8msCXRGFUBEH8vF DUH2GczX6i1boOyzXPOP2E5ZY6JCuL8qu4c+ofKVavrhZ2eEod3eO/ltWZ3u2LUKCHw== 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=NmfgJrlW; arc=none smtp.remote-ip=209.85.216.48 Received: from mail-pj1-f48.google.com (mail-pj1-f48.google.com [209.85.216.48]) (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 1635D606D821 for ; Thu, 5 Nov 2026 02:32:09 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 1635D606D821 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=NmfgJrlW Received: by mail-pj1-f48.google.com with SMTP id 98e67ed59e1d1-3b0a2a44a0cso726610a91.1 for ; Wed, 04 Nov 2026 18:32:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845929; x=1794450729; 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=1jlMIlqruFJ1uritPSLSHJBp9A2j8r7yMkm4OaCEFzg=; b=NmfgJrlWOsPSgUQDjsExsNnwciyEJTbAmZMOWLFDV+ljtqNMn2IWe9dJ/znPmyissK rTfp+qLTIQUQmxZ9XbkZD4Gq3DuHWUtWvLnEh0A3o5aNDvcQsifYnWaPUYicAspYO5d5 yPw1jrMOsGmSjMigG+wZiPBah2zs9EWOBcOePJAkZnxGfuNMWpc46POPGkfPilxzQzYR +ntUFJz3SBOvWkwGeX58m1OqnEz/BBEKGcK691jIUWxxzPbYGMFERUuUedPUrkVioWtC 3E6PAB1lotbKZUqPMCze/6scC6SP9KFr+ioTA0z8qQ5bDUzUMk89cPxRAeql0Etn0JJj a2Fg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845929; x=1794450729; 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=1jlMIlqruFJ1uritPSLSHJBp9A2j8r7yMkm4OaCEFzg=; b=NU1QBIZT6cYTTdPcZy81rDeFkoZ+0y49n5addwHISykQ6/fTEClin6KFyV3T8Ic987 UWo66BY/YO8eKHJgigVW3kcvfNi8/R+lvejPoR9zy2xuQooGtmkUVPp7bzjGEp1XNAtg OoqhbcdtNjWORfdR5Dkg4WpXOM7yaIxATOJ4smkgRndntHoKMuLMXIfXFUm8NKDvew5g DV6PIlvu+nJ6HCiAAZlUdu7anIpYm+Da1sNwsa1EXBsQM04Dj4zbd81CchrZQvDRkBke 7fxW4CiBw2p/NQ2x4U1/uZTaz37wKln8Nrj3DU7g9njT44u0d0VSs/+sYD4qTSMnwaQ7 5PYA== X-Forwarded-Encrypted: i=1; AKwUvBxAOm20p30/ohdxTfUrOTCGDYBuCNQ/7EVWMxLQlVj9IP/9z4kJ2aFhkvr1Nse1XBZmArCArDy+GW1/8Mw=@vger.kernel.org X-Gm-Message-State: APuU4Z9mo1YOFwfRTb0ycswN8Wrhlkr45YVamJY6OCfcmD/TBCg0Capx b10G6ZfTHwo8e3yOhLwzU29gF0Ymt3I5t1tsZ6tShUxs5G0wu4GQVl04 X-Gm-Gg: AYBFou20OrmEkOVdNGZyYPuSjK9gdtYFd/urdqkitXsC8+UkTzqxjFiFQUZ74Hns4vD UMErKrh/FdGhsgJbVI/Hr6k7mqNpiCLGgLp5OFTvM2Rz0STBFbZQQuSBss5QynotIUFBLwfr/u1 k46jkZRcQVhlEh3sa1Bgb5izKWsVi3BlG3DQXv3sTZ+RbKQZPa4Qt6k+PXUA9/+iVLa/KMslgsl o4sOnNNCyhikc6930wmUgodv0vyzhkjlM+tGQAu6QEmJqxdM05zsNaT8KUj1Qm0v10ZS/pdelTA rFPYKid/ETeZ6grWMkGtMlJUbhcnP7bhsX6UJIdjeQQ19vBm4NI/ff5VBuYkjsBDUg4UCOIyaEV Xi1EN+BojXePoukGdVv+aC1U1dPqcBvZ/4GS+n8ej83+Eq26A3gkvXZU9LUAr/hzRqWXzFp3IcX VjvdHZzSP896aHM25xThPIVEFoH1dmP/G5Qgz6x3h6tGgs6gQFke48nHyXs8AE8oFsk4julZ7Ii WKQl5JGp3caLUfVAeAk2yIhX05rA0v62xY60qdGxogWuSOYw/zy3JbhuExDuC7gOIdc50ZObcQx mX4eeGQ36zaxyMAo5dGheisDYAhICHUicNiN/y5OXwUT9pGHGMNRowbFraXslGD7FZR/8JdQdX7 EyllFRqqsJ2ZtuhGGdpW058MtF7PxxzvcosCCaI3HBAy+Hww= X-Received: by 2002:a17:90b:3f84:b0:3ab:b39f:41da with SMTP id 98e67ed59e1d1-3ae89776af6mr15049951a91.11.1793845929206; Wed, 04 Nov 2026 18:32:09 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.06 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:08 -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 v16 06/12] ALSA: virtio: Retain queued messages through device reset Date: Thu, 5 Nov 2026 11:31:34 +0900 Message-ID: <20261105023140.18373-7-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Keep the control queue available through snd_card_free() so close-time STOP and RELEASE commands can still be submitted. Block new control requests and mark pending messages canceled before reset, then release their queue references only after reset and queue deletion. For PCM queues, detach each remaining virtqueue buffer after the device has stopped DMA and retire its message without reporting a normal completion. Detach all queues before deleting them so outstanding DMA mappings are unmapped and no token remains for a late device completion. Clear each queue wrapper inside the same critical section that detaches its buffers. Check the callback's queue identity under the wrapper lock for PCM, control and event queues. This makes queue retirement explicit through teardown and restore. A forced callback test passed on both versions; the proposed detached-ring IRQ failure was not reproduced on the tested PCI transport. Protect event callback admission with the event queue lock. Disabling events waits for an active dispatch, and later callbacks see a closed gate before ALSA objects are freed. Enable and pending-event draining share the same lock. Reclaim pending event buffers after a failed probe without dispatching notifications into ALSA objects that have not been constructed. Record the queue's prior enabled state under its lock before closing admission. Fixes: 9d45e514da88 ("ALSA: virtio: handling control messages") Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_card.c | 102 ++++++++++++++++++++++++++++++++------= --- sound/virtio/virtio_card.h | 4 ++ sound/virtio/virtio_ctl_msg.c | 64 +++++++++++++++++++++++--- sound/virtio/virtio_ctl_msg.h | 4 ++ sound/virtio/virtio_pcm.h | 2 + sound/virtio/virtio_pcm_msg.c | 42 +++++++++++++++-- 6 files changed, 184 insertions(+), 34 deletions(-) diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -70,23 +70,13 @@ } } =20 -/** - * virtsnd_event_notify_cb() - Dispatch all reported events from the event= queue. - * @vqueue: Underlying event virtqueue. - * - * This callback function is called upon a vring interrupt request from the - * device. - * - * Context: Interrupt context. - */ -static void virtsnd_event_notify_cb(struct virtqueue *vqueue) -{ - struct virtio_snd *snd =3D vqueue->vdev->priv; - struct virtio_snd_queue *queue =3D virtsnd_event_queue(snd); +static void virtsnd_event_drain_locked(struct virtio_snd *snd, + struct virtqueue *vqueue) +{ struct virtio_snd_event *event; u32 length; =20 - guard(spinlock_irqsave)(&queue->lock); + lockdep_assert_held(&virtsnd_event_queue(snd)->lock); do { virtqueue_disable_cb(vqueue); while ((event =3D virtqueue_get_buf(vqueue, &length))) { @@ -94,6 +84,26 @@ virtsnd_event_send(vqueue, event, true, GFP_ATOMIC); } } while (!virtqueue_enable_cb(vqueue)); +} + +/** + * virtsnd_event_notify_cb() - Dispatch all reported events from the event= queue. + * @vqueue: Underlying event virtqueue. + * + * This callback function is called upon a vring interrupt request from the + * device. + * + * Context: Interrupt context. + */ +static void virtsnd_event_notify_cb(struct virtqueue *vqueue) +{ + struct virtio_snd *snd =3D vqueue->vdev->priv; + struct virtio_snd_queue *queue =3D virtsnd_event_queue(snd); + + guard(spinlock_irqsave)(&queue->lock); + if (queue->vqueue !=3D vqueue || !snd->event_vq_enabled) + return; + virtsnd_event_drain_locked(snd, vqueue); } =20 /** @@ -158,8 +168,12 @@ { struct virtio_snd_queue *queue =3D virtsnd_event_queue(snd); =20 + guard(spinlock_irqsave)(&queue->lock); + if (!queue->vqueue) + return; + snd->event_vq_enabled =3D true; if (!virtqueue_enable_cb(queue->vqueue)) - virtsnd_event_notify_cb(queue->vqueue); + virtsnd_event_drain_locked(snd, queue->vqueue); } =20 /** @@ -172,13 +186,19 @@ { struct virtio_snd_queue *queue =3D virtsnd_event_queue(snd); struct virtio_snd_event *event; + bool dispatch_events; u32 length; =20 + guard(spinlock_irqsave)(&queue->lock); + dispatch_events =3D snd->event_vq_enabled; + snd->event_vq_enabled =3D false; if (queue->vqueue) { - guard(spinlock_irqsave)(&queue->lock); virtqueue_disable_cb(queue->vqueue); - while ((event =3D virtqueue_get_buf(queue->vqueue, &length))) - virtsnd_event_dispatch(snd, event); + while ((event =3D virtqueue_get_buf(queue->vqueue, &length))) { + /* Reclaim used buffers even when failed probe cannot dispatch. */ + if (dispatch_events) + virtsnd_event_dispatch(snd, event); + } } } =20 @@ -338,6 +358,40 @@ } =20 /** + * virtsnd_reset_vqs() - Stop device access and unmap outstanding buffers. + * @snd: VirtIO sound device. + * + * Context: Any context that permits to sleep. + */ +static void virtsnd_reset_vqs(struct virtio_snd *snd) +{ + struct virtio_device *vdev =3D snd->vdev; + unsigned int i; + + virtio_reset_device(vdev); + + /* Deleting a virtqueue does not unmap its outstanding descriptors. */ + for (i =3D 0; i < VIRTIO_SND_VQ_MAX; ++i) { + struct virtio_snd_queue *queue =3D &snd->queues[i]; + unsigned long flags; + void *msg; + + spin_lock_irqsave(&queue->lock, flags); + if (queue->vqueue) + while ((msg =3D virtqueue_detach_unused_buf(queue->vqueue))) + if (i =3D=3D VIRTIO_SND_VQ_TX || i =3D=3D VIRTIO_SND_VQ_RX) + virtsnd_pcm_msg_cancel(msg); + if (i =3D=3D VIRTIO_SND_VQ_EVENT) + snd->event_vq_enabled =3D false; + /* IRQ callbacks must never see a detached ring as live. */ + queue->vqueue =3D NULL; + spin_unlock_irqrestore(&queue->lock, flags); + } + + vdev->config->del_vqs(vdev); +} + +/** * virtsnd_remove() - Remove VirtIO and ALSA devices. * @vdev: VirtIO parent device. * @@ -349,13 +403,14 @@ unsigned int i; =20 virtsnd_disable_event_vq(snd); - virtsnd_ctl_msg_cancel_all(snd); =20 if (snd->card) snd_card_free(snd->card); =20 - virtio_reset_device(vdev); - vdev->config->del_vqs(vdev); + virtsnd_ctl_msg_cancel_all(snd); + + virtsnd_reset_vqs(snd); + virtsnd_ctl_msg_cleanup(snd); =20 for (i =3D 0; snd->substreams && i < snd->nsubstreams; ++i) { struct virtio_pcm_substream *vss =3D &snd->substreams[i]; @@ -383,8 +438,8 @@ virtsnd_disable_event_vq(snd); virtsnd_ctl_msg_cancel_all(snd); =20 - virtio_reset_device(vdev); - vdev->config->del_vqs(vdev); + virtsnd_reset_vqs(snd); + virtsnd_ctl_msg_cleanup(snd); =20 for (i =3D 0; i < snd->nsubstreams; ++i) cancel_work_sync(&snd->substreams[i].elapsed_period); @@ -412,6 +467,7 @@ return rc; =20 virtio_device_ready(vdev); + virtsnd_ctl_msg_enable(snd); =20 virtsnd_enable_event_vq(snd); =20 diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -47,6 +47,8 @@ * @queues: Virtqueue wrappers. * @card: ALSA sound card. * @ctl_msgs: Pending control request list. + * @ctl_msgs_blocked: Control requests are blocked during teardown or susp= end. + * @event_vq_enabled: Event callbacks may dispatch, protected by the event= queue lock. * @event_msgs: Device events. * @pcm_list: VirtIO PCM device list. * @jacks: VirtIO jacks. @@ -64,6 +66,8 @@ struct virtio_snd_queue queues[VIRTIO_SND_VQ_MAX]; struct snd_card *card; struct list_head ctl_msgs; + bool ctl_msgs_blocked; + bool event_vq_enabled; struct virtio_snd_event *event_msgs; struct list_head pcm_list; struct virtio_jack *jacks; diff --git a/sound/virtio/virtio_ctl_msg.c b/sound/virtio/virtio_ctl_msg.c --- a/sound/virtio/virtio_ctl_msg.c +++ b/sound/virtio/virtio_ctl_msg.c @@ -15,6 +15,7 @@ * @list: Pending message list entry. * @notify: Request completed notification. * @ref_count: Reference count used to manage a message lifetime. + * @cancelled: Request canceled during device teardown. */ struct virtio_snd_msg { struct scatterlist sg_request; @@ -22,6 +23,7 @@ struct list_head list; struct completion notify; refcount_t ref_count; + bool cancelled; }; =20 /** @@ -100,7 +102,7 @@ =20 INIT_LIST_HEAD(&msg->list); init_completion(&msg->notify); - /* This reference is dropped in virtsnd_ctl_msg_complete(). */ + /* The caller owns this reference; send adds a queue reference. */ refcount_set(&msg->ref_count, 1); =20 return msg; @@ -134,6 +136,7 @@ int rc; =20 virtsnd_ctl_msg_ref(msg); + /* This reference stays with the queue until completion or teardown. */ =20 /* Set the default status in case the message was canceled. */ response->code =3D cpu_to_le32(VIRTIO_SND_S_IO_ERR); @@ -147,12 +150,17 @@ psgs[nouts + nins++] =3D in_sgs; =20 scoped_guard(spinlock_irqsave, &queue->lock) { - rc =3D virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, msg, - GFP_ATOMIC); + if (snd->ctl_msgs_blocked) + rc =3D -ENODEV; + else + rc =3D virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, + msg, GFP_ATOMIC); if (!rc) { notify =3D virtqueue_kick_prepare(queue->vqueue); =20 list_add_tail(&msg->list, &snd->ctl_msgs); + if (notify) + virtqueue_notify(queue->vqueue); } } =20 @@ -168,9 +176,6 @@ =20 goto on_exit; } - - if (notify) - virtqueue_notify(queue->vqueue); =20 if (nowait) goto on_exit; @@ -184,6 +189,11 @@ rc =3D -ETIMEDOUT; } =20 + goto on_exit; + } + + if (READ_ONCE(msg->cancelled)) { + rc =3D -ENODEV; goto on_exit; } =20 @@ -227,9 +237,33 @@ * virtsnd_ctl_msg_cancel_all() - Cancel all pending control messages. * @snd: VirtIO sound device. * - * Context: Any context. + * Context: Any context. Blocks new senders and marks pending requests can= celed. + * Existing senders are woken when their request completes or aft= er + * the device has been reset. */ void virtsnd_ctl_msg_cancel_all(struct virtio_snd *snd) +{ + struct virtio_snd_queue *queue =3D virtsnd_control_queue(snd); + struct virtio_snd_msg *msg; + + guard(spinlock_irqsave)(&queue->lock); + snd->ctl_msgs_blocked =3D true; + list_for_each_entry(msg, &snd->ctl_msgs, list) { + WRITE_ONCE(msg->cancelled, true); + } +} + +/** + * virtsnd_ctl_msg_cleanup() - Release messages left after queue teardown. + * @snd: VirtIO sound device. + * + * This must be called after device reset, buffer detachment, and del_vqs(= ), + * when no queue callback or DMA access can still reach a message. The que= ue + * object and its lock must remain alive until this function returns. + * + * Context: Any context. + */ +void virtsnd_ctl_msg_cleanup(struct virtio_snd *snd) { struct virtio_snd_queue *queue =3D virtsnd_control_queue(snd); =20 @@ -244,6 +278,20 @@ } =20 /** + * virtsnd_ctl_msg_enable() - Allow control requests after queue restorati= on. + * @snd: VirtIO sound device. + * + * Context: Any context. + */ +void virtsnd_ctl_msg_enable(struct virtio_snd *snd) +{ + struct virtio_snd_queue *queue =3D virtsnd_control_queue(snd); + + guard(spinlock_irqsave)(&queue->lock); + snd->ctl_msgs_blocked =3D false; +} + +/** * virtsnd_ctl_query_info() - Query the item configuration from the device. * @snd: VirtIO sound device. * @command: Control request code (VIRTIO_SND_R_XXX_INFO). @@ -295,6 +343,8 @@ u32 length; =20 guard(spinlock_irqsave)(&queue->lock); + if (queue->vqueue !=3D vqueue) + return; do { virtqueue_disable_cb(vqueue); while ((msg =3D virtqueue_get_buf(vqueue, &length))) diff --git a/sound/virtio/virtio_ctl_msg.h b/sound/virtio/virtio_ctl_msg.h --- a/sound/virtio/virtio_ctl_msg.h +++ b/sound/virtio/virtio_ctl_msg.h @@ -68,6 +68,10 @@ =20 void virtsnd_ctl_msg_cancel_all(struct virtio_snd *snd); =20 +void virtsnd_ctl_msg_cleanup(struct virtio_snd *snd); + +void virtsnd_ctl_msg_enable(struct virtio_snd *snd); + void virtsnd_ctl_msg_complete(struct virtio_snd_msg *msg); =20 int virtsnd_ctl_query_info(struct virtio_snd *snd, int command, int start_= id, 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 @@ -128,6 +128,8 @@ =20 void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss); =20 +void virtsnd_pcm_msg_cancel(struct virtio_pcm_msg *msg); + int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long o= ffset, unsigned long bytes); =20 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 @@ -305,6 +305,35 @@ { guard(spinlock_irqsave)(&vss->lock); return vss->msg_count; +} + +/** + * virtsnd_pcm_msg_cancel() - Cancel a message detached during device rese= t. + * @msg: Message returned by virtqueue_detach_unused_buf(). + * + * Reset cancellation retires the queue token without treating it as a dev= ice + * completion. In particular, it does not advance the PCM hardware pointer= or + * schedule period work. + * + * Context: Process context with the PCM queue lock held. + */ +void virtsnd_pcm_msg_cancel(struct virtio_pcm_msg *msg) +{ + struct virtio_pcm_substream *vss =3D msg->substream; + bool wake; + + { + guard(spinlock)(&vss->lock); + msg->length =3D 0; + vss->xfer_enabled =3D false; + if (WARN_ON_ONCE(!vss->msg_count)) + return; + vss->msg_count--; + wake =3D !vss->msg_count; + } + + if (wake) + wake_up_all(&vss->msg_empty); } =20 /** @@ -367,16 +396,21 @@ =20 /** * virtsnd_pcm_notify_cb() - Process all completed I/O messages. - * @queue: Underlying tx/rx virtqueue. + * @queue: Underlying tx/rx queue wrapper. + * @vqueue: Queue that raised this callback. * * Context: Interrupt context. Takes and releases the tx/rx queue spinlock. */ -static inline void virtsnd_pcm_notify_cb(struct virtio_snd_queue *queue) +static inline void +virtsnd_pcm_notify_cb(struct virtio_snd_queue *queue, struct virtqueue *vq= ueue) { struct virtio_pcm_msg *msg; u32 written_bytes; =20 guard(spinlock_irqsave)(&queue->lock); + /* Reset can detach the queue before the transport drains its IRQs. */ + if (queue->vqueue !=3D vqueue) + return; do { virtqueue_disable_cb(queue->vqueue); while ((msg =3D virtqueue_get_buf(queue->vqueue, &written_bytes))) @@ -394,7 +428,7 @@ { struct virtio_snd *snd =3D vqueue->vdev->priv; =20 - virtsnd_pcm_notify_cb(virtsnd_tx_queue(snd)); + virtsnd_pcm_notify_cb(virtsnd_tx_queue(snd), vqueue); } =20 /** @@ -407,7 +441,7 @@ { struct virtio_snd *snd =3D vqueue->vdev->priv; =20 - virtsnd_pcm_notify_cb(virtsnd_rx_queue(snd)); + virtsnd_pcm_notify_cb(virtsnd_rx_queue(snd), vqueue); } =20 /** From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.215.170 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845934; b=Nw+xbjGdtIa3sk/O98EFbraeaOnXcQlFkuQe9MAfGpcAUh0dMHgmp5vNzom7M31X0ihT I9SSyWx/6g2wArT7uHkOaadKOUx4OLfRtKdWr6loPOXamNF8ez35wqx2c7KXWmD14WCda kM2+i7BRP0qlwO0tzxtnUKLTcBxvWDRIDfHkXXUAk7975rSfqmS+idr8DwZV/iuNjsj4u 4sD16diRuQTljmRXVaSkFlzGtA/uGAnQsdLgXIJyM+dHVL5dv0IDo++wcejJf3JKdN5Fx oxdMpd64VSmjGiSEseZA4YtP3bBtLmRk+t1NsfhBSHi+HilhdvfRtNe+EuLkI/lC+aA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845934; 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=vgfdeeHEgZBQIdxBCKgRTxdF7RB7bMleD+kwgQZ9CP4=; b=g+0icKzUsFt4ldbDuSBpHjCUMWf5+XqaqoGb4wnllRsCMQvg6UfFY7wOtVIZRwjYfXfC ucPwFw9PZZpI+/ntriCMUtp+VRW961KaDijB8HDgT9HdvV9O/x7IrYPVIkiy7fAn2W0OR uQIJS18RR0dLoFvnghzs9HZohERfEhf8UmLRqL8MRJA/sNyNouB1NBk4Pqf6FFoTzFCCF R3K177Ak9p15Lt/Q8q1YjI+rGvL0us2MiruRFNrHoXNvs+Osx+oiLZ/HKMtGr/m5MtzZo vd1erLxb1hi56MQOowh8MgBIi231lpfNJiFFG2qCAvz9hrHWVeAK0eBxQw9j1p8UuMQ== 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="l+r/pK3B"; arc=none smtp.remote-ip=209.85.215.170 Received: from mail-pg1-f170.google.com (mail-pg1-f170.google.com [209.85.215.170]) (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 CDC4E606D802 for ; Thu, 5 Nov 2026 02:32:13 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org CDC4E606D802 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=l+r/pK3B Received: by mail-pg1-f170.google.com with SMTP id 41be03b00d2f7-cc1bc88a20eso956528a12.3 for ; Wed, 04 Nov 2026 18:32:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845933; x=1794450733; 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=vgfdeeHEgZBQIdxBCKgRTxdF7RB7bMleD+kwgQZ9CP4=; b=l+r/pK3BwPP+Wi2MFbnzWKxBsS5eEc38p6MIqn3rQCV/meBP3xsKwgKqKMkuSq7J8i nx8WCiprtJhxZF/Qz4Jvbsd4GOccDE+x3puCjtNQsTzhoQP31soG235dPZoJLDdjJJGR Ji0p3hxTf6tQf7z3R1UP+nkwBSnvXKJmXVkJhueCrubM5j1nAMrBZgp+Md5vAk+98Zqj JNwliJ26rBrHn1vT9IXotj31p1EsSun+BG7Z/50LncE2YU+amMMaRHrkGVxjYrHehY6F NJuV6BXsQrwc+ORTnyIV/9ykRJVHUaH/ftxgviHAAHXeMp+hf7/XXmIESVTZHkTw0DMl RM4g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845933; x=1794450733; 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=vgfdeeHEgZBQIdxBCKgRTxdF7RB7bMleD+kwgQZ9CP4=; b=xMbkVUIrv5uhYVHdt6NVJ1iP9f4V7tQ+uHRmeSvCdL1wcNazIkK9VHYGDfs951hy3o FXSHtLnMXd2Ke+6wnlOeKyZD7WO+egTJzmkWXFyYyFwK5NC6iJHB0xD5iPVRBDlZg7jf LM5+YTQ5wL1cXHjf3LNn2stI4AxZLZ5vhDMJndoUeA8FSKIChtGG6JzLoOipBuJotGc0 a0Y5iuBblwMXRdVpHJoUsngWO9DUQHVWdsMNhERLjC0KOjHIWGYd+ZUHRMJg7ZQxJ0ej oZaXS3+ej4WrXj14Z9J39+r26Ll1zUFnS4sFwtHAFoor3GqqNLkz4dTfGOpUI2E0lqON oBhw== X-Forwarded-Encrypted: i=1; AKwUvBzLDpfOl015ZQz7nA9vID9nluF2qEiNl9deTALM+6xOLj+7mVbDyOESRayanHaJjHQjojiCDZRPwRAf2K0=@vger.kernel.org X-Gm-Message-State: APuU4Z/aWIFqxMfT3k8MV/B9segDUKytl/39Mmh8XU/aKKgc1SlbHTkO ZrJDT2pqmcP/lPX6TTGArcZimw3PDUi3qkXF1eDSFVzjdXj1/3KhE/lx X-Gm-Gg: AYBFou2q9gIbyWNx3OJkZFLZptb/hjdWrRofn6DBAKuKWwKIpMFPTjMnAbi0hIn/VWo XpIKq2zkKlYildpWhTdCwweOnrc5Fnd0RFeM0GuvtZ2vlOHiSKv8RmbnnddsT+CRZwi/R91do5b dTdrgRt2BBPIWFg15CVXXazQsqfqE/OZsDGnxK5LbKlaUlQKovqJ2js60w615PKGyC9Mnm1Hygl ZYATiwtgve+Un+YQezb+CCY65cMTkYsQuMQjDlExS+NjIX0eJHcFs14O1L7neUipfpkcmx0rOFm V9yWycgCs/M6Wq+AZ6nVFNodOPxYujodZMQr/BZjsK7F0Dkx/FEmaX8UgueBybYO+RxVmCIUUZj +zwhZ4LWcvCyY9dME7ukEiAROWiEQqsB4Y1KHjzSSw3WEcLXexVdx6pQwUnof/CdvF29je7v8Iv 8bPY1tMHslSjJLCZ9Ku8O8vEFkwOxv9lbuQo9/X3w/hRUSUBxkUmYJjx8S54bDdhD4uWvlCI1xk z9k4M0Mdio/IJ/LITXpgkEeH/K26ZLPN85GmghfYXJkAB2JoplasbekLlcoJJbwCNJYEQg+kAay qq+7S5ylzwgjsEIDgphHoQXuIV8GFHyNcn9I4kOE8aPRc41ZvX+QT2uWN4NzfEYWpJ91RpGrntH G4dXC5mic8ilV3VSW3ouot3WbDdWgirC9uLjSfTdQzj7DQ2q+1qVJ6QsiaMuUCCiLGeTd X-Received: by 2002:a17:90b:4a83:b0:3a8:c0cd:57fa with SMTP id 98e67ed59e1d1-3ae897c834emr15482936a91.11.1793845932834; Wed, 04 Nov 2026 18:32:12 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.09 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:12 -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 v16 07/12] ALSA: virtio: Keep PCM DMA buffers alive through device ownership Date: Thu, 5 Nov 2026 11:31:35 +0900 Message-ID: <20261105023140.18373-8-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Retain references to the pages backing ALSA's vmalloc buffer until its queued I/O messages are freed. On NOMMU systems, use driver-owned memory for DMA and copy playback data before submission and capture data after completion, so ALSA buffer release cannot invalidate an active request. Validate the PCM buffer and each period before building scatterlists. Require a successful full-period response for ordinary capture before advancing its pointer. Each descriptor targets a fixed ring slice, so a short response would expose the unwritten remainder as captured data. Require whole-byte periods whose total matches the negotiated buffer. Check offset, length and the final message index before indexing the message array, including empty transfers and arithmetic wraparound. Seed the ordinary capture ring once per prepare. Native STOP preserves queued requests and unread samples, so PAUSE_RELEASE must retain their ownership. Re-enqueuing the entire ring adds another period to pending message lengths and exposes unread DMA slots to the device. Return consumed space through the existing indirect ACK path. Copy valid NOMMU capture payloads while their runtime is still prepared, including late non-empty completions after STOP. If the source or runtime range is invalid, retire the request and latch XRUN without publishing progress for bytes that could not be copied. Fixes: f40a28679e0b ("ALSA: virtio: handling control and I/O messages for t= he PCM device") Keep the fixed ring-slice offset available on MMU and NOMMU builds. Accept capture data only when the completed slice matches hw_ptr, and retire empty stopped completions without advancing capture state. Once an XRUN is latched, later completions only retire queue ownership. An out-of-order full completion reports XRUN rather than publishing an unwritten ring slice; this path does not reorder completed requests. Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_pcm.h | 13 ++ sound/virtio/virtio_pcm_msg.c | 303 +++++++++++++++++++++++++++++++++++--= ---- sound/virtio/virtio_pcm_ops.c | 19 ++- 3 files changed, 292 insertions(+), 43 deletions(-) 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 @@ -13,6 +13,7 @@ =20 struct virtio_pcm; struct virtio_pcm_msg; +struct page; =20 /** * struct virtio_pcm_substream - VirtIO PCM substream. @@ -29,6 +30,7 @@ * substream operators, including @stopped. * @buffer_bytes: Current buffer size in bytes. * @hw_ptr: Substream hardware pointer value in bytes [0 ... buffer_bytes). + * @capture_rx_seeded: Ordinary capture ring was queued since prepare. * @xfer_enabled: Data transfer state (0 - off, 1 - on). * @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun). * @stopped: True if the substream is stopped and must be released on the = device @@ -42,6 +44,10 @@ * @msgs: Allocated I/O messages. * @nmsgs: Number of allocated I/O messages. * @msg_count: Number of pending I/O messages in the virtqueue. + * @dma_pages: Pages backing DMA messages, retained until messages are fre= ed. + * @ndma_pages: Number of retained pages. + * @owned_dma_area: Driver-owned PCM buffer backing asynchronous messages. + * @owned_dma_bytes: Size of @owned_dma_area. * @msg_empty: Notify when msg_count is zero. */ struct virtio_pcm_substream { @@ -57,6 +63,7 @@ spinlock_t lock; size_t buffer_bytes; size_t hw_ptr; + bool capture_rx_seeded; bool xfer_enabled; bool xfer_xrun; bool stopped; @@ -68,6 +75,12 @@ struct virtio_pcm_msg **msgs; unsigned int nmsgs; unsigned int msg_count; + struct page **dma_pages; + unsigned int ndma_pages; +#ifndef CONFIG_MMU + u8 *owned_dma_area; + size_t owned_dma_bytes; +#endif wait_queue_head_t msg_empty; }; =20 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 @@ -3,6 +3,9 @@ * virtio-snd: Virtio sound device * Copyright (C) 2021 OpenSynergy GmbH */ +#include +#include +#include #include =20 #include "virtio_card.h" @@ -13,6 +16,7 @@ * @xfer: Request header payload. * @status: Response header payload. * @length: Data length in bytes. + * @data_offset: Offset of the fixed PCM ring slice for ordinary I/O. * @sgs: Payload scatter-gather table. */ struct virtio_pcm_msg { @@ -20,6 +24,7 @@ struct virtio_snd_pcm_xfer xfer; struct virtio_snd_pcm_status status; size_t length; + size_t data_offset; struct scatterlist sgs[]; }; =20 @@ -37,9 +42,8 @@ }; =20 /** - * virtsnd_pcm_sg_num() - Count the number of sg-elements required to repr= esent - * vmalloc'ed buffer. - * @data: Pointer to vmalloc'ed buffer. + * virtsnd_pcm_sg_num() - Count the sg-elements for a page-backed buffer. + * @data: Pointer to the page-backed buffer. * @length: Buffer size. * * Context: Any context. @@ -47,14 +51,18 @@ */ static int virtsnd_pcm_sg_num(u8 *data, unsigned int length) { - phys_addr_t sg_address; - unsigned int sg_length; + phys_addr_t sg_address =3D 0; + unsigned int sg_length =3D 0; int num =3D 0; =20 while (length) { struct page *pg =3D vmalloc_to_page(data); - phys_addr_t pg_address =3D page_to_phys(pg); + phys_addr_t pg_address; size_t pg_length; + + if (!pg) + return -EINVAL; + pg_address =3D page_to_phys(pg); =20 pg_length =3D PAGE_SIZE - offset_in_page(data); if (pg_length > length) @@ -75,11 +83,80 @@ return num; } =20 -/** - * virtsnd_pcm_sg_from() - Build sg-list from vmalloc'ed buffer. +#ifdef CONFIG_MMU +/** + * virtsnd_pcm_dma_pages_put() - Release retained PCM buffer pages. + * @vss: VirtIO PCM substream. + * + * Context: Process context. + */ +static void virtsnd_pcm_dma_pages_put(struct virtio_pcm_substream *vss) +{ + while (vss->ndma_pages) + put_page(vss->dma_pages[--vss->ndma_pages]); + kfree(vss->dma_pages); + vss->dma_pages =3D NULL; +} + +/** + * virtsnd_pcm_dma_pages_get() - Retain the pages backing the PCM buffer. + * @vss: VirtIO PCM substream. + * @data: PCM buffer address. + * @length: PCM buffer size. + * + * Keep the backing pages alive while virtqueue descriptors can still refe= r to + * them, even if ALSA releases the vmalloc mapping after a failed drain. + * + * Context: Process context. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_pcm_dma_pages_get(struct virtio_pcm_substream *vss, + u8 *data, size_t length) +{ + size_t i; + size_t npages; + size_t span; + + if (!data || !length || !is_vmalloc_addr(data)) + return -EINVAL; + if (vss->dma_pages || vss->ndma_pages) + return -EBUSY; + + if (check_add_overflow(length, (size_t)offset_in_page(data), &span)) + return -EOVERFLOW; + + npages =3D span / PAGE_SIZE + !!(span % PAGE_SIZE); + if (npages > UINT_MAX) + return -EOVERFLOW; + + vss->dma_pages =3D kcalloc(npages, sizeof(*vss->dma_pages), GFP_KERNEL); + if (!vss->dma_pages) + return -ENOMEM; + + for (i =3D 0; i < npages; i++) { + struct page *page =3D vmalloc_to_page(data + i * PAGE_SIZE); + + if (!page) + goto err_put_pages; + + get_page(page); + vss->dma_pages[vss->ndma_pages++] =3D page; + } + + return 0; + +err_put_pages: + virtsnd_pcm_dma_pages_put(vss); + + return -EINVAL; +} +#endif + +/** + * virtsnd_pcm_sg_from() - Build an sg-list from a page-backed buffer. * @sgs: Preallocated sg-list to populate. * @nsgs: The maximum number of elements in the @sgs. - * @data: Pointer to vmalloc'ed buffer. + * @data: Pointer to the page-backed buffer. * @length: Buffer size. * * Splits the buffer into physically contiguous parts and makes an sg-list= of @@ -132,36 +209,97 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, unsigned int periods, unsigned int period_bytes) { - struct snd_pcm_runtime *runtime =3D vss->substream->runtime; + struct snd_pcm_runtime *runtime; + size_t buffer_bytes; unsigned int i; + int rc; + + if (vss->msgs || vss->nmsgs || vss->dma_pages || vss->ndma_pages) + return -EBUSY; +#ifndef CONFIG_MMU + if (vss->owned_dma_area || vss->owned_dma_bytes) + return -EBUSY; +#endif + if (!periods || !period_bytes) + return -EINVAL; + + if (check_mul_overflow((size_t)periods, (size_t)period_bytes, + &buffer_bytes)) + return -EOVERFLOW; + + if (!vss->substream || !vss->substream->runtime) + return -EINVAL; + runtime =3D vss->substream->runtime; + if (!runtime->dma_area || buffer_bytes > runtime->dma_bytes) + return -EINVAL; + +#ifdef CONFIG_MMU + rc =3D virtsnd_pcm_dma_pages_get(vss, runtime->dma_area, buffer_bytes); + if (rc) + return rc; +#else + vss->owned_dma_area =3D kzalloc(buffer_bytes, GFP_KERNEL); + if (!vss->owned_dma_area) + return -ENOMEM; + vss->owned_dma_bytes =3D buffer_bytes; +#endif =20 vss->msgs =3D kzalloc_objs(*vss->msgs, periods); - if (!vss->msgs) - return -ENOMEM; + if (!vss->msgs) { + rc =3D -ENOMEM; + goto err_free_pages; + } =20 vss->nmsgs =3D periods; =20 for (i =3D 0; i < periods; ++i) { - u8 *data =3D runtime->dma_area + period_bytes * i; - int sg_num =3D virtsnd_pcm_sg_num(data, period_bytes); + int sg_num; struct virtio_pcm_msg *msg; + u8 *data; +#ifdef CONFIG_MMU + data =3D runtime->dma_area + (size_t)period_bytes * i; +#else + data =3D vss->owned_dma_area + (size_t)period_bytes * i; +#endif + sg_num =3D virtsnd_pcm_sg_num(data, period_bytes); + if (sg_num < 0) { + rc =3D sg_num; + goto err_free_msgs; + } =20 msg =3D kzalloc_flex(*msg, sgs, sg_num + 2); - if (!msg) - return -ENOMEM; + if (!msg) { + rc =3D -ENOMEM; + goto err_free_msgs; + } =20 msg->substream =3D vss; + vss->msgs[i] =3D msg; sg_init_one(&msg->sgs[PCM_MSG_SG_XFER], &msg->xfer, sizeof(msg->xfer)); sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], &msg->status, sizeof(msg->status)); + msg->data_offset =3D (size_t)period_bytes * i; virtsnd_pcm_sg_from(&msg->sgs[PCM_MSG_SG_DATA], sg_num, data, period_bytes); - - vss->msgs[i] =3D msg; } =20 return 0; + +err_free_msgs: + virtsnd_pcm_msg_free(vss); + return rc; + +err_free_pages: +#ifdef CONFIG_MMU + virtsnd_pcm_dma_pages_put(vss); +#else + kfree(vss->owned_dma_area); + vss->owned_dma_area =3D NULL; + vss->owned_dma_bytes =3D 0; +#endif + + return rc; } =20 /** @@ -177,6 +315,13 @@ for (i =3D 0; vss->msgs && i < vss->nmsgs; ++i) kfree(vss->msgs[i]); kfree(vss->msgs); +#ifdef CONFIG_MMU + virtsnd_pcm_dma_pages_put(vss); +#else + kfree(vss->owned_dma_area); + vss->owned_dma_area =3D NULL; + vss->owned_dma_bytes =3D 0; +#endif =20 vss->msgs =3D NULL; vss->nmsgs =3D 0; @@ -199,6 +344,7 @@ guard(spinlock_irqsave)(&vss->lock); for (i =3D 0; vss->msgs && i < vss->nmsgs; ++i) vss->msgs[i]->length =3D 0; + vss->capture_rx_seeded =3D false; } =20 /** @@ -228,8 +374,15 @@ bool notify =3D false; int rc; =20 + if (!bytes) + return 0; + if (!period_bytes || !vss->msgs || offset >=3D vss->buffer_bytes || + bytes > vss->buffer_bytes - offset) + return -EINVAL; start =3D offset / period_bytes; end =3D (offset + bytes - 1) / period_bytes; + if (end >=3D vss->nmsgs) + return -EINVAL; =20 for (i =3D start; i <=3D end; i++) { struct virtio_pcm_msg *msg =3D vss->msgs[i]; @@ -248,6 +401,33 @@ if (msg->length =3D=3D period_bytes) { msg->xfer.stream_id =3D cpu_to_le32(vss->sid); memset(&msg->status, 0, sizeof(msg->status)); + +#ifndef CONFIG_MMU + if (vss->direction =3D=3D SNDRV_PCM_STREAM_PLAYBACK) { + struct snd_pcm_runtime *runtime =3D + vss->substream ? vss->substream->runtime : NULL; + size_t data_bytes =3D msg->length; + + if (!runtime || !runtime->dma_area || !vss->owned_dma_area || + msg->data_offset > runtime->dma_bytes || + data_bytes > runtime->dma_bytes - msg->data_offset || + msg->data_offset > vss->owned_dma_bytes || + data_bytes > vss->owned_dma_bytes - msg->data_offset) { + dev_err_ratelimited(&vdev->dev, + "SID %u: invalid playback buffer for I/O message\n", + vss->sid); + msg->length -=3D n; + if (msg_count !=3D vss->msg_count && + !(vss->features & + (1U << VIRTIO_SND_PCM_F_MSG_POLLING)) && + virtqueue_kick_prepare(vqueue)) + virtqueue_notify(vqueue); + return -EINVAL; + } + memcpy(vss->owned_dma_area + msg->data_offset, + runtime->dma_area + msg->data_offset, data_bytes); + } +#endif =20 if (vss->direction =3D=3D SNDRV_PCM_STREAM_PLAYBACK) rc =3D virtqueue_add_sgs(vqueue, psgs, 2, 1, msg, @@ -356,42 +536,81 @@ size_t written_bytes) { struct virtio_pcm_substream *vss =3D msg->substream; + bool capture_invalid =3D false; =20 /* - * hw_ptr always indicates the buffer position of the first I/O message - * in the virtqueue. Therefore, on each completion of an I/O message, - * the hw_ptr value is unconditionally advanced. + * Completed requests can return captured data after STOP while the PCM + * buffer remains prepared. Once preparation ends, completions only retire + * queue ownership. */ guard(spinlock)(&vss->lock); - /* - * If the capture substream returned an incorrect status, then just - * increase the hw_ptr by the message size. - */ - if (vss->direction =3D=3D SNDRV_PCM_STREAM_PLAYBACK || - written_bytes <=3D sizeof(msg->status)) + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + (!vss->prepared || vss->xfer_xrun || + (vss->stopped && written_bytes <=3D sizeof(msg->status)))) { + msg->length =3D 0; + vss->msg_count--; + if (!vss->msg_count) + wake_up_all(&vss->msg_empty); + return; + } + + /* Only the next complete ring slice can extend captured data. */ + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + (msg->data_offset !=3D vss->hw_ptr || + written_bytes < sizeof(msg->status) || + le32_to_cpu(msg->status.status) !=3D VIRTIO_SND_S_OK || + written_bytes - sizeof(msg->status) !=3D msg->length)) { + dev_err_ratelimited(&vss->snd->vdev->dev, + "SID %u: invalid capture position, status or length\n", + vss->sid); + vss->xfer_xrun =3D true; + capture_invalid =3D true; + } +#ifndef CONFIG_MMU + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + vss->prepared && !capture_invalid && + written_bytes > sizeof(msg->status)) { + struct snd_pcm_runtime *runtime =3D + vss->substream ? vss->substream->runtime : NULL; + + if (!runtime || !runtime->dma_area || !vss->owned_dma_area || + msg->data_offset > runtime->dma_bytes || + msg->length > runtime->dma_bytes - msg->data_offset || + msg->data_offset > vss->owned_dma_bytes || + msg->length > vss->owned_dma_bytes - msg->data_offset) { + dev_err_ratelimited(&vss->snd->vdev->dev, + "SID %u: invalid capture buffer for I/O message\n", + vss->sid); + vss->xfer_xrun =3D true; + capture_invalid =3D true; + } else { + memcpy(runtime->dma_area + msg->data_offset, + vss->owned_dma_area + msg->data_offset, msg->length); + } + } +#endif + /* Publish pointer progress only for a complete TX or valid RX request. */ + if (!capture_invalid) { vss->hw_ptr +=3D msg->length; - else - vss->hw_ptr +=3D written_bytes - sizeof(msg->status); - - if (vss->hw_ptr >=3D vss->buffer_bytes) - vss->hw_ptr -=3D vss->buffer_bytes; + if (vss->hw_ptr >=3D vss->buffer_bytes) + vss->hw_ptr -=3D vss->buffer_bytes; + } =20 msg->length =3D 0; - - vss->xfer_xrun =3D false; vss->msg_count--; =20 if (vss->xfer_enabled) { - struct snd_pcm_runtime *runtime =3D vss->substream->runtime; - - runtime->delay =3D - bytes_to_frames(runtime, - le32_to_cpu(msg->status.latency_bytes)); - + if (!capture_invalid) { + struct snd_pcm_runtime *runtime =3D vss->substream->runtime; + + runtime->delay =3D + bytes_to_frames(runtime, + le32_to_cpu(msg->status.latency_bytes)); + } schedule_work(&vss->elapsed_period); - } else if (!vss->msg_count) { + } + if (!vss->msg_count) wake_up_all(&vss->msg_empty); - } } =20 /** 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 @@ -25,6 +25,8 @@ * signal or due to a timeout. In this case, the device can still access * messages even after calling ops->hw_free(). It can also issue an inte= rrupt, * and the interrupt handler will also try to access message structures. + * The messages retain references to the PCM buffer pages until they are + * freed, so late DMA does not target pages released by ALSA. * * Therefore, freeing of already allocated messages occurs: * @@ -260,10 +262,21 @@ struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_device *vdev =3D vss->snd->vdev; unsigned int msg_count; + size_t period_buffer_bytes; bool stopped; int rc; =20 virtsnd_pcm_close_ack(substream, false); + + /* Fixed descriptors require whole-byte periods covering the buffer. */ + if (check_mul_overflow((size_t)params_periods(hw_params), + (size_t)params_period_bytes(hw_params), + &period_buffer_bytes) || + period_buffer_bytes !=3D params_buffer_bytes(hw_params) || + (params_period_size(hw_params) * + (u64)snd_pcm_format_physical_width(params_format(hw_params)) * + params_channels(hw_params)) % 8) + return -EINVAL; =20 scoped_guard(spinlock_irqsave, &vss->lock) { msg_count =3D vss->msg_count; @@ -433,8 +446,12 @@ substream->runtime->stop_operating =3D true; return -EBADFD; } - if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + !vss->capture_rx_seeded) { rc =3D virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes); + if (!rc) + vss->capture_rx_seeded =3D true; + } if (rc) { vss->stopped =3D true; substream->runtime->stop_operating =3D true; From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.215.178 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845938; b=haJwfjYPnQIsdDvYr75STQcZQDQbiXD8oE4K5YYv4vdSeZq+r0bMfkc/KFtc46pFDUHs cItBj2G4Egz8iVDWsX+8msMgzhX9pZ+bsFUzEzBhS5Q6zAeC30NcLimscHFN+3187lZCB /R45dezQLhXDTDAKMt4q1gpH4+Rg8xJkgPRS2Qc4ttF9LYGp/DMQ5WZEWYwromrZo9pZb PVpriWAezsoVVkQ5P4LJXzMS17dCOWz652osmz63LgWwXx5HS9WGXPttOmlEzDNJYyip9 H650sg2v5DUz4ScQTomc5TJc//VFanBAR9iqvkLgZh5wl+aZMox9fyaQ4eR95caM2vw== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845938; 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=wN53f2o2lrw0Ep6vBL4s/UvXzf4wV7yzLC3X+qzVNBI=; b=qdE2nC4whuYs/OCj1uXM7avK/hmiWUC/ScjipmrwnLVLx5aZC27SYdAu3mEtyuqXFJtV Yu6GX+1qx/JEHPlTtP8GdfqWvN64DZT9VA20HIY6WwJ7NizZH6g4XVufYVGVzYYgVFQUd bAWy2dwp6JOX5aN8PcS4fmumnDeqZfm2f0EFxL81Koarvi/LsQHmAJeDf5ubC0+LnQqfi 21Pq256RJJbBp+MzwfPDhDS/g6tXhhjuLZCMQiH58WlaL0LxnQ+6Zm+bJiM6qbBGOi3OT YbFGjG+ZV8nmKuEUk5ZwqCr0Mec4N+yH6kwT/PPnrdDiJwc7v8P3mpFpTErVhWpF+aQ== 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=Z9UCIuO0; arc=none smtp.remote-ip=209.85.215.178 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (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 280B9606D809 for ; Thu, 5 Nov 2026 02:32:17 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 280B9606D809 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=Z9UCIuO0 Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-cc502d509c0so579364a12.2 for ; Wed, 04 Nov 2026 18:32:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845936; x=1794450736; 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=wN53f2o2lrw0Ep6vBL4s/UvXzf4wV7yzLC3X+qzVNBI=; b=Z9UCIuO0F9KtFmLbkWcijfRaMi6XgRTdK+gwkr+KKxwQkLPikMroj/8ImiGeWNskcu 5OXUwAZfjxPP3G2mzgeWEhfDsr++ZSXJ4RXFzC8Hi+5RNQgXC9Mx529OROEZ0trXWY2Z wIHzLHTDQJxeWB6T7KlxJ7AYhP1j0pF0wgLW7iGyKOUAPAosamETz9q8hRGOozJrWmAn cmQIDl61Mgw7OH7SoG754IM6a0wa8EwLRM5tvzODq8XZsyBjgyQcjSgXmyklZcv708pY nAoQKgf4Q2lLo3WSYnZA7SkFHXHNK8lDjawOjX8LgGf9umGuthjPKXEAu5G0AvkLUjfP Hb3A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845936; x=1794450736; 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=wN53f2o2lrw0Ep6vBL4s/UvXzf4wV7yzLC3X+qzVNBI=; b=QMvEnXTa7cns7QLLHmN/pUPv5Xh0q6jHUD8CVAJhcnjbPIdgzs3JYGTMAPsUDKav3w lBCck0xfVmmchk6t9T/1uSJ1s+z5eJ+gQ1AuWzgLCihKuIP4N6/4fnOYa1DfP6tOp4CU i+OzCZmkVQ6qKlmK226nTNdipfP5x9Naqt46cUWCL0kn/1PoPUaRJi2ongw+l+Ai7Etq CwOvBdiSsYfX1B9oRe4jJ+6Yu84wr5+FygjCG9VySTcQlni4bEALXxlqgsj/DQ70w5ji 6KNswQQBFUTK18hh3Cc7H3nqm4EI+pJCjSkc8eFDqSal9i2NY7GHvZVof8LyDJ87Y9lW IMJQ== X-Forwarded-Encrypted: i=1; AKwUvBxzYlbyKQL1PGIRCAi5F7yrdcuaxHMqSdafuQVGVwjJRIB81n1HOpdowNYMWeOID1hwX98cK0nVyzg9LTk=@vger.kernel.org X-Gm-Message-State: APuU4Z8YZhw0XXIqM0XaE2TQfztV0beoqo+Xpem9NT2qNfdHqmFX/cto EKPOKSauxQM5Ox2du223lFvG0vlGqI+ugklg7cxXiPDjYnWQs3D33w7O X-Gm-Gg: AYBFou1RUj1L4NQKCVde2bIKuI4sKlGdTO8jR99Sd1IVvBNdyNnVLGQvseh7owSHuJ1 hAOgsoDoZr1kd/iLyZEJOoE3UdrvzMqhZRWsZDYdcb5y8sulLY3KyHoNvjhT44PckTbMUqe6x4t 4Moo0H1AnaWU6XF1HGJOOBqUYOu+KivjsaLOLZAAbxoAIBRNybz5SGwGFx7UmL8SlhYq5GFJ/st RtALmfp/tOUvutbz75QtUcBSq/HHG7nTq1UMRGxZ0RkRXrfZwr2R+I9I4y4ylR+bpyl2yDsp5rq y6lraBz77CFsfbYc1PmM2/sQyGsyLNj9j1Rw32VQyPFq3oAFTzhtHmq6E9lJOvJhTUyv58uNCTF GmUnjk7XWjyxOSsAyACWPPudxyh7ogVx+KJeXzZKlL5ZXb85oeBdZBikMfcM8zJt2HgXXBMkWqO YVGQ3lMsaf/KrMrPKmvoJBEWdBHjyx1OBHkpQt36YT6HHeNwERyzi/Hd2cQzZLppMd7BF9nuZxP C2R666QLFHX8BSD3dqlWjosDGgjy571t12N28GQVFQWKeyoH9i1KFilm7zrB2LKxoOySusTFfC2 LSuNaLpJj7iaf2e0BjcwEakIu79pEF4Q4CiUnwWACsg+WAfcAqFvLq0F/dzXGexetHsVZlPCVRm Bz+IkgQhB+nyTNcOF6WwzieNYnfGbvw9Xl291yVt92vppdwg= X-Received: by 2002:a17:90b:4ac1:b0:3a8:211b:6c6f with SMTP id 98e67ed59e1d1-3ae898662b7mr16216560a91.67.1793845936369; Wed, 04 Nov 2026 18:32:16 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.13 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:15 -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 v16 08/12] ALSA: virtio: Keep control payloads alive through DMA completion Date: Thu, 5 Nov 2026 11:31:36 +0900 Message-ID: <20261105023140.18373-9-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Control requests can expose caller-owned payloads to the device after a synchronous send times out. Keep request and response headers and payloads in the reference-counted message so caller cleanup cannot invalidate DMA. Keep the queue reference until device completion or post-reset cleanup, even when the caller times out or is interrupted. Consume the caller's reference on every send path, and let response readers retain an extra reference through copyout. Check payload sizes and allocation arithmetic before building the scatterlists. A control value payload is also read by the CPU as a typed structure. DMA cacheline alignment alone does not guarantee the structure's CPU alignment. Align regions to the greater of the DMA cacheline and control-value type alignment, and reserve prefix slack from the buffer base so typed accesses remain aligned while scatterlist regions stay on cacheline boundaries. Keep response values in a separately allocated aligned response buffer so INTEGER64 control accesses are aligned from this patch onward. Fixes: 9d45e514da88 ("ALSA: virtio: handling control messages") Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_ctl_msg.c | 289 +++++++++++++++++++++++++++++++-------= --- sound/virtio/virtio_ctl_msg.h | 24 ++- sound/virtio/virtio_kctl.c | 121 +++++++++-------- sound/virtio/virtio_pcm_msg.c | 2=20 4 files changed, 300 insertions(+), 136 deletions(-) diff --git a/sound/virtio/virtio_ctl_msg.c b/sound/virtio/virtio_ctl_msg.c --- a/sound/virtio/virtio_ctl_msg.c +++ b/sound/virtio/virtio_ctl_msg.c @@ -3,7 +3,10 @@ * virtio-snd: Virtio sound device * Copyright (C) 2021 OpenSynergy GmbH */ +#include +#include #include +#include #include =20 #include "virtio_card.h" @@ -11,7 +14,12 @@ /** * struct virtio_snd_msg - Control message. * @sg_request: Scattergather list containing a device request (header). + * @sg_request_data: Scattergather list containing request data. * @sg_response: Scattergather list containing a device response (status). + * @sg_response_data: Scattergather list containing response data. + * @dma_buffer: Message-owned storage for all DMA-visible SG regions. + * @request_data_size: Size of the request data buffer. + * @response_data_size: Size of the response data buffer. * @list: Pending message list entry. * @notify: Request completed notification. * @ref_count: Reference count used to manage a message lifetime. @@ -19,7 +27,12 @@ */ struct virtio_snd_msg { struct scatterlist sg_request; + struct scatterlist sg_request_data; struct scatterlist sg_response; + struct scatterlist sg_response_data; + void *dma_buffer; + size_t request_data_size; + size_t response_data_size; struct list_head list; struct completion notify; refcount_t ref_count; @@ -47,8 +60,10 @@ */ void virtsnd_ctl_msg_unref(struct virtio_snd_msg *msg) { - if (refcount_dec_and_test(&msg->ref_count)) + if (refcount_dec_and_test(&msg->ref_count)) { + kfree(msg->dma_buffer); kfree(msg); + } } =20 /** @@ -71,12 +86,73 @@ void *virtsnd_ctl_msg_response(struct virtio_snd_msg *msg) { return sg_virt(&msg->sg_response); +} + +/** + * virtsnd_ctl_msg_request_data() - Get the request data buffer. + * @msg: Control message. + * + * Context: Any context. + */ +void *virtsnd_ctl_msg_request_data(struct virtio_snd_msg *msg) +{ + return msg->request_data_size ? sg_virt(&msg->sg_request_data) : NULL; +} + +/** + * virtsnd_ctl_msg_response_data() - Get the response data buffer. + * @msg: Control message. + * + * Context: Any context. + */ +void *virtsnd_ctl_msg_response_data(struct virtio_snd_msg *msg) +{ + return msg->response_data_size ? sg_virt(&msg->sg_response_data) : NULL; +} + +/** + * virtsnd_ctl_msg_sg_init() - Place one DMA region on cacheline boundarie= s. + * @sg: Scattergather entry for the region. + * @cursor: Current location in the message-owned DMA buffer. + * @size: Region length. + * @alignment: Alignment for DMA cachelines and typed CPU accesses. + * + * Context: Any context during message allocation. + * Return: 0 on success, -EOVERFLOW if the address cannot be represented. + */ +static int virtsnd_ctl_msg_sg_init(struct scatterlist *sg, u8 **cursor, + size_t size, size_t alignment) +{ + uintptr_t address =3D (uintptr_t)*cursor; + uintptr_t end; + size_t padding; + + padding =3D address % alignment; + if (padding && + check_add_overflow(address, (uintptr_t)(alignment - padding), + &address)) + return -EOVERFLOW; + + if (check_add_overflow(address, (uintptr_t)size, &end)) + return -EOVERFLOW; + + padding =3D end % alignment; + if (padding && + check_add_overflow(end, (uintptr_t)(alignment - padding), &end)) + return -EOVERFLOW; + + sg_init_one(sg, (void *)address, (unsigned int)size); + *cursor =3D (u8 *)end; + + return 0; } =20 /** * virtsnd_ctl_msg_alloc() - Allocate and initialize a control message. * @request_size: Size of request header. * @response_size: Size of response header. + * @request_data_size: Size of request data buffer. + * @response_data_size: Size of response data buffer. * @gfp: Kernel flags for memory allocation. * * The message will be automatically freed when the ref_count value is 0. @@ -85,20 +161,85 @@ * Return: Allocated message on success, NULL on failure. */ struct virtio_snd_msg *virtsnd_ctl_msg_alloc(size_t request_size, - size_t response_size, gfp_t gfp) + size_t response_size, + size_t request_data_size, + size_t response_data_size, + gfp_t gfp) { struct virtio_snd_msg *msg; - - if (!request_size || !response_size) + struct scatterlist *sgs[4]; + size_t sizes[4] =3D { + request_size, + response_size, + request_data_size, + response_data_size, + }; + size_t alignment; + size_t dma_buffer_size; + size_t aligned_size; + size_t padding; + u8 *data; + unsigned int i; + int cache_alignment; + int rc; + + if (!request_size || !response_size || request_size > UINT_MAX || + response_size > UINT_MAX || request_data_size > UINT_MAX || + response_data_size > UINT_MAX) return NULL; - - msg =3D kzalloc(sizeof(*msg) + request_size + response_size, gfp); + cache_alignment =3D dma_get_cache_alignment(); + if (cache_alignment <=3D 0) + return NULL; + /* DMA coherence does not remove the CPU's type alignment requirement. */ + alignment =3D max_t(size_t, cache_alignment, + __alignof__(struct virtio_snd_ctl_value)); + + /* Reserve prefix alignment slack before the first DMA region. */ + dma_buffer_size =3D alignment - 1; + for (i =3D 0; i < ARRAY_SIZE(sizes); ++i) { + if (!sizes[i]) + continue; + + padding =3D sizes[i] % alignment; + aligned_size =3D sizes[i]; + if (padding && + check_add_overflow(aligned_size, alignment - padding, + &aligned_size)) + return NULL; + + if (check_add_overflow(dma_buffer_size, aligned_size, + &dma_buffer_size)) + return NULL; + } + + msg =3D kzalloc(sizeof(*msg), gfp); if (!msg) return NULL; =20 - sg_init_one(&msg->sg_request, (u8 *)msg + sizeof(*msg), request_size); - sg_init_one(&msg->sg_response, (u8 *)msg + sizeof(*msg) + request_size, - response_size); + msg->dma_buffer =3D kzalloc(dma_buffer_size, gfp); + if (!msg->dma_buffer) { + kfree(msg); + return NULL; + } + data =3D msg->dma_buffer; + sgs[0] =3D &msg->sg_request; + sgs[1] =3D &msg->sg_response; + sgs[2] =3D &msg->sg_request_data; + sgs[3] =3D &msg->sg_response_data; + for (i =3D 0; i < ARRAY_SIZE(sizes); ++i) { + if (!sizes[i]) + continue; + + rc =3D virtsnd_ctl_msg_sg_init(sgs[i], &data, sizes[i], alignment); + if (rc) { + kfree(msg->dma_buffer); + kfree(msg); + return NULL; + } + } + + msg->request_data_size =3D request_data_size; + msg->response_data_size =3D response_data_size; =20 INIT_LIST_HEAD(&msg->list); init_completion(&msg->notify); @@ -112,17 +253,15 @@ * virtsnd_ctl_msg_send() - Send a control message. * @snd: VirtIO sound device. * @msg: Control message. - * @out_sgs: Additional sg-list to attach to the request header (may be NU= LL). - * @in_sgs: Additional sg-list to attach to the response header (may be NU= LL). * @nowait: Flag indicating whether to wait for completion. * * Context: Any context. Takes and releases the control queue spinlock. * May sleep if @nowait is false. + * Consumes the caller's reference on every return path. * Return: 0 on success, -errno on failure. */ int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *ms= g, - struct scatterlist *out_sgs, - struct scatterlist *in_sgs, bool nowait) + bool nowait) { struct virtio_device *vdev =3D snd->vdev; struct virtio_snd_queue *queue =3D virtsnd_control_queue(snd); @@ -133,6 +272,7 @@ unsigned int nins =3D 0; struct scatterlist *psgs[4]; bool notify =3D false; + unsigned long flags; int rc; =20 virtsnd_ctl_msg_ref(msg); @@ -142,27 +282,27 @@ response->code =3D cpu_to_le32(VIRTIO_SND_S_IO_ERR); =20 psgs[nouts++] =3D &msg->sg_request; - if (out_sgs) - psgs[nouts++] =3D out_sgs; + if (msg->request_data_size) + psgs[nouts++] =3D &msg->sg_request_data; =20 psgs[nouts + nins++] =3D &msg->sg_response; - if (in_sgs) - psgs[nouts + nins++] =3D in_sgs; - - scoped_guard(spinlock_irqsave, &queue->lock) { - if (snd->ctl_msgs_blocked) - rc =3D -ENODEV; - else - rc =3D virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, - msg, GFP_ATOMIC); - if (!rc) { - notify =3D virtqueue_kick_prepare(queue->vqueue); - - list_add_tail(&msg->list, &snd->ctl_msgs); - if (notify) - virtqueue_notify(queue->vqueue); - } - } + if (msg->response_data_size) + psgs[nouts + nins++] =3D &msg->sg_response_data; + + spin_lock_irqsave(&queue->lock, flags); + if (snd->ctl_msgs_blocked) + rc =3D -ENODEV; + else + rc =3D virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, msg, + GFP_ATOMIC); + if (!rc) { + notify =3D virtqueue_kick_prepare(queue->vqueue); + + list_add_tail(&msg->list, &snd->ctl_msgs); + if (notify) + virtqueue_notify(queue->vqueue); + } + spin_unlock_irqrestore(&queue->lock, flags); =20 if (rc) { dev_err(&vdev->dev, "failed to send control message (0x%08x)\n", @@ -173,46 +313,36 @@ * called, it is necessary to decrement the reference count. */ virtsnd_ctl_msg_unref(msg); - - goto on_exit; - } - - if (nowait) - goto on_exit; - - rc =3D wait_for_completion_interruptible_timeout(&msg->notify, js); - if (rc <=3D 0) { + } else if (!nowait) { + rc =3D wait_for_completion_interruptible_timeout(&msg->notify, js); if (!rc) { dev_err(&vdev->dev, "control message (0x%08x) timeout\n", le32_to_cpu(request->code)); rc =3D -ETIMEDOUT; + } else if (rc > 0) { + if (READ_ONCE(msg->cancelled)) { + rc =3D -ENODEV; + } else { + switch (le32_to_cpu(response->code)) { + case VIRTIO_SND_S_OK: + rc =3D 0; + break; + case VIRTIO_SND_S_NOT_SUPP: + rc =3D -EOPNOTSUPP; + break; + case VIRTIO_SND_S_IO_ERR: + rc =3D -EIO; + break; + default: + rc =3D -EINVAL; + break; + } + } } - - goto on_exit; - } - - if (READ_ONCE(msg->cancelled)) { - rc =3D -ENODEV; - goto on_exit; - } - - switch (le32_to_cpu(response->code)) { - case VIRTIO_SND_S_OK: - rc =3D 0; - break; - case VIRTIO_SND_S_NOT_SUPP: - rc =3D -EOPNOTSUPP; - break; - case VIRTIO_SND_S_IO_ERR: - rc =3D -EIO; - break; - default: - rc =3D -EINVAL; - break; - } - -on_exit: + } + + /* Drop the reference consumed by this send attempt. */ virtsnd_ctl_msg_unref(msg); =20 return rc; @@ -308,10 +438,23 @@ { struct virtio_snd_msg *msg; struct virtio_snd_query_info *query; - struct scatterlist sg; + size_t info_size; + int rc; + + if (count < 0) + return -EINVAL; + + if (size > UINT_MAX || + check_mul_overflow((size_t)count, size, &info_size) || + info_size > UINT_MAX) + return -EOVERFLOW; + + if (info_size && !info) + return -EINVAL; =20 msg =3D virtsnd_ctl_msg_alloc(sizeof(*query), - sizeof(struct virtio_snd_hdr), GFP_KERNEL); + sizeof(struct virtio_snd_hdr), 0, info_size, + GFP_KERNEL); if (!msg) return -ENOMEM; =20 @@ -321,9 +464,13 @@ query->count =3D cpu_to_le32(count); query->size =3D cpu_to_le32(size); =20 - sg_init_one(&sg, info, count * size); - - return virtsnd_ctl_msg_send(snd, msg, NULL, &sg, false); + virtsnd_ctl_msg_ref(msg); + rc =3D virtsnd_ctl_msg_send(snd, msg, false); + if (!rc && info_size) + memcpy(info, virtsnd_ctl_msg_response_data(msg), info_size); + virtsnd_ctl_msg_unref(msg); + + return rc; } =20 /** diff --git a/sound/virtio/virtio_ctl_msg.h b/sound/virtio/virtio_ctl_msg.h --- a/sound/virtio/virtio_ctl_msg.h +++ b/sound/virtio/virtio_ctl_msg.h @@ -21,20 +21,28 @@ void *virtsnd_ctl_msg_response(struct virtio_snd_msg *msg); =20 struct virtio_snd_msg *virtsnd_ctl_msg_alloc(size_t request_size, - size_t response_size, gfp_t gfp); + size_t response_size, + size_t request_data_size, + size_t response_data_size, + gfp_t gfp); + +void *virtsnd_ctl_msg_request_data(struct virtio_snd_msg *msg); + +void *virtsnd_ctl_msg_response_data(struct virtio_snd_msg *msg); =20 int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *ms= g, - struct scatterlist *out_sgs, - struct scatterlist *in_sgs, bool nowait); + bool nowait); =20 /** * virtsnd_ctl_msg_send_sync() - Simplified sending of synchronous message. * @snd: VirtIO sound device. * @msg: Control message. * - * After returning from this function, the message will be deleted. If mes= sage - * content is still needed, the caller must additionally to - * virtsnd_ctl_msg_ref/unref() it. + * This function consumes the caller's message reference on return. A mess= age + * accepted by the control queue keeps its queue reference until completio= n or + * post-reset cleanup, including when this function returns on timeout or + * interruption. Callers that read the response after success must retain = an + * additional reference through that access. * * The msg_timeout_ms module parameter defines the message completion time= out. * If the message is not completed within this time, the function will ret= urn an @@ -49,7 +57,7 @@ static inline int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd, struct virtio_snd_msg *msg) { - return virtsnd_ctl_msg_send(snd, msg, NULL, NULL, false); + return virtsnd_ctl_msg_send(snd, msg, false); } =20 /** @@ -63,7 +71,7 @@ static inline int virtsnd_ctl_msg_send_async(struct virtio_snd *snd, struct virtio_snd_msg *msg) { - return virtsnd_ctl_msg_send(snd, msg, NULL, NULL, true); + return virtsnd_ctl_msg_send(snd, msg, true); } =20 void virtsnd_ctl_msg_cancel_all(struct virtio_snd *snd); diff --git a/sound/virtio/virtio_kctl.c b/sound/virtio/virtio_kctl.c --- a/sound/virtio/virtio_kctl.c +++ b/sound/virtio/virtio_kctl.c @@ -3,8 +3,10 @@ * virtio-snd: Virtio sound device * Copyright (C) 2022 OpenSynergy GmbH */ +#include +#include +#include #include -#include =20 #include "virtio_card.h" =20 @@ -157,14 +159,16 @@ struct virtio_snd_ctl_hdr *hdr; struct virtio_snd_ctl_value *kvalue; size_t request_size =3D sizeof(*hdr); - size_t response_size =3D sizeof(struct virtio_snd_hdr) + sizeof(*kvalue); + size_t response_size =3D sizeof(struct virtio_snd_hdr); unsigned int i; int rc; =20 - msg =3D virtsnd_ctl_msg_alloc(request_size, response_size, GFP_KERNEL); + msg =3D virtsnd_ctl_msg_alloc(request_size, response_size, 0, + sizeof(*kvalue), GFP_KERNEL); if (!msg) return -ENOMEM; =20 + /* Response lifetime through ALSA value copyout. */ virtsnd_ctl_msg_ref(msg); =20 hdr =3D virtsnd_ctl_msg_request(msg); @@ -175,8 +179,7 @@ if (rc) goto on_failure; =20 - kvalue =3D (void *)((u8 *)virtsnd_ctl_msg_response(msg) + - sizeof(struct virtio_snd_hdr)); + kvalue =3D virtsnd_ctl_msg_response_data(msg); =20 switch (type) { case VIRTIO_SND_CTL_TYPE_BOOLEAN: @@ -233,7 +236,8 @@ size_t response_size =3D sizeof(struct virtio_snd_hdr); unsigned int i; =20 - msg =3D virtsnd_ctl_msg_alloc(request_size, response_size, GFP_KERNEL); + msg =3D virtsnd_ctl_msg_alloc(request_size, response_size, 0, 0, + GFP_KERNEL); if (!msg) return -ENOMEM; =20 @@ -288,22 +292,27 @@ struct virtio_snd *snd =3D snd_kcontrol_chip(kcontrol); struct virtio_snd_msg *msg; struct virtio_snd_ctl_hdr *hdr; - unsigned int *tlv; - struct scatterlist sg; + size_t request_data_size =3D 0; + size_t response_data_size =3D 0; int rc; =20 + switch (op_flag) { + case SNDRV_CTL_TLV_OP_READ: + response_data_size =3D size; + break; + case SNDRV_CTL_TLV_OP_WRITE: + case SNDRV_CTL_TLV_OP_CMD: + request_data_size =3D size; + break; + default: + return -EINVAL; + } + msg =3D virtsnd_ctl_msg_alloc(sizeof(*hdr), sizeof(struct virtio_snd_hdr), + request_data_size, response_data_size, GFP_KERNEL); if (!msg) return -ENOMEM; - - tlv =3D kzalloc(size, GFP_KERNEL); - if (!tlv) { - rc =3D -ENOMEM; - goto on_msg_unref; - } - - sg_init_one(&sg, tlv, size); =20 hdr =3D virtsnd_ctl_msg_request(msg); hdr->control_id =3D cpu_to_le32(kcontrol->private_value); @@ -312,13 +321,16 @@ case SNDRV_CTL_TLV_OP_READ: hdr->hdr.code =3D cpu_to_le32(VIRTIO_SND_R_CTL_TLV_READ); =20 - rc =3D virtsnd_ctl_msg_send(snd, msg, NULL, &sg, false); - if (!rc) { - if (copy_to_user(utlv, tlv, size)) - rc =3D -EFAULT; - } - - break; + /* Response lifetime through user copyout. */ + virtsnd_ctl_msg_ref(msg); + rc =3D virtsnd_ctl_msg_send(snd, msg, false); + if (!rc && size && + copy_to_user(utlv, virtsnd_ctl_msg_response_data(msg), + size)) + rc =3D -EFAULT; + virtsnd_ctl_msg_unref(msg); + + return rc; case SNDRV_CTL_TLV_OP_WRITE: case SNDRV_CTL_TLV_OP_CMD: if (op_flag =3D=3D SNDRV_CTL_TLV_OP_WRITE) @@ -327,28 +339,16 @@ hdr->hdr.code =3D cpu_to_le32(VIRTIO_SND_R_CTL_TLV_COMMAND); =20 - if (copy_from_user(tlv, utlv, size)) { - rc =3D -EFAULT; - goto on_msg_unref; - } else { - rc =3D virtsnd_ctl_msg_send(snd, msg, &sg, NULL, false); + if (size && copy_from_user(virtsnd_ctl_msg_request_data(msg), utlv, + size)) { + virtsnd_ctl_msg_unref(msg); + return -EFAULT; } =20 - break; - default: - rc =3D -EINVAL; - /* We never get here - we listed all values for op_flag */ - WARN_ON(1); - goto on_msg_unref; - } - kfree(tlv); - return rc; - -on_msg_unref: - virtsnd_ctl_msg_unref(msg); - kfree(tlv); - - return rc; + return virtsnd_ctl_msg_send(snd, msg, false); + } + + return -EINVAL; } =20 /** @@ -369,27 +369,36 @@ struct virtio_snd_msg *msg; struct virtio_snd_ctl_hdr *hdr; unsigned int n =3D le32_to_cpu(kinfo->value.enumerated.items); - struct scatterlist sg; - - msg =3D virtsnd_ctl_msg_alloc(sizeof(*hdr), - sizeof(struct virtio_snd_hdr), GFP_KERNEL); - if (!msg) - return -ENOMEM; + size_t items_size; + int rc; + + if (check_mul_overflow((size_t)n, sizeof(*kctl->items), &items_size) || + items_size > UINT_MAX) + return -EOVERFLOW; =20 kctl->items =3D devm_kcalloc(&vdev->dev, n, sizeof(*kctl->items), GFP_KERNEL); - if (!kctl->items) { - virtsnd_ctl_msg_unref(msg); - return -ENOMEM; - } - - sg_init_one(&sg, kctl->items, n * sizeof(*kctl->items)); + if (!kctl->items) + return -ENOMEM; + + msg =3D virtsnd_ctl_msg_alloc(sizeof(*hdr), + sizeof(struct virtio_snd_hdr), 0, items_size, + GFP_KERNEL); + if (!msg) + return -ENOMEM; =20 hdr =3D virtsnd_ctl_msg_request(msg); hdr->hdr.code =3D cpu_to_le32(VIRTIO_SND_R_CTL_ENUM_ITEMS); hdr->control_id =3D cpu_to_le32(cid); =20 - return virtsnd_ctl_msg_send(snd, msg, NULL, &sg, false); + /* Item-buffer lifetime through the control-list copyout. */ + virtsnd_ctl_msg_ref(msg); + rc =3D virtsnd_ctl_msg_send(snd, msg, false); + if (!rc && items_size) + memcpy(kctl->items, virtsnd_ctl_msg_response_data(msg), items_size); + virtsnd_ctl_msg_unref(msg); + + return rc; } =20 /** 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 @@ -687,7 +687,7 @@ break; } =20 - msg =3D virtsnd_ctl_msg_alloc(request_size, response_size, gfp); + msg =3D virtsnd_ctl_msg_alloc(request_size, response_size, 0, 0, gfp); if (msg) { struct virtio_snd_pcm_hdr *hdr =3D virtsnd_ctl_msg_request(msg); From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.214.176 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845940; b=LtkeMlTyq+/IRiLoxn3wvq7+qia1/c8R/WRjQfz7JU57u+QEbnUZ51TR61v8gFARiSHz /FhPTiPTDxF0DgmXKBN3CVMsOqaifrdQCQHzQF4CTISiyD403pI8JCPpeE4ezE6vSWTSc KGpaqCqHS5ttYmvYKN9zD2GHADra0Twa330wHHCf1IUepgvzRFGqpMgiCEC40jvODJMiG ejuVNfNoQO7Rat2GPpSmLvkr7g9aGgZx08FvjhHYFkjJ0jK/YkKaqs+KKig/af4CP2oHS iR8CoKEYtyq2gWdz8P8VfcPzh+3Vybtz1EeCRfWkk9R++CzOBFOIWBnW5fMqrjMCn2Q== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845940; 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=+MrFgURXGVOlsfMgOcHhXpqYFBlUXyLFf4QG/xpHN8w=; b=x4ICInH5804ayZJKFv56FqT2FHwHbIFPq7SJoqzsFsDWNOlzFdP5nitJgCW7pUU8R8HX ebqGgqtUI+C1LNAglVZlOmZt6k85UO6RrpUDAaRHHiLN8Y22DIOe2Hdrgf/h8R1NR6k1T swSiGyyoseYo5XSuc52NjyYSD4fg/gbVZ1ZdC6wXUwAHGqokj4CnVemQhNWAyVjklUeRh EPScWaoo846OnFuJ03n+ETggJhgmQJDu/MIi8Yl4CXN3fKp+0Ib4fJC0V8l77TwNphrbY bet+chr/k9wryebUhG8r9UXCOefDSCVn2L4XofjO3evvWyI8ZKBvRB1YDW/5Q+JtY9g== 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=Vv9lVfIq; arc=none smtp.remote-ip=209.85.214.176 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (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 6814C606EE90 for ; Thu, 5 Nov 2026 02:32:20 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 6814C606EE90 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=Vv9lVfIq Received: by mail-pl1-f176.google.com with SMTP id d9443c01a7336-2ed1b9e8f43so3979705ad.1 for ; Wed, 04 Nov 2026 18:32:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845940; x=1794450740; 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=+MrFgURXGVOlsfMgOcHhXpqYFBlUXyLFf4QG/xpHN8w=; b=Vv9lVfIq/uGJ92iz3/mvZUy3hIePG5UElXuauRlR+iqCnP1sx89GAXQrx8D/iWJip2 kgeN5AR6Liz5URIgTLQAdRyX1C1KnS0LuPPef9+gy28JTiTIKb1MARzID0UUJHPtOi+v 8oJGsVAedydWXl7xRsaokKjNL0+fAxnMU6+YRgLomvXnaFvwdMlRDCYrztExIUp5fUXB m/yyffdRUaxVA36XvGjx/gNHck3rnE7vj8e8CU+kIvJPZnuSsuCzP3vw8PIb9FdPhjMa Aw3dCk5rUz8ViMTwH2xCdMS6gRGCr7VhnelEvinwourcZhIdLf280N9xO0ZSzRTt9TEd WnDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845940; x=1794450740; 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=+MrFgURXGVOlsfMgOcHhXpqYFBlUXyLFf4QG/xpHN8w=; b=Lp4/LAXAhmbcR0poKCi7xqepRf3R8LC5wVjNfhQKTZZ0TW4kFlbxS9+64oic6Hlm8u O26s3X8c5ZzApZT98lYkHAahr7P8Z86NaHjXH7TjTEYBu8ETUBru4cAKRPIGL2PIGKGk WYHEwgtUy7/nGudL/jC9zbCPVsn/WM+MT1GVVQecf+9va+VTh7+M82GPuWrgpxAayogi 4BBcZ3r1DvNYnqXpY2DzluA6jG53obOc0GX25DVS1cBiGwnKN5VsfEjBxTYpromm5nSS sFGtDWuk1eEXplRjzbBsfGgqMJ37f9ICpZI4+Xn3Pfhy4zrcDyJ/J8Tq8WSzntGLTK8X Sl1w== X-Forwarded-Encrypted: i=1; AKwUvByYWQEXhRRMq6IxXtTtDCYKabecqQwy05AMXimvwnK1+xjLmXKbeVFS5Jwuo4glJvqfsPYRvdh5+okZhuk=@vger.kernel.org X-Gm-Message-State: APuU4Z8woNEtd1rOk1n9fWsuXFDqc3E02DPETUs11uL5m4Ot7pfIrCLa 683Oi5I+3Lh2ZaDF1qZPreGGLjMYZCvS1cl5mfGFZL8MKGPc8ZpM+jYe X-Gm-Gg: AYBFou18ljMrsNRUDI/btCtUdMIOrlIa5KKbRjP0Iuw2A0nvzUBAbc0BeCmf2wQiHv5 P4BhVSfN88GSGuDcpFp7LUsIYyjPAFJXY1O0fGDqHqG8Un+x/auwLg3vDLj6A3nj7o+AbK2xMs9 4wdh3zcCuwAjO2YN0cYF2LUxy8Qdv3L7t2ztI/FAwwqs23/0Y9BBGpebylKt9mB61kwv4Rl9YqJ 2gr8RwHU7S2NUAwluggKYwOcoKEHvc8Z6mcEm/iMrWFbHW2h3AhXvK2iarA157aJ+/gz0jJ9Jf2 kdwKXk1FpP4DGpPN6DUNrGpo6uF0hzlBjd6FbKFqLzoVtnoQ5wwPjhJwcqYVCbovNhjxKr1yjDb r++HdIQHwJztnPnT58CG4Xg8dX9cuip8zJ+AjYCi3DKvj+nMFnMaccRycpAhcirfkad1Vyjmaek ID95V0PrJvoE8Gu9ICV12vykPcveSfK0rE7iR4ESflidmoDXXz0dPVBM7FI8oEYC3IawPm9k2yU KBs3aI9lBSFHftBIPWM4u7+IJlcHVvCbl+ULGP4uOZrEI41YYRXKNUCul2fxUlIqtd8LVcrPm7P 7z60qtAim7c42Al6BhCH/ETXl+bpLenfTncnFgDpUVYRxpThBzCWfgM727j3HbcHmliWlF2YVeo too3BMVgBg2awAeWWnqWVe7X0Q0TWZDFNQk8uJUZUE+mBe2cDKtSWJIt/CBRObtpYMbJq X-Received: by 2002:a17:90b:35c8:b0:3ac:9a5e:9ce7 with SMTP id 98e67ed59e1d1-3ae897aa7aemr15044527a91.49.1793845939762; Wed, 04 Nov 2026 18:32:19 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.16 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:19 -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 v16 09/12] ALSA: virtio: wait through signals for PCM teardown Date: Thu, 5 Nov 2026 11:31:37 +0900 Message-ID: <20261105023140.18373-10-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" A signal can end the interruptible wait while a STOP or RELEASE request is still pending. A later teardown may then submit another RELEASE before the first request completes. Keep both waits non-interruptible and use the existing message timeout. Other control requests remain interruptible. Device reset still cancels the pending request and wakes the waiter. Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_ctl_msg.c | 15 ++++++++++++++- sound/virtio/virtio_ctl_msg.h | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/sound/virtio/virtio_ctl_msg.c b/sound/virtio/virtio_ctl_msg.c --- a/sound/virtio/virtio_ctl_msg.c +++ b/sound/virtio/virtio_ctl_msg.c @@ -314,7 +314,20 @@ */ virtsnd_ctl_msg_unref(msg); } else if (!nowait) { - rc =3D wait_for_completion_interruptible_timeout(&msg->notify, js); + /* + * Teardown must observe completion even with a pending signal. + * Otherwise a subsequent open can retry RELEASE while the device + * is still processing the previous teardown request. + */ + switch (le32_to_cpu(request->code)) { + case VIRTIO_SND_R_PCM_STOP: + case VIRTIO_SND_R_PCM_RELEASE: + rc =3D wait_for_completion_timeout(&msg->notify, js); + break; + default: + rc =3D wait_for_completion_interruptible_timeout(&msg->notify, js); + break; + } if (!rc) { dev_err(&vdev->dev, "control message (0x%08x) timeout\n", diff --git a/sound/virtio/virtio_ctl_msg.h b/sound/virtio/virtio_ctl_msg.h --- a/sound/virtio/virtio_ctl_msg.h +++ b/sound/virtio/virtio_ctl_msg.h @@ -47,6 +47,9 @@ * The msg_timeout_ms module parameter defines the message completion time= out. * If the message is not completed within this time, the function will ret= urn an * error. + * PCM STOP and RELEASE wait through pending signals within this timeout. + * Other requests remain interruptible. Device reset cancels and wakes eit= her + * kind of waiter. * * Context: Any context that permits to sleep. * Return: 0 on success, -errno on failure. From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.215.175 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845947; b=zqBpYW/fv8l7dJYWgDpFM1yBcfaCseGiBNlclco4E+oUg50slGbxTSwTqD0U61JGNZiH ucasriPZrNOxR0i/kcEjeLXe2lsw68MFGOITfnFbb8xM00/I6v1rywnjR/d2sU5jCxJxQ rq2/ueZCc3FlZCKgCnc9OM8oX7LNlE6JlzGLcAw2qmZgWDyrNT6YdNztUNzz/39sCHbvG UsCdts868+je8VHYDkcPVOMbxavm2Kvcrvh9sjkcbgnjJCPxq/tlu8nu5Z/JuimYHg25X ZBzLGZlBt+Cn9wdd+yzX9QpKLqy0UJRPVw2JyhCELDI1XS6abA2dHtvdiod+IeXW/Sg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845947; 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=kNurRCmrSxZa3+JRRpNKYM0CZeMbDH3Sog4latEc7kk=; b=LhRP5aznYq/mW43gES5dgTMzYyRqG2vBBIl375aG+tAHi4oD8aQk6w+aqxXxxq6wVBMG YEk0AtMdF06KHJ0qCaEucmjbjeD+qK94npu2hVw1T/pcNanVqaxQdDsXRahpTOy5T46kd hDq/FVKYptJTQzw++daw+0+StZFMwce0Ybm2eCEjSdacab8bumbOkOCwJWpTIMWj1g+5e MmUig0o1Huu17jUy9vqjEBRzmB7xRazbUk+SGeJfhFXH+uBkaB3FAA6kMO+dDkn3GZTov CoR3JrPkaminWwDhFJlXcDmA8LWVAg39kGNIURoQi/emX06RroDeFAKvZU58vqsXuJA== 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=GbA+slDM; arc=none smtp.remote-ip=209.85.215.175 Received: from mail-pg1-f175.google.com (mail-pg1-f175.google.com [209.85.215.175]) (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 57314606D814 for ; Thu, 5 Nov 2026 02:32:26 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 57314606D814 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=GbA+slDM Received: by mail-pg1-f175.google.com with SMTP id 41be03b00d2f7-cdadf7e248aso621624a12.3 for ; Wed, 04 Nov 2026 18:32:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845946; x=1794450746; 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=kNurRCmrSxZa3+JRRpNKYM0CZeMbDH3Sog4latEc7kk=; b=GbA+slDM0tqCuGeWM9YGB2JHdCI+ifn44FX2flZBk7gDexBYDfRiiC0AvV6o+7pjlt sBWVrfq/V1E4WAFtJcEB78FVZp557NCQRNS8X6pupghxUSOwcQ3OzKpKKBpB4kadGS9D bP2Iehayqjg+drmLc2det65bpD24pJF/l+c9ttfD5hF3chQoNCarbJPHeHldNIhl0FEZ 5WpnpMKXpQwm1gm5MwarPOSkf5lTLU+Wr+3ZPGcFFiIUNccTHtoZBjKp/kjSxIEOyipB VU5l/w2e28wXJyrx6JVBRgoDxYLc3Tbkoh6Lb/QNq7ArsUXripLLA9ZKHdGDCKj5dBdL dflw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845946; x=1794450746; 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=kNurRCmrSxZa3+JRRpNKYM0CZeMbDH3Sog4latEc7kk=; b=t/AQihZSPTsiCZCjxLWI7Mh/Vlgb8JdTZJ7EPfUYLseH4f2kdoet2V2GGjWHhWganm pdZniDGxn1mwtZKPbTG6UE032BdKWjRvEnvkKoacHm6hGb5vhFmT7jugkMe13qDaJ0Dw WRS/CZ7WkeAHuXUuMSviG5F3zX+pV99SGTDXEbnTwMSprP1or0w1+W/2qmLGy/zQNWTt eqSYJ0atfWA/35ezqmmf/chDQOxVtkDwt8Mgcw8XIUGD41vvNImE3583AJ5ub1orV2zv lLw9MD+UerUzQuUDx7Ne3p8AUViiw/fmOlmS00ufqcs7I+2UEafZ6b5oA7TSviz5Ao1g FLKg== X-Forwarded-Encrypted: i=1; AKwUvBz/4ZIY/nIllNq4wrzfS5HIZ/RkFS0to8/kjTQUKUaSc4T8PMWaaFJ/4Hxia+8CdJt8spwFTiRqZAc1iZ8=@vger.kernel.org X-Gm-Message-State: APuU4Z+ILoXrt8jQBbR0Voz70W2XLgQtLHAMpbTKCZ6FFRWFqaaO+36j nKZIBNbZMenIF560icAtYQJ4bIXlBwKb94zfTVZJe6sVDOugCCDx3Z1l X-Gm-Gg: AYBFou1GEGfDEUVRvYRqz09kuD8Tm3fJrzZLEk+7BvGiM1FI+Qn0RRjLqu4dAUZ5ajV sBTyh30tsb8zQt2ZD5BvDWD1R3lTD/x0kTVL9Gcj/h9oa58eP4iCELQqclfMgintB5nFrNuSfdA OFmBql+4+kVA/eC7EZzmg6dp6MN6kvnMStKH3KcoU9tZVD39ssPwwI/+3ufLnC25w7czeSe5Of7 OicEJo9eftl7tIObUfgez+OSRdmq4P9Sruk0QWAe0VkDmBYQS8R9oerOSB1AV88EvLBiSdpq47J tkFRD5nz27NmU8kq9/N/S/QHcJobg/EW2ZIu1OOuoY27H3Azds5uKul1BV5Ct/fWb0RhnNInq50 jtPgQu1+RPrWOc8VCcPGqAyrYtz73KkSjPDdDYI5eN1xFY5kOIvhPm9t1rE1Q7vc1/QBZHPedpI hSAdHExVDHBk9kyFpI7lb/mMXHrc+xkrR00ID8PPb7pgHe7JVdPbcXQ2zezXsnJs8QgI+CgNzpU 1w8sXVvJNjMuMlfR5kft/ZCjpgMVrEEHWIPXrQuTQRlH1YDY3IWjBJuvw2eTy2PQ018gSTRaouP +FfP4mvEMz4SNhKKUbvI+1dY3Pe9AIzkJ/l8wo/j3X62a7GfVkh9Nfbbxf0KNDxI5+z5pQDLaiS gSCGRcdOVAkMKJs+nNzQJyIIpNx8k7Wi/QChlcITEyUaPVKk6 X-Received: by 2002:a17:90b:2d90:b0:3ac:9e76:aa9b with SMTP id 98e67ed59e1d1-3ae8957cca9mr14579899a91.7.1793845944933; Wed, 04 Nov 2026 18:32:24 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.20 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:23 -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 v16 10/12] ALSA: virtio: add staged capture and serialize PCM lifecycle accounting Date: Thu, 5 Nov 2026 11:31:38 +0900 Message-ID: <20261105023140.18373-11-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Use request-owned RX storage and separate produced, observed and consumed positions for shared capture clients. Serialize lifecycle operations with lifecycle_lock and ACK accounting with the PCM stream lock. Close ACK admission before any sleeping teardown and keep prepare's gate through the core pointer reset. Reject unavailable epochs with -EBADFD. Real transfer XRUNs arm stop_operating in ACK and pause/restart triggers, so DROP and prepare retire the remaining device state. Track RELEASE completion and rearm release_required immediately after a successful resume PREPARE. Publish the resumed prepared state before RX submission because a device can complete RX before the START response. Record suspend state even when the stopped stream has no queued messages. Account staged ACK progress in runtime->min_align frames. Advance the byte counters and application cursor by that same aligned amount, keeping any fractional-byte frame tail for the next ACK, including boundary wrap. Complete capture's private cursor rebase on every post-reset error path while retaining its XRUN latch. Playback RESET with device-owned TX requests returns -EBUSY before changing either pointer. With no pending TX, rebase all indirect offsets and clear partial message fills together. Applications can use DROP/PREPARE before retrying a busy RESET. This return-value change needs maintainer review. Teardown disables period work until the next successful prepare. After device reset and queue deletion, complete pending control messages before waiting for period work that may itself be blocked on a control reply. Keep full-ring overwrite detection and retire the last pending request with a wakeup for the release waiter. The capture compatibility options remain opt-in and disabled by default. Carry the same stopped-plus-XRUN admission rule into the request-owned and shared capture ACK paths. A stopped capture tail remains available to DRAINING when no transfer XRUN is latched. Use prepared as the lifetime gate for staged capture pointer and ACK access. DRAINING state alone does not authorize access after prepared has been cleared; retain the explicit PAUSED/RELEASED exception. Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_card.c | 58 + sound/virtio/virtio_ctl_msg.c | 58 + sound/virtio/virtio_ctl_msg.h | 9=20 sound/virtio/virtio_pcm.c | 125 +++ sound/virtio/virtio_pcm.h | 145 +++ sound/virtio/virtio_pcm_msg.c | 511 ++++++++++-- sound/virtio/virtio_pcm_ops.c | 1800 ++++++++++++++++++++++++++++++++++++-= ---- 7 files changed, 2385 insertions(+), 321 deletions(-) diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -368,6 +368,23 @@ struct virtio_device *vdev =3D snd->vdev; unsigned int i; =20 + for (i =3D 0; snd->substreams && i < snd->nsubstreams; ++i) { + struct virtio_pcm_substream *vss =3D &snd->substreams[i]; + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + + guard(spinlock_irqsave)(&queue->lock); + guard(spinlock)(&vss->lock); + vss->reset_epoch++; + vss->resetting =3D true; + vss->prepared =3D false; + vss->preparing =3D false; + vss->device_running =3D false; + vss->release_required =3D false; + vss->control_uncertain =3D false; + vss->capture_reset_observing =3D false; + vss->xfer_enabled =3D false; + vss->suspended =3D true; + } virtio_reset_device(vdev); =20 /* Deleting a virtqueue does not unmap its outstanding descriptors. */ @@ -387,8 +404,31 @@ queue->vqueue =3D NULL; spin_unlock_irqrestore(&queue->lock, flags); } - vdev->config->del_vqs(vdev); +} + +/** + * virtsnd_cancel_period_work() - Drain PCM work after control cleanup. + * @snd: VirtIO sound device. + * + * Must follow control message cleanup, which wakes work blocked on a PCM + * control request. + * + * Context: Any context that permits to sleep. + */ +static void virtsnd_cancel_period_work(struct virtio_snd *snd) +{ + unsigned int i; + + for (i =3D 0; snd->substreams && i < snd->nsubstreams; ++i) { + struct virtio_pcm_substream *vss =3D &snd->substreams[i]; + + cancel_work_sync(&vss->elapsed_period); + guard(spinlock_irqsave)(&vss->lock); + vss->stopped =3D false; + vss->xfer_xrun =3D false; + vss->pause_restart_state =3D VIRTSND_PCM_PAUSE_RESTART_NONE; + } } =20 /** @@ -410,12 +450,13 @@ virtsnd_ctl_msg_cancel_all(snd); =20 virtsnd_reset_vqs(snd); + /* Wake PCM workers blocked on control replies before draining them. */ virtsnd_ctl_msg_cleanup(snd); + virtsnd_cancel_period_work(snd); =20 for (i =3D 0; snd->substreams && i < snd->nsubstreams; ++i) { struct virtio_pcm_substream *vss =3D &snd->substreams[i]; =20 - cancel_work_sync(&vss->elapsed_period); virtsnd_pcm_msg_free(vss); } =20 @@ -433,16 +474,14 @@ static int virtsnd_freeze(struct virtio_device *vdev) { struct virtio_snd *snd =3D vdev->priv; - unsigned int i; =20 virtsnd_disable_event_vq(snd); virtsnd_ctl_msg_cancel_all(snd); =20 virtsnd_reset_vqs(snd); + /* Wake PCM workers blocked on control replies before draining them. */ virtsnd_ctl_msg_cleanup(snd); - - for (i =3D 0; i < snd->nsubstreams; ++i) - cancel_work_sync(&snd->substreams[i].elapsed_period); + virtsnd_cancel_period_work(snd); =20 kfree(snd->event_msgs); snd->event_msgs =3D NULL; @@ -461,6 +500,7 @@ { struct virtio_snd *snd =3D vdev->priv; int rc; + unsigned int i; =20 rc =3D virtsnd_find_vqs(snd); if (rc) @@ -469,6 +509,12 @@ virtio_device_ready(vdev); virtsnd_ctl_msg_enable(snd); =20 + for (i =3D 0; i < snd->nsubstreams; ++i) { + struct virtio_pcm_substream *vss =3D &snd->substreams[i]; + + guard(spinlock_irqsave)(&vss->lock); + vss->resetting =3D false; + } virtsnd_enable_event_vq(snd); =20 return 0; diff --git a/sound/virtio/virtio_ctl_msg.c b/sound/virtio/virtio_ctl_msg.c --- a/sound/virtio/virtio_ctl_msg.c +++ b/sound/virtio/virtio_ctl_msg.c @@ -23,6 +23,9 @@ * @list: Pending message list entry. * @notify: Request completed notification. * @ref_count: Reference count used to manage a message lifetime. + * @pcm: Optional PCM generation owner, valid until control queue teardown. + * @pcm_epoch: Operation generation captured before request allocation. + * @submitted: Request was handed to the device. * @cancelled: Request canceled during device teardown. */ struct virtio_snd_msg { @@ -37,7 +40,49 @@ struct completion notify; refcount_t ref_count; bool cancelled; + bool submitted; + struct virtio_pcm_substream *pcm; + u64 pcm_epoch; }; + +/* Bind an operation's original generation before publishing the request. = */ +void virtsnd_ctl_msg_bind_pcm(struct virtio_snd_msg *msg, + struct virtio_pcm_substream *vss, u64 epoch) +{ + msg->pcm =3D vss; + msg->pcm_epoch =3D epoch; +} + +u64 virtsnd_ctl_msg_pcm_epoch(struct virtio_snd_msg *msg) +{ + return msg->pcm_epoch; +} + +static int virtsnd_ctl_msg_enqueue(struct virtio_snd *snd, + struct virtio_snd_msg *msg, + struct scatterlist **sgs, + unsigned int nouts, unsigned int nins) +{ + struct virtqueue *vq =3D virtsnd_control_queue(snd)->vqueue; + + if (!msg->pcm) + return virtqueue_add_sgs(vq, sgs, nouts, nins, msg, GFP_ATOMIC); + + /* Control queue -> PCM lock serializes enqueue against device reset. */ + guard(spinlock)(&msg->pcm->lock); + if (msg->pcm->resetting || msg->pcm->reset_epoch !=3D msg->pcm_epoch) + return -ECANCELED; + return virtqueue_add_sgs(vq, sgs, nouts, nins, msg, GFP_ATOMIC); +} + +/* Caller retains a reference across the synchronous send. */ +bool virtsnd_ctl_msg_uncertain(struct virtio_snd_msg *msg, int rc) +{ + return msg->submitted && + (rc =3D=3D -ETIMEDOUT || rc =3D=3D -ERESTARTSYS || + READ_ONCE(msg->cancelled)); +} + =20 /** * virtsnd_ctl_msg_ref() - Increment reference counter for the message. @@ -293,9 +338,9 @@ if (snd->ctl_msgs_blocked) rc =3D -ENODEV; else - rc =3D virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, msg, - GFP_ATOMIC); + rc =3D virtsnd_ctl_msg_enqueue(snd, msg, psgs, nouts, nins); if (!rc) { + msg->submitted =3D true; notify =3D virtqueue_kick_prepare(queue->vqueue); =20 list_add_tail(&msg->list, &snd->ctl_msgs); @@ -315,11 +360,14 @@ virtsnd_ctl_msg_unref(msg); } else if (!nowait) { /* - * Teardown must observe completion even with a pending signal. - * Otherwise a subsequent open can retry RELEASE while the device - * is still processing the previous teardown request. + * PCM state changes must observe completion with a pending signal. + * Otherwise recovery can issue a conflicting command while the + * device is still processing the previous transition. */ switch (le32_to_cpu(request->code)) { + case VIRTIO_SND_R_PCM_SET_PARAMS: + case VIRTIO_SND_R_PCM_PREPARE: + case VIRTIO_SND_R_PCM_START: case VIRTIO_SND_R_PCM_STOP: case VIRTIO_SND_R_PCM_RELEASE: rc =3D wait_for_completion_timeout(&msg->notify, js); diff --git a/sound/virtio/virtio_ctl_msg.h b/sound/virtio/virtio_ctl_msg.h --- a/sound/virtio/virtio_ctl_msg.h +++ b/sound/virtio/virtio_ctl_msg.h @@ -11,6 +11,13 @@ =20 struct virtio_snd; struct virtio_snd_msg; +struct virtio_pcm_substream; + +void virtsnd_ctl_msg_bind_pcm(struct virtio_snd_msg *msg, + struct virtio_pcm_substream *vss, u64 epoch); +u64 virtsnd_ctl_msg_pcm_epoch(struct virtio_snd_msg *msg); + +bool virtsnd_ctl_msg_uncertain(struct virtio_snd_msg *msg, int rc); =20 void virtsnd_ctl_msg_ref(struct virtio_snd_msg *msg); =20 @@ -47,7 +54,7 @@ * The msg_timeout_ms module parameter defines the message completion time= out. * If the message is not completed within this time, the function will ret= urn an * error. - * PCM STOP and RELEASE wait through pending signals within this timeout. + * PCM state-changing requests wait through pending signals within this ti= meout. * Other requests remain interruptible. Device reset cancels and wakes eit= her * kind of waiter. * 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 @@ -310,8 +310,119 @@ { struct virtio_pcm_substream *vss =3D container_of(work, struct virtio_pcm_substream, elapsed_period); - - snd_pcm_period_elapsed(vss->substream); + struct snd_pcm_substream *substream =3D vss->substream; + struct snd_pcm_runtime *runtime; + size_t period_bytes; + u64 period_floor; + u64 snapshot; + bool staged_capture; + bool paused_xrun =3D false; + bool notify =3D false; + bool partial =3D false; + bool requeue =3D false; + + scoped_guard(spinlock_irqsave, &vss->lock) { + staged_capture =3D vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + vss->capture_staging; + } + + /* Serialize the pointer snapshot with ALSA's pointer and ACK callbacks. = */ + guard(pcm_stream_lock_irqsave)(substream); + if (!substream->runtime) + return; + if (!snd_pcm_running(substream)) { + scoped_guard(spinlock_irqsave, &vss->lock) { + runtime =3D substream->runtime; + paused_xrun =3D runtime && + runtime->state =3D=3D SNDRV_PCM_STATE_PAUSED && + vss->xfer_xrun; + if (paused_xrun) + virtsnd_pcm_capture_arm_paused_stop(vss); + } + if (paused_xrun) { + /* Stop paused transfers and wake blocked readers. */ + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } + return; + } + + if (!staged_capture) { + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) { + guard(spinlock_irqsave)(&vss->lock); + period_bytes =3D snd_pcm_lib_period_bytes(substream); + period_floor =3D period_bytes ? + div64_ul(vss->capture_copied_total, period_bytes) : 0; + partial =3D vss->xfer_xrun || + period_floor <=3D vss->capture_notified_periods; + if (!partial) + vss->capture_notified_periods =3D period_floor; + } + /* Resume recovery can wake readers without a new period IRQ. */ + if (partial) + snd_pcm_progress_under_stream_lock(substream); + else + snd_pcm_period_elapsed_under_stream_lock(substream); + return; + } + + scoped_guard(spinlock_irqsave, &vss->lock) { + runtime =3D substream->runtime; + if (!vss->runtime_active || + (!vss->xfer_enabled && !vss->xfer_xrun) || !runtime) + return; + + period_bytes =3D snd_pcm_lib_period_bytes(substream); + if (vss->xfer_xrun) { + notify =3D true; + } else if (!period_bytes || period_bytes >=3D vss->buffer_bytes || + !virtsnd_pcm_capture_counters_valid(vss) || + vss->capture_notified_periods > + div64_ul(vss->capture_copied_total, period_bytes)) { + vss->xfer_xrun =3D true; + notify =3D true; + } else { + period_floor =3D div64_ul(vss->capture_copied_total, period_bytes); + if (period_floor > vss->capture_notified_periods) { + /* Reserve crossed boundaries before the callback. */ + vss->capture_notified_periods =3D period_floor; + notify =3D true; + } else if (vss->capture_copied_total > + vss->capture_pointer_seen_total) { + notify =3D true; + partial =3D true; + } + } + + if (notify) { + snapshot =3D vss->capture_copied_total; + vss->capture_callback_snapshot =3D snapshot; + vss->capture_callback_snapshot_active =3D true; + } + } + + if (!notify) + return; + + /* Pin the callback position while completions keep updating actual total= s. */ + if (partial) + snd_pcm_progress_under_stream_lock(substream); + else + snd_pcm_period_elapsed_under_stream_lock(substream); + + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->capture_callback_snapshot_active =3D false; + vss->capture_callback_snapshot =3D 0; + if (vss->runtime_active && vss->xfer_enabled && + !vss->xfer_xrun && substream->runtime) { + period_bytes =3D snd_pcm_lib_period_bytes(substream); + if (period_bytes && + vss->capture_copied_total > vss->capture_pointer_seen_total) + requeue =3D true; + } + } + + if (requeue) + virtsnd_pcm_schedule_period_elapsed(vss); } =20 /** @@ -351,8 +462,10 @@ vss->snd =3D snd; vss->sid =3D i; INIT_WORK(&vss->elapsed_period, virtsnd_pcm_period_elapsed); + disable_work(&vss->elapsed_period); init_waitqueue_head(&vss->msg_empty); spin_lock_init(&vss->lock); + mutex_init(&vss->lifecycle_lock); } =20 info =3D kzalloc_objs(*info, snd->nsubstreams); @@ -486,7 +599,7 @@ snd_pcm_set_ops(vpcm->pcm, i, &virtsnd_pcm_ops[i]); } =20 - snd_pcm_set_managed_buffer_all(vpcm->pcm, + snd_pcm_lib_preallocate_pages_for_all(vpcm->pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); } @@ -517,8 +630,12 @@ break; case VIRTIO_SND_EVT_PCM_XRUN: scoped_guard(spinlock, &vss->lock) { - if (vss->xfer_enabled) + if (vss->xfer_enabled || + (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + vss->prepared && !vss->stopped)) { vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + } } break; } 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 @@ -7,13 +7,32 @@ #define VIRTIO_SND_PCM_H =20 #include +#include +#include #include +#include #include #include =20 struct virtio_pcm; struct virtio_pcm_msg; struct page; + +enum virtio_pcm_pause_restart_state { + VIRTSND_PCM_PAUSE_RESTART_NONE, + VIRTSND_PCM_PAUSE_RESTART_CONFIGURED, + VIRTSND_PCM_PAUSE_RESTART_SET_PARAMS_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_PREPARE_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_PREPARED, + VIRTSND_PCM_PAUSE_RESTART_START_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_RUNNING, + VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_STOPPED, + VIRTSND_PCM_PAUSE_RESTART_RELEASE_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT, + VIRTSND_PCM_PAUSE_RESTART_RELEASED, + VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED, +}; =20 /** * struct virtio_pcm_substream - VirtIO PCM substream. @@ -26,21 +45,45 @@ * @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 t= rigger. + * @period_work_quiesced: Teardown owns a work disable, protected by @life= cycle_lock. + * @resetting: Device queues are unavailable until restore completes. + * @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. * @hw_ptr: Substream hardware pointer value in bytes [0 ... buffer_bytes). + * @capture_ready: Managed-mode bytes copied but not yet acknowledged by A= LSA; + * always zero in free-running mode. + * @capture_boundary: ALSA boundary captured for this prepared epoch. + * @capture_copied_total: Monotonic bytes copied into the capture ring. + * @capture_acked_total: Monotonic bytes acknowledged by the capture clien= t. + * @capture_pointer_seen_total: Last capture position sampled by ALSA. + * @capture_notified_periods: Highest full-period boundary reserved for no= tify. + * @capture_callback_snapshot: Capture position fixed for the period callb= ack. + * @capture_callback_snapshot_active: The callback must use its fixed posi= tion. + * @runtime_active: ALSA runtime buffer may be accessed by the staged path. + * @capture_staging: Capture uses request-owned payload buffers. * @capture_rx_seeded: Ordinary capture ring was queued since prepare. + * @capture_started: Non-empty capture data was received since prepare. + * @capture_empty: Consecutive empty startup completions. + * @capture_free_running: Cached staged-capture policy, protected by @lock. * @xfer_enabled: Data transfer state (0 - off, 1 - on). * @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun). * @stopped: True if the substream is stopped and must be released on the = device * side; protected by @lock. - * @prepared: True after device PREPARE and the core pointer reset succeed; - * protected by @lock. - * @preparing: ACK gate through teardown and prepare's core reset; - * protected by @lock. + * @prepared: Device PREPARE succeeded; protected by @lock. + * @preparing: ACK gate during sleeping teardown and prepare's core reset;= protected by + * the PCM stream lock and @lock. + * @device_running: Native START succeeded without a subsequent successful= STOP. + * @release_required: Successful PREPARE still needs a successful RELEASE. + * @control_uncertain: Native control result requires device reset before = reuse. + * @capture_reset_observing: Reclaim staged samples during RESET pointer c= apture. + * @indirect_reset_observing: Suppress ordinary ACK refill during RESET po= inter + * capture; 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. * @msgs: Allocated I/O messages. * @nmsgs: Number of allocated I/O messages. * @msg_count: Number of pending I/O messages in the virtqueue. @@ -61,17 +104,39 @@ struct snd_pcm_hardware hw; struct work_struct elapsed_period; spinlock_t lock; + struct mutex lifecycle_lock; + bool period_work_quiesced; + u64 reset_epoch; + bool resetting; size_t buffer_bytes; size_t hw_ptr; + size_t capture_ready; + snd_pcm_uframes_t capture_boundary; + u64 capture_copied_total; + u64 capture_acked_total; + u64 capture_pointer_seen_total; + u64 capture_notified_periods; + u64 capture_callback_snapshot; + bool capture_callback_snapshot_active; + bool runtime_active; + bool capture_staging; + bool capture_started; bool capture_rx_seeded; + bool capture_free_running; + unsigned int capture_empty; bool xfer_enabled; bool xfer_xrun; bool stopped; bool prepared; bool preparing; - /* Successful PREPARE still needs RELEASE; protected by lock. */ + /* Native control state, protected by lock. */ + bool device_running; bool release_required; + bool control_uncertain; + bool capture_reset_observing; + bool indirect_reset_observing; bool suspended; + enum virtio_pcm_pause_restart_state pause_restart_state; struct virtio_pcm_msg **msgs; unsigned int nmsgs; unsigned int msg_count; @@ -84,6 +149,64 @@ wait_queue_head_t msg_empty; }; =20 +/* Caller holds the PCM stream lock and vss->lock. */ +static inline void +virtsnd_pcm_capture_arm_paused_stop(struct virtio_pcm_substream *vss) +{ + struct snd_pcm_runtime *runtime =3D vss->substream->runtime; + + lockdep_assert_held(&vss->lock); + if (!runtime || runtime->state !=3D SNDRV_PCM_STATE_PAUSED) + return; + + /* ALSA skips trigger(STOP) when moving a paused stream to XRUN. */ + vss->xfer_enabled =3D false; + vss->stopped =3D true; + runtime->stop_operating =3D true; +} + +/* Keep non-staged PCM notifications on schedule_work()'s normal queue. */ +static inline bool +virtsnd_pcm_schedule_period_elapsed(struct virtio_pcm_substream *vss) +{ + return schedule_work(&vss->elapsed_period); +} + +/* + * ALSA may observe a subperiod tail before its period callback. Keep actu= al + * unread occupancy separate from pointer observations and notification st= ate; + * free-running streams account occupancy from monotonic totals instead. + */ +static inline size_t virtsnd_pcm_ring_offset(u64 total, size_t size) +{ + u64 remainder; + + div64_u64_rem(total, size, &remainder); + return remainder; +} + +static inline bool +virtsnd_pcm_capture_counters_valid(const struct virtio_pcm_substream *vss) +{ + u64 outstanding; + + if (!vss->buffer_bytes || + vss->capture_copied_total < vss->capture_acked_total || + vss->capture_pointer_seen_total > vss->capture_copied_total || + vss->hw_ptr !=3D virtsnd_pcm_ring_offset(vss->capture_copied_total, v= ss->buffer_bytes) || + (vss->capture_callback_snapshot_active && + (vss->capture_callback_snapshot < vss->capture_pointer_seen_total || + vss->capture_callback_snapshot > vss->capture_copied_total))) + return false; + + if (vss->capture_free_running) + return !vss->capture_ready; + + outstanding =3D vss->capture_copied_total - vss->capture_acked_total; + return outstanding =3D=3D (u64)vss->capture_ready && + vss->capture_ready < vss->buffer_bytes; +} + /** * struct virtio_pcm_stream - VirtIO PCM stream. * @substreams: VirtIO substreams belonging to the stream. @@ -114,6 +237,11 @@ =20 extern const struct snd_pcm_ops virtsnd_pcm_ops[]; =20 +extern bool virtsnd_capture_staging; +extern bool virtsnd_capture_packed_status; +extern bool virtsnd_capture_allow_short; +extern bool virtsnd_capture_allow_empty; + int virtsnd_pcm_validate(struct virtio_device *vdev); =20 int virtsnd_pcm_parse_cfg(struct virtio_snd *snd); @@ -132,11 +260,16 @@ =20 struct virtio_snd_msg * virtsnd_pcm_ctl_msg_alloc(struct virtio_pcm_substream *vss, - unsigned int command, gfp_t gfp); + unsigned int command, gfp_t gfp, u64 epoch); =20 int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, unsigned int periods, unsigned int period_bytes); =20 +int virtsnd_pcm_capture_send(struct virtio_pcm_substream *vss); + +int virtsnd_pcm_capture_refresh_mode_locked(struct virtio_pcm_substream *v= ss, + bool *free_running); + void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss); =20 void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss); 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 @@ -3,9 +3,12 @@ * virtio-snd: Virtio sound device * Copyright (C) 2021 OpenSynergy GmbH */ +#include +#include #include #include #include +#include #include =20 #include "virtio_card.h" @@ -16,14 +19,16 @@ * @xfer: Request header payload. * @status: Response header payload. * @length: Data length in bytes. + * @data: Request-owned payload for staged capture. * @data_offset: Offset of the fixed PCM ring slice for ordinary I/O. * @sgs: Payload scatter-gather table. */ struct virtio_pcm_msg { struct virtio_pcm_substream *substream; - struct virtio_snd_pcm_xfer xfer; - struct virtio_snd_pcm_status status; + struct virtio_snd_pcm_xfer *xfer; + struct virtio_snd_pcm_status *status; size_t length; + u8 *data; size_t data_offset; struct scatterlist sgs[]; }; @@ -226,6 +231,8 @@ if (check_mul_overflow((size_t)periods, (size_t)period_bytes, &buffer_bytes)) return -EOVERFLOW; + if (vss->capture_staging && periods < 2) + return -EINVAL; =20 if (!vss->substream || !vss->substream->runtime) return -EINVAL; @@ -234,14 +241,19 @@ return -EINVAL; =20 #ifdef CONFIG_MMU - rc =3D virtsnd_pcm_dma_pages_get(vss, runtime->dma_area, buffer_bytes); - if (rc) - return rc; + if (!vss->capture_staging) { + rc =3D virtsnd_pcm_dma_pages_get(vss, runtime->dma_area, + buffer_bytes); + if (rc) + return rc; + } #else - vss->owned_dma_area =3D kzalloc(buffer_bytes, GFP_KERNEL); - if (!vss->owned_dma_area) - return -ENOMEM; - vss->owned_dma_bytes =3D buffer_bytes; + if (!vss->capture_staging) { + vss->owned_dma_area =3D kzalloc(buffer_bytes, GFP_KERNEL); + if (!vss->owned_dma_area) + return -ENOMEM; + vss->owned_dma_bytes =3D buffer_bytes; + } #endif =20 vss->msgs =3D kzalloc_objs(*vss->msgs, periods); @@ -253,32 +265,54 @@ vss->nmsgs =3D periods; =20 for (i =3D 0; i < periods; ++i) { - int sg_num; struct virtio_pcm_msg *msg; u8 *data; + int sg_num; + + if (vss->capture_staging) { + data =3D vzalloc(period_bytes); + } else { #ifdef CONFIG_MMU - data =3D runtime->dma_area + (size_t)period_bytes * i; + data =3D runtime->dma_area + (size_t)period_bytes * i; #else - data =3D vss->owned_dma_area + (size_t)period_bytes * i; + data =3D vss->owned_dma_area + (size_t)period_bytes * i; #endif + } + if (!data) { + rc =3D -ENOMEM; + goto err_free_msgs; + } + sg_num =3D virtsnd_pcm_sg_num(data, period_bytes); if (sg_num < 0) { + if (vss->capture_staging) + vfree(data); rc =3D sg_num; goto err_free_msgs; } =20 msg =3D kzalloc_flex(*msg, sgs, sg_num + 2); if (!msg) { + if (vss->capture_staging) + vfree(data); rc =3D -ENOMEM; goto err_free_msgs; } =20 msg->substream =3D vss; + msg->data =3D vss->capture_staging ? data : NULL; vss->msgs[i] =3D msg; - sg_init_one(&msg->sgs[PCM_MSG_SG_XFER], &msg->xfer, - sizeof(msg->xfer)); - sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], &msg->status, - sizeof(msg->status)); + /* DMA payloads must not share cachelines with CPU-owned fields. */ + msg->xfer =3D kzalloc(sizeof(*msg->xfer), GFP_KERNEL); + msg->status =3D kzalloc(sizeof(*msg->status), GFP_KERNEL); + if (!msg->xfer || !msg->status) { + rc =3D -ENOMEM; + goto err_free_msgs; + } + sg_init_one(&msg->sgs[PCM_MSG_SG_XFER], msg->xfer, + sizeof(*msg->xfer)); + sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], msg->status, + sizeof(*msg->status)); msg->data_offset =3D (size_t)period_bytes * i; virtsnd_pcm_sg_from(&msg->sgs[PCM_MSG_SG_DATA], sg_num, data, period_bytes); @@ -312,8 +346,14 @@ { unsigned int i; =20 - for (i =3D 0; vss->msgs && i < vss->nmsgs; ++i) - kfree(vss->msgs[i]); + for (i =3D 0; vss->msgs && i < vss->nmsgs; ++i) { + if (vss->msgs[i]) { + vfree(vss->msgs[i]->data); + kfree(vss->msgs[i]->xfer); + kfree(vss->msgs[i]->status); + kfree(vss->msgs[i]); + } + } kfree(vss->msgs); #ifdef CONFIG_MMU virtsnd_pcm_dma_pages_put(vss); @@ -335,15 +375,26 @@ * substream was stopped are never sent to the device, so their length mus= t be * cleared before the substream is started again. * - * Context: Any context. Takes and releases the VirtIO substream spinlock. + * Context: Caller holds the VirtIO substream spinlock. */ void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss) { unsigned int i; =20 - guard(spinlock_irqsave)(&vss->lock); + lockdep_assert_held(&vss->lock); for (i =3D 0; vss->msgs && i < vss->nmsgs; ++i) vss->msgs[i]->length =3D 0; + vss->capture_ready =3D 0; + vss->capture_copied_total =3D 0; + vss->capture_acked_total =3D 0; + vss->capture_pointer_seen_total =3D 0; + vss->capture_notified_periods =3D 0; + vss->capture_callback_snapshot =3D 0; + vss->capture_callback_snapshot_active =3D false; + vss->capture_boundary =3D 0; + vss->capture_free_running =3D false; + vss->capture_empty =3D 0; + vss->capture_started =3D false; vss->capture_rx_seeded =3D false; } =20 @@ -374,6 +425,8 @@ bool notify =3D false; int rc; =20 + if (!vqueue || !vss->prepared || vss->suspended) + return -EBADFD; if (!bytes) return 0; if (!period_bytes || !vss->msgs || offset >=3D vss->buffer_bytes || @@ -399,8 +452,8 @@ =20 msg->length +=3D n; if (msg->length =3D=3D period_bytes) { - msg->xfer.stream_id =3D cpu_to_le32(vss->sid); - memset(&msg->status, 0, sizeof(msg->status)); + msg->xfer->stream_id =3D cpu_to_le32(vss->sid); + memset(msg->status, 0, sizeof(*msg->status)); =20 #ifndef CONFIG_MMU if (vss->direction =3D=3D SNDRV_PCM_STREAM_PLAYBACK) { @@ -474,6 +527,293 @@ return 0; } =20 +/* Caller holds vss->lock. */ +int virtsnd_pcm_capture_refresh_mode_locked(struct virtio_pcm_substream *v= ss, + bool *free_running) +{ + struct snd_pcm_runtime *runtime; + snd_pcm_uframes_t stop_threshold; + u64 outstanding; + bool new_free_running; + + if (!vss->capture_staging || !vss->runtime_active || !vss->substream) + return -EBADFD; + + runtime =3D vss->substream->runtime; + if (!runtime || !vss->buffer_bytes || !vss->capture_boundary) + return -EBADFD; + + /* Validate the currently committed policy before observing a transition.= */ + if (!virtsnd_pcm_capture_counters_valid(vss)) + return -EIO; + + stop_threshold =3D READ_ONCE(runtime->stop_threshold); + new_free_running =3D stop_threshold =3D=3D vss->capture_boundary; + if (new_free_running !=3D vss->capture_free_running) { + outstanding =3D vss->capture_copied_total - + vss->capture_acked_total; + if (new_free_running) { + vss->capture_ready =3D 0; + } else { + if (outstanding >=3D vss->buffer_bytes) + return -EPIPE; + vss->capture_ready =3D outstanding; + } + vss->capture_free_running =3D new_free_running; + } + + *free_running =3D new_free_running; + return 0; +} + +/* Caller holds the RX queue and vss locks and has sampled the policy. */ +static int virtsnd_pcm_capture_enqueue(struct virtio_pcm_substream *vss, + bool free_running) +{ + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + struct virtqueue *vqueue =3D queue->vqueue; + unsigned long period_bytes; + size_t free_bytes; + size_t request_limit =3D vss->nmsgs; + unsigned int i; + bool added =3D false; + + if (!vqueue || !vss->prepared || vss->suspended || + !vss->capture_staging || !vss->runtime_active) + return -EBADFD; + + period_bytes =3D snd_pcm_lib_period_bytes(vss->substream); + if (!period_bytes || !vss->buffer_bytes || + period_bytes >=3D vss->buffer_bytes || + free_running !=3D vss->capture_free_running || + !virtsnd_pcm_capture_counters_valid(vss) || + vss->msg_count > vss->nmsgs) + return -EIO; + + if (!free_running) { + free_bytes =3D vss->buffer_bytes - vss->capture_ready; + request_limit =3D free_bytes / period_bytes + + !!(free_bytes % period_bytes); + request_limit =3D min_t(size_t, request_limit, vss->nmsgs); + } + + for (i =3D 0; i < vss->nmsgs && vss->msg_count < request_limit; ++i) { + struct virtio_pcm_msg *msg =3D vss->msgs[i]; + struct scatterlist *sgs[] =3D { + &msg->sgs[PCM_MSG_SG_XFER], + &msg->sgs[PCM_MSG_SG_DATA], + &msg->sgs[PCM_MSG_SG_STATUS] + }; + int rc; + + if (msg->length) + continue; + + msg->xfer->stream_id =3D cpu_to_le32(vss->sid); + /* Do not let an absent packed trailer reuse old PCM bytes. */ + memset(msg->data, 0, period_bytes); + memset(msg->status, 0, sizeof(*msg->status)); + msg->status->status =3D cpu_to_le32(VIRTIO_SND_S_BAD_MSG); + rc =3D virtqueue_add_sgs(vqueue, sgs, 1, 2, msg, GFP_ATOMIC); + if (rc) { + if (added && !(vss->features & + (1U << VIRTIO_SND_PCM_F_MSG_POLLING)) && + virtqueue_kick_prepare(vqueue)) + virtqueue_notify(vqueue); + return rc; + } + + msg->length =3D period_bytes; + vss->msg_count++; + added =3D true; + } + + if (added && !(vss->features & + (1U << VIRTIO_SND_PCM_F_MSG_POLLING)) && + virtqueue_kick_prepare(vqueue)) + virtqueue_notify(vqueue); + + return 0; +} + +/** + * virtsnd_pcm_capture_send() - Refresh policy and queue staged requests. + * @vss: VirtIO PCM substream. + * + * Context: Expects the RX queue and substream spinlocks to be held by cal= ler. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_pcm_capture_send(struct virtio_pcm_substream *vss) +{ + bool free_running; + int rc; + + rc =3D virtsnd_pcm_capture_refresh_mode_locked(vss, &free_running); + if (rc) + return rc; + + return virtsnd_pcm_capture_enqueue(vss, free_running); +} + +static int virtsnd_pcm_capture_status(const struct virtio_pcm_msg *msg, + size_t written_bytes, + size_t frame_bytes, + size_t *audio_bytes, + u32 *latency_bytes) +{ + struct virtio_snd_pcm_status status; + size_t max_written; + size_t first; + + if (!frame_bytes || + check_add_overflow(msg->length, sizeof(status), &max_written) || + written_bytes < sizeof(status) || written_bytes > max_written) + return -EIO; + + *audio_bytes =3D written_bytes - sizeof(status); + if (*audio_bytes > msg->length || *audio_bytes % frame_bytes) + return -EIO; + + if (msg->substream->capture_staging && virtsnd_capture_packed_status) { + first =3D min_t(size_t, sizeof(status), + msg->length - *audio_bytes); + if (first) + memcpy(&status, msg->data + *audio_bytes, first); + if (first < sizeof(status)) + memcpy((u8 *)&status + first, msg->status, + sizeof(status) - first); + } else { + status =3D *msg->status; + } + + if (le32_to_cpu(status.status) !=3D VIRTIO_SND_S_OK) + return -EIO; + + *latency_bytes =3D le32_to_cpu(status.latency_bytes); + return 0; +} + +static void virtsnd_pcm_capture_xrun_locked(struct virtio_pcm_substream *v= ss) +{ + if (!vss->xfer_xrun) { + vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + } +} + +static void virtsnd_pcm_capture_complete(struct virtio_pcm_msg *msg, + size_t written_bytes) +{ + struct virtio_pcm_substream *vss =3D msg->substream; + size_t audio_bytes =3D 0; + size_t next_ready =3D 0; + u64 next_copied_total =3D 0; + bool free_running =3D false; + bool got_audio =3D false; + + guard(spinlock)(&vss->lock); + vss->msg_count--; + /* Native pause stops refills but must retain requests already in flight.= */ + if (vss->runtime_active && vss->prepared && !vss->stopped && + !vss->xfer_xrun) { + struct snd_pcm_runtime *runtime =3D vss->substream->runtime; + size_t frame_bytes =3D runtime ? + runtime->frame_bits / gcd(runtime->frame_bits, 8) : 0; + u32 latency_bytes; + const char *invalid_reason =3D NULL; + + /* A full ring is modulo-ambiguous, so fail closed before copying. */ + if (!runtime || !runtime->dma_area) + invalid_reason =3D "runtime"; + else if (virtsnd_pcm_capture_refresh_mode_locked(vss, + &free_running)) + invalid_reason =3D "policy"; + else if (!virtsnd_pcm_capture_counters_valid(vss)) + invalid_reason =3D "counters"; + else if (vss->hw_ptr !=3D + virtsnd_pcm_ring_offset(vss->capture_copied_total, vss->buffer_bytes)) + invalid_reason =3D "hw_ptr"; + else if (virtsnd_pcm_capture_status(msg, written_bytes, + frame_bytes, &audio_bytes, + &latency_bytes)) + invalid_reason =3D "status"; + else if (check_add_overflow(vss->capture_copied_total, + (u64)audio_bytes, + &next_copied_total)) + invalid_reason =3D "total_overflow"; + else if (free_running && audio_bytes >=3D vss->buffer_bytes) + invalid_reason =3D "payload_exceeds_ring"; + else if (!free_running && + audio_bytes >=3D vss->buffer_bytes - vss->capture_ready) + invalid_reason =3D "ring_capacity"; + else if (!free_running && + check_add_overflow(vss->capture_ready, audio_bytes, + &next_ready)) + invalid_reason =3D "ready_overflow"; + else if (!free_running && next_ready >=3D vss->buffer_bytes) + invalid_reason =3D "ring_full"; + else if (free_running && + (next_copied_total < vss->capture_pointer_seen_total || + next_copied_total - vss->capture_pointer_seen_total >=3D + vss->buffer_bytes)) + invalid_reason =3D "pointer_window"; + else if (free_running) + next_ready =3D 0; + + if (invalid_reason) { + virtsnd_pcm_capture_xrun_locked(vss); + dev_warn_ratelimited(&vss->snd->vdev->dev, + "SID %u: staged capture completion rejected (%s)\n", + vss->sid, invalid_reason); + } else if (!audio_bytes) { + unsigned int limit =3D min_t(unsigned int, vss->nmsgs, + UINT_MAX / 2) * 2; + + if (!virtsnd_capture_allow_empty || vss->capture_started || + vss->capture_empty >=3D limit) { + virtsnd_pcm_capture_xrun_locked(vss); + } else { + vss->capture_empty++; + } + } else if (audio_bytes < msg->length && + !virtsnd_capture_allow_short) { + virtsnd_pcm_capture_xrun_locked(vss); + } else { + size_t ring_ptr =3D virtsnd_pcm_ring_offset(vss->capture_copied_total, + vss->buffer_bytes); + size_t first =3D min(audio_bytes, + vss->buffer_bytes - ring_ptr); + + memcpy(runtime->dma_area + ring_ptr, msg->data, first); + if (audio_bytes > first) + memcpy(runtime->dma_area, msg->data + first, + audio_bytes - first); + vss->capture_copied_total =3D next_copied_total; + vss->hw_ptr =3D virtsnd_pcm_ring_offset(next_copied_total, vss->buffer_= bytes); + vss->capture_ready =3D next_ready; + vss->capture_started =3D true; + vss->capture_empty =3D 0; + runtime->delay =3D bytes_to_frames(runtime, latency_bytes); + got_audio =3D true; + } + } + + msg->length =3D 0; + if (vss->runtime_active && vss->xfer_enabled) { + if (!vss->xfer_xrun && + virtsnd_pcm_capture_enqueue(vss, free_running)) { + dev_warn_ratelimited(&vss->snd->vdev->dev, + "SID %u: failed to refill capture queue\n", + vss->sid); + virtsnd_pcm_capture_xrun_locked(vss); + } + } else if (!vss->msg_count) { + wake_up_all(&vss->msg_empty); + } + if (got_audio && !vss->xfer_xrun) + virtsnd_pcm_schedule_period_elapsed(vss); +} + /** * virtsnd_pcm_msg_pending_num() - Returns the number of pending I/O messa= ges. * @vss: VirtIO substream. @@ -525,9 +865,9 @@ * allowed, then each completed message is immediately placed back at the = end * of the queue. * - * For the playback substream, @written_bytes is equal to sizeof(msg->stat= us). - * - * For the capture substream, @written_bytes is equal to sizeof(msg->statu= s) + * For the playback substream, @written_bytes is equal to sizeof(*msg->sta= tus). + * + * For the capture substream, @written_bytes is equal to sizeof(*msg->stat= us) * plus the number of captured bytes. * * Context: Interrupt context. Takes and releases the VirtIO substream spi= nlock. @@ -536,78 +876,103 @@ size_t written_bytes) { struct virtio_pcm_substream *vss =3D msg->substream; - bool capture_invalid =3D false; - - /* - * Completed requests can return captured data after STOP while the PCM - * buffer remains prepared. Once preparation ends, completions only retire - * queue ownership. - */ + size_t completed_bytes; + u32 latency_bytes; + + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + vss->capture_staging) { + virtsnd_pcm_capture_complete(msg, written_bytes); + return; + } + + /* Only completed TX requests and valid RX audio advance the pointer. */ guard(spinlock)(&vss->lock); + /* Empty STOP returns retire ownership without inventing captured data. */ if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && (!vss->prepared || vss->xfer_xrun || - (vss->stopped && written_bytes <=3D sizeof(msg->status)))) { + (vss->stopped && written_bytes <=3D sizeof(*msg->status)))) { msg->length =3D 0; vss->msg_count--; if (!vss->msg_count) wake_up_all(&vss->msg_empty); return; } - - /* Only the next complete ring slice can extend captured data. */ - if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && - (msg->data_offset !=3D vss->hw_ptr || - written_bytes < sizeof(msg->status) || - le32_to_cpu(msg->status.status) !=3D VIRTIO_SND_S_OK || - written_bytes - sizeof(msg->status) !=3D msg->length)) { - dev_err_ratelimited(&vss->snd->vdev->dev, - "SID %u: invalid capture position, status or length\n", - vss->sid); - vss->xfer_xrun =3D true; - capture_invalid =3D true; + completed_bytes =3D msg->length; + latency_bytes =3D le32_to_cpu(msg->status->latency_bytes); + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) { + struct snd_pcm_runtime *runtime =3D vss->substream->runtime; + size_t frame_bytes =3D runtime->frame_bits / gcd(runtime->frame_bits, 8); + + /* Only the next complete ring slice can extend captured data. */ + if (msg->data_offset !=3D vss->hw_ptr || + virtsnd_pcm_capture_status(msg, written_bytes, frame_bytes, + &completed_bytes, &latency_bytes) || + !completed_bytes || completed_bytes !=3D msg->length) { + vss->xfer_xrun =3D true; + msg->length =3D 0; + vss->msg_count--; + if (!vss->msg_count) + wake_up_all(&vss->msg_empty); + virtsnd_pcm_schedule_period_elapsed(vss); + return; + } } #ifndef CONFIG_MMU if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && - vss->prepared && !capture_invalid && - written_bytes > sizeof(msg->status)) { + vss->prepared && + written_bytes > sizeof(*msg->status)) { struct snd_pcm_runtime *runtime =3D vss->substream ? vss->substream->runtime : NULL; + size_t reported_bytes =3D written_bytes - sizeof(*msg->status); + size_t data_bytes =3D min_t(size_t, reported_bytes, msg->length); =20 if (!runtime || !runtime->dma_area || !vss->owned_dma_area || msg->data_offset > runtime->dma_bytes || - msg->length > runtime->dma_bytes - msg->data_offset || + data_bytes > runtime->dma_bytes - msg->data_offset || msg->data_offset > vss->owned_dma_bytes || - msg->length > vss->owned_dma_bytes - msg->data_offset) { + data_bytes > vss->owned_dma_bytes - msg->data_offset) { dev_err_ratelimited(&vss->snd->vdev->dev, "SID %u: invalid capture buffer for I/O message\n", vss->sid); + /* Do not publish progress for data that could not be copied. */ + virtsnd_pcm_capture_xrun_locked(vss); + msg->length =3D 0; + vss->msg_count--; + if (!vss->msg_count) + wake_up_all(&vss->msg_empty); + return; + } + + memcpy(runtime->dma_area + msg->data_offset, + vss->owned_dma_area + msg->data_offset, data_bytes); + } +#endif + vss->hw_ptr +=3D completed_bytes; + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) { + /* A full unseen ring cannot be represented by the modulo pointer. */ + if (check_add_overflow(vss->capture_copied_total, + (u64)completed_bytes, + &vss->capture_copied_total) || + vss->capture_copied_total < vss->capture_pointer_seen_total || + vss->capture_copied_total - vss->capture_pointer_seen_total >=3D + vss->buffer_bytes) vss->xfer_xrun =3D true; - capture_invalid =3D true; - } else { - memcpy(runtime->dma_area + msg->data_offset, - vss->owned_dma_area + msg->data_offset, msg->length); - } - } -#endif - /* Publish pointer progress only for a complete TX or valid RX request. */ - if (!capture_invalid) { - vss->hw_ptr +=3D msg->length; - if (vss->hw_ptr >=3D vss->buffer_bytes) - vss->hw_ptr -=3D vss->buffer_bytes; - } + } + + if (vss->hw_ptr >=3D vss->buffer_bytes) + vss->hw_ptr -=3D vss->buffer_bytes; =20 msg->length =3D 0; + + /* Keep latched errors until prepare establishes a fresh epoch. */ vss->msg_count--; =20 - if (vss->xfer_enabled) { - if (!capture_invalid) { - struct snd_pcm_runtime *runtime =3D vss->substream->runtime; - - runtime->delay =3D - bytes_to_frames(runtime, - le32_to_cpu(msg->status.latency_bytes)); - } - schedule_work(&vss->elapsed_period); + if (vss->xfer_enabled || + (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && vss->xfer_xrun)) { + struct snd_pcm_runtime *runtime =3D vss->substream->runtime; + + runtime->delay =3D bytes_to_frames(runtime, latency_bytes); + virtsnd_pcm_schedule_period_elapsed(vss); } if (!vss->msg_count) wake_up_all(&vss->msg_empty); @@ -667,6 +1032,7 @@ * virtsnd_pcm_ctl_msg_alloc() - Allocate and initialize the PCM device co= ntrol * message for the specified substream. * @vss: VirtIO PCM substream. + * @epoch: Operation generation captured before allocation. * @command: Control request code (VIRTIO_SND_R_PCM_XXX). * @gfp: Kernel flags for memory allocation. * @@ -675,7 +1041,7 @@ */ struct virtio_snd_msg * virtsnd_pcm_ctl_msg_alloc(struct virtio_pcm_substream *vss, - unsigned int command, gfp_t gfp) + unsigned int command, gfp_t gfp, u64 epoch) { size_t request_size =3D sizeof(struct virtio_snd_pcm_hdr); size_t response_size =3D sizeof(struct virtio_snd_hdr); @@ -693,6 +1059,7 @@ =20 hdr->hdr.code =3D cpu_to_le32(command); hdr->stream_id =3D cpu_to_le32(vss->sid); + virtsnd_ctl_msg_bind_pcm(msg, vss, epoch); } =20 return msg; 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 @@ -3,9 +3,43 @@ * virtio-snd: Virtio sound device * Copyright (C) 2021 OpenSynergy GmbH */ +#include +#include #include =20 #include "virtio_card.h" + +static const struct snd_pcm_ops virtsnd_pcm_capture_staged_ops; +static const struct snd_pcm_ops virtsnd_pcm_capture_indirect_ops; + +bool virtsnd_capture_staging; +module_param_named(capture_staging, virtsnd_capture_staging, bool, 0444); +MODULE_PARM_DESC(capture_staging, + "Use request-owned buffers for experimental PCM capture"); + +bool virtsnd_capture_packed_status; +module_param_named(capture_packed_status, virtsnd_capture_packed_status, + bool, 0444); +MODULE_PARM_DESC(capture_packed_status, + "Read staged capture status after the actual audio payload"); + +bool virtsnd_capture_allow_short; +module_param_named(capture_allow_short, virtsnd_capture_allow_short, + bool, 0444); +MODULE_PARM_DESC(capture_allow_short, + "Accept non-empty short staged capture responses"); + +bool virtsnd_capture_allow_empty; +module_param_named(capture_allow_empty, virtsnd_capture_allow_empty, + bool, 0444); +MODULE_PARM_DESC(capture_allow_empty, + "Retry bounded empty startup capture responses"); + +static bool virtsnd_capture_pause_restart; +module_param_named(capture_pause_restart, virtsnd_capture_pause_restart, + bool, 0444); +MODULE_PARM_DESC(capture_pause_restart, + "Release and reprepare staged capture streams across pause"); =20 /* * I/O messages lifetime @@ -97,46 +131,243 @@ }; =20 static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream); - -/** - * virtsnd_pcm_close_ack() - Update PCM prepare admission state. - * @substream: Kernel ALSA substream. - * @preparing: True while driver prepare and the core pointer reset are ac= tive. - * - * Context: Process context. - */ -static void virtsnd_pcm_close_ack(struct snd_pcm_substream *substream, - bool preparing) -{ - struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - - scoped_guard(pcm_stream_lock_irqsave, substream) { - guard(spinlock_irqsave)(&vss->lock); - vss->prepared =3D false; +static int virtsnd_pcm_sync_stop_locked(struct snd_pcm_substream *substrea= m); +static int virtsnd_pcm_pause_restart_sync_stop(struct snd_pcm_substream *s= ubstream); + +static void virtsnd_pcm_start_failed(struct virtio_pcm_substream *vss, u64= epoch) +{ + bool notify; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return; vss->xfer_enabled =3D false; - vss->preparing =3D preparing; - } -} - -static void virtsnd_pcm_start_failed(struct virtio_pcm_substream *vss) -{ - scoped_guard(spinlock_irqsave, &vss->lock) { + vss->stopped =3D vss->release_required || vss->msg_count; + notify =3D vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + (!vss->capture_staging || vss->runtime_active); + if (notify) + vss->xfer_xrun =3D true; + } + vss->substream->runtime->stop_operating =3D true; + if (notify) + virtsnd_pcm_schedule_period_elapsed(vss); +} + +static bool +virtsnd_pcm_pause_restart_enabled(struct virtio_pcm_substream *vss) +{ + return virtsnd_capture_pause_restart && vss->capture_staging && + vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE; +} + +static bool virtsnd_pcm_pause_restart_transitioning(enum virtio_pcm_pause_= restart_state state) +{ + switch (state) { + case VIRTSND_PCM_PAUSE_RESTART_SET_PARAMS_SUBMITTED: + case VIRTSND_PCM_PAUSE_RESTART_PREPARE_SUBMITTED: + case VIRTSND_PCM_PAUSE_RESTART_START_SUBMITTED: + case VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED: + case VIRTSND_PCM_PAUSE_RESTART_RELEASE_SUBMITTED: + return true; + default: + return false; + } +} + +static int virtsnd_pcm_send_sync(struct virtio_pcm_substream *vss, + struct virtio_snd_msg *msg) +{ + u64 epoch =3D virtsnd_ctl_msg_pcm_epoch(msg); + bool uncertain; + int rc; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch || + vss->control_uncertain) { + rc =3D vss->control_uncertain ? -EIO : -ECANCELED; + virtsnd_ctl_msg_unref(msg); + return rc; + } + } + virtsnd_ctl_msg_ref(msg); + rc =3D virtsnd_ctl_msg_send_sync(vss->snd, msg); + uncertain =3D virtsnd_ctl_msg_uncertain(msg, rc); + virtsnd_ctl_msg_unref(msg); + scoped_guard(spinlock_irqsave, &vss->lock) { + if (epoch !=3D vss->reset_epoch || vss->resetting) + return -ECANCELED; + if (rc && uncertain) + vss->control_uncertain =3D true; + } + return rc; +} + +static int +virtsnd_pcm_pause_restart_send(struct virtio_pcm_substream *vss, + struct virtio_snd_msg *msg, + unsigned long allowed_states, + enum virtio_pcm_pause_restart_state submitted_state, + enum virtio_pcm_pause_restart_state success_state) +{ + enum virtio_pcm_pause_restart_state state; + bool uncertain; + u64 epoch =3D virtsnd_ctl_msg_pcm_epoch(msg); + int rc; + + if (!virtsnd_pcm_pause_restart_enabled(vss)) + return virtsnd_pcm_send_sync(vss, msg); + + scoped_guard(spinlock_irqsave, &vss->lock) { + state =3D vss->pause_restart_state; + if (vss->resetting || vss->reset_epoch !=3D epoch) { + rc =3D -ECANCELED; + } else if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED) { + rc =3D -EIO; + } else if (virtsnd_pcm_pause_restart_transitioning(state)) { + vss->pause_restart_state =3D + VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED; + rc =3D -EIO; + } else if (!(allowed_states & BIT(state))) { + rc =3D -EBADFD; + } else { + vss->pause_restart_state =3D submitted_state; + rc =3D 0; + } + } + + if (rc) { + virtsnd_ctl_msg_unref(msg); + return rc; + } + + virtsnd_ctl_msg_ref(msg); + rc =3D virtsnd_ctl_msg_send_sync(vss->snd, msg); + uncertain =3D virtsnd_ctl_msg_uncertain(msg, rc); + virtsnd_ctl_msg_unref(msg); + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->reset_epoch !=3D epoch || vss->resetting) + return -ECANCELED; + if ((rc && uncertain) || vss->pause_restart_state !=3D submitted_state) { + vss->pause_restart_state =3D + VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED; + if (!rc) + rc =3D -EIO; + } else if (rc) { + vss->pause_restart_state =3D state; + } else { + vss->pause_restart_state =3D success_state; + } + } + + if (rc && uncertain) + dev_err(&vss->snd->vdev->dev, + "SID %u: PCM control state is unknown; device reprobe required\n", + vss->sid); + + return rc; +} + +static void +virtsnd_pcm_pause_restart_fail(struct virtio_pcm_substream *vss, + bool reset_required, u64 epoch) +{ + bool notify_xrun; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return; + notify_xrun =3D vss->runtime_active; vss->xfer_enabled =3D false; vss->stopped =3D true; - } + vss->xfer_xrun =3D true; + if (reset_required) + vss->pause_restart_state =3D + VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED; + } + if (notify_xrun) + virtsnd_pcm_schedule_period_elapsed(vss); vss->substream->runtime->stop_operating =3D true; } =20 -/* Keep completed RX data valid while RELEASE drains the device queue. */ -static void virtsnd_pcm_block_ack(struct snd_pcm_substream *substream) -{ - struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - - scoped_guard(pcm_stream_lock_irqsave, substream) { - guard(spinlock_irqsave)(&vss->lock); - vss->preparing =3D true; - vss->xfer_enabled =3D false; - } +static int virtsnd_pcm_dev_prepare(struct virtio_pcm_substream *vss, u64 e= poch) +{ + struct virtio_snd_msg *msg; + + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_PREPARE, + GFP_KERNEL, epoch); + if (!msg) + return -ENOMEM; + + return virtsnd_pcm_pause_restart_send(vss, msg, + BIT(VIRTSND_PCM_PAUSE_RESTART_CONFIGURED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_RELEASED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_STOPPED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_PREPARED), + VIRTSND_PCM_PAUSE_RESTART_PREPARE_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_PREPARED); +} + +static int virtsnd_pcm_pause_prepare(struct virtio_pcm_substream *vss, u64= epoch) +{ + struct virtio_snd_msg *msg; + + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_PREPARE, + GFP_KERNEL, epoch); + if (!msg) + return -ENOMEM; + + return virtsnd_pcm_pause_restart_send(vss, msg, + BIT(VIRTSND_PCM_PAUSE_RESTART_RELEASED), + VIRTSND_PCM_PAUSE_RESTART_PREPARE_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_PREPARED); +} + +static int virtsnd_pcm_release_wait(struct virtio_pcm_substream *vss, u64 = epoch) +{ + unsigned int js =3D msecs_to_jiffies(virtsnd_msg_timeout_ms); + long waited; + + waited =3D wait_event_timeout(vss->msg_empty, + !virtsnd_pcm_msg_pending_num(vss), js); + guard(spinlock_irqsave)(&vss->lock); + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + if (!waited || vss->msg_count || + vss->pause_restart_state !=3D VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT) + return -ETIMEDOUT; + vss->prepared =3D false; + vss->release_required =3D false; + vss->pause_restart_state =3D VIRTSND_PCM_PAUSE_RESTART_RELEASED; + return 0; +} + +static int +virtsnd_pcm_pause_release_flush(struct virtio_pcm_substream *vss, + unsigned long allowed_states, u64 epoch) +{ + struct virtio_snd_msg *msg; + int rc; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + } + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_RELEASE, + GFP_KERNEL, epoch); + if (!msg) + return -ENOMEM; + rc =3D virtsnd_pcm_pause_restart_send(vss, msg, allowed_states, + VIRTSND_PCM_PAUSE_RESTART_RELEASE_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT); + if (rc) + return rc; + return virtsnd_pcm_release_wait(vss, epoch); +} + +static void virtsnd_pcm_pause_restart_failed(struct virtio_pcm_substream *= vss, + u64 epoch) +{ + virtsnd_pcm_pause_restart_fail(vss, false, epoch); } =20 /** @@ -151,23 +382,42 @@ struct virtio_pcm *vpcm =3D snd_pcm_substream_chip(substream); struct virtio_pcm_stream *vs =3D &vpcm->streams[substream->stream]; struct virtio_pcm_substream *vss =3D vs->substreams[substream->number]; + int rc; =20 substream->runtime->hw =3D vss->hw; substream->private_data =3D vss; + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->capture_staging =3D virtsnd_capture_staging && + vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE; + if (vss->capture_staging) { + vss->runtime_active =3D false; + vss->xfer_enabled =3D false; + } + } + + /* Keep RESET operation selection fixed for the opened runtime. */ + substream->ops =3D vss->capture_staging ? + &virtsnd_pcm_capture_staged_ops : + &virtsnd_pcm_ops[substream->stream]; + + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && !vss->capture_stagi= ng) + substream->ops =3D &virtsnd_pcm_capture_indirect_ops; =20 snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); =20 scoped_guard(spinlock_irqsave, &vss->lock) vss->stopped |=3D !!vss->msg_count; - vss->suspended =3D false; =20 /* * If the substream has already been used, then the I/O queue may be in * an invalid state. Just in case, we do a check and try to return the * queue to its original state, if necessary. */ - return virtsnd_pcm_sync_stop(substream); + rc =3D virtsnd_pcm_sync_stop(substream); + if (!rc) + enable_work(&vss->elapsed_period); + return rc; } =20 /** @@ -179,6 +429,15 @@ */ static int virtsnd_pcm_close(struct snd_pcm_substream *substream) { + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->runtime_active =3D false; + vss->xfer_enabled =3D false; + } + /* Block late producers as well as drain already queued notifications. */ + disable_work_sync(&vss->elapsed_period); + return 0; } =20 @@ -191,6 +450,7 @@ * @channels: Selected number of channels. * @format: Selected sample format (SNDRV_PCM_FORMAT_XXX). * @rate: Selected frame rate. + * @epoch: Operation generation captured before preparing the request. * * Context: Any context that permits to sleep. * Return: 0 on success, -errno on failure. @@ -200,7 +460,7 @@ unsigned int period_bytes, unsigned int channels, snd_pcm_format_t format, - unsigned int rate) + unsigned int rate, u64 epoch) { struct virtio_snd_msg *msg; struct virtio_snd_pcm_set_params *request; @@ -226,7 +486,7 @@ return -EINVAL; =20 msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_SET_PARAMS, - GFP_KERNEL); + GFP_KERNEL, epoch); if (!msg) return -ENOMEM; =20 @@ -245,7 +505,14 @@ request->features |=3D cpu_to_le32(1U << VIRTIO_SND_PCM_F_EVT_XRUNS); =20 - return virtsnd_ctl_msg_send_sync(vss->snd, msg); + return virtsnd_pcm_pause_restart_send(vss, msg, + BIT(VIRTSND_PCM_PAUSE_RESTART_NONE) | + BIT(VIRTSND_PCM_PAUSE_RESTART_CONFIGURED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_PREPARED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_STOPPED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_RELEASED), + VIRTSND_PCM_PAUSE_RESTART_SET_PARAMS_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_CONFIGURED); } =20 /** @@ -264,10 +531,10 @@ unsigned int msg_count; size_t period_buffer_bytes; bool stopped; + u64 epoch; int rc; =20 - virtsnd_pcm_close_ack(substream, false); - + guard(mutex)(&vss->lifecycle_lock); /* Fixed descriptors require whole-byte periods covering the buffer. */ if (check_mul_overflow((size_t)params_periods(hw_params), (size_t)params_period_bytes(hw_params), @@ -277,6 +544,25 @@ (u64)snd_pcm_format_physical_width(params_format(hw_params)) * params_channels(hw_params)) % 8) return -EINVAL; + scoped_guard(spinlock_irqsave, &vss->lock) + epoch =3D vss->reset_epoch; + /* Drain the previous buffer before ALSA can replace its mapping. */ + rc =3D virtsnd_pcm_sync_stop_locked(substream); + if (rc) + return rc; + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->prepared =3D false; + } + + if (vss->capture_staging) { + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->runtime_active =3D false; + vss->xfer_enabled =3D false; + } + cancel_work_sync(&vss->elapsed_period); + } =20 scoped_guard(spinlock_irqsave, &vss->lock) { msg_count =3D vss->msg_count; @@ -290,16 +576,20 @@ if (stopped) { dev_err(&vdev->dev, "SID %u: stream release failed\n", vss->sid); - scoped_guard(pcm_stream_lock_irqsave, substream) - substream->runtime->stop_operating =3D true; + substream->runtime->stop_operating =3D true; return -EBADFD; } + + rc =3D snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)= ); + if (rc < 0) + return rc; + substream->runtime->buffer_changed =3D rc > 0; =20 rc =3D virtsnd_pcm_dev_set_params(vss, params_buffer_bytes(hw_params), params_period_bytes(hw_params), params_channels(hw_params), params_format(hw_params), - params_rate(hw_params)); + params_rate(hw_params), epoch); if (rc) return rc; =20 @@ -309,8 +599,18 @@ */ virtsnd_pcm_msg_free(vss); =20 - return virtsnd_pcm_msg_alloc(vss, params_periods(hw_params), - params_period_bytes(hw_params)); + rc =3D virtsnd_pcm_msg_alloc(vss, params_periods(hw_params), + params_period_bytes(hw_params)); + if (!rc) { + guard(spinlock_irqsave)(&vss->lock); + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->suspended =3D false; + if (vss->capture_staging) + vss->runtime_active =3D true; + } + + return rc; } =20 /** @@ -323,96 +623,498 @@ static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - - virtsnd_pcm_close_ack(substream, false); + int rc; + + guard(mutex)(&vss->lifecycle_lock); + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->stopped |=3D vss->prepared; + vss->prepared =3D false; + vss->runtime_active =3D false; + vss->xfer_enabled =3D false; + } + rc =3D virtsnd_pcm_sync_stop_locked(substream); + cancel_work_sync(&vss->elapsed_period); + + if (vss->capture_staging) { + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->runtime_active =3D false; + vss->xfer_enabled =3D false; + } + cancel_work_sync(&vss->elapsed_period); + scoped_guard(spinlock_irqsave, &vss->lock) { + vss->hw_ptr =3D 0; + vss->capture_ready =3D 0; + vss->capture_copied_total =3D 0; + vss->capture_acked_total =3D 0; + vss->capture_pointer_seen_total =3D 0; + vss->capture_notified_periods =3D 0; + vss->capture_callback_snapshot =3D 0; + vss->capture_callback_snapshot_active =3D false; + vss->capture_boundary =3D 0; + vss->capture_free_running =3D false; + } + } =20 /* If the queue is flushed, we can safely free the messages here. */ if (!virtsnd_pcm_msg_pending_num(vss)) virtsnd_pcm_msg_free(vss); =20 - return 0; + snd_pcm_lib_free_pages(substream); + return rc; } =20 /** - * virtsnd_pcm_prepare_device() - Prepare the PCM substream on the device. + * 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_device(struct snd_pcm_substream *substream) +static int virtsnd_pcm_prepare_locked(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_device *vdev =3D vss->snd->vdev; - struct virtio_snd_msg *msg; + bool pause_restart =3D virtsnd_pcm_pause_restart_enabled(vss); + bool free_running; + int rc; bool stopped; - int rc; + bool suspended; + u64 epoch; + + scoped_guard(pcm_stream_lock_irqsave, substream) { + guard(spinlock_irqsave)(&vss->lock); + if (vss->resetting) + return -ECANCELED; + epoch =3D vss->reset_epoch; + stopped =3D vss->stopped || vss->release_required || vss->msg_count; + vss->prepared =3D false; + vss->preparing =3D true; + } + if (stopped) { + rc =3D virtsnd_pcm_sync_stop_locked(substream); + if (rc) + return rc; + } + scoped_guard(pcm_stream_lock_irqsave, substream) { + guard(spinlock_irqsave)(&vss->lock); + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + suspended =3D vss->suspended; + } + + if (pause_restart) { + enum virtio_pcm_pause_restart_state state; + + scoped_guard(spinlock_irqsave, &vss->lock) + state =3D vss->pause_restart_state; + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED || + virtsnd_pcm_pause_restart_transitioning(state)) { + virtsnd_pcm_pause_restart_fail(vss, true, epoch); + return -EIO; + } + } =20 scoped_guard(spinlock_irqsave, &vss->lock) stopped =3D vss->stopped; if (stopped) { dev_err(&vdev->dev, "SID %u: stream release failed\n", vss->sid); - scoped_guard(pcm_stream_lock_irqsave, substream) - substream->runtime->stop_operating =3D true; + substream->runtime->stop_operating =3D true; return -EBADFD; } =20 - 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); return -EBADFD; } =20 - vss->buffer_bytes =3D snd_pcm_lib_buffer_bytes(substream); - vss->hw_ptr =3D 0; } else { struct snd_pcm_runtime *runtime =3D substream->runtime; unsigned int buffer_bytes =3D snd_pcm_lib_buffer_bytes(substream); unsigned int period_bytes =3D snd_pcm_lib_period_bytes(substream); =20 rc =3D virtsnd_pcm_dev_set_params(vss, buffer_bytes, period_bytes, - runtime->channels, - runtime->format, runtime->rate); - if (rc) + runtime->channels, + runtime->format, runtime->rate, epoch); + if (rc) { + if (pause_restart && rc !=3D -ECANCELED) + virtsnd_pcm_pause_restart_fail(vss, false, epoch); return rc; - } - - vss->suspended =3D false; - vss->msg_count =3D 0; - virtsnd_pcm_msg_reset(vss); - - memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect)); - vss->pcm_indirect.sw_buffer_size =3D - vss->pcm_indirect.hw_buffer_size =3D - snd_pcm_lib_buffer_bytes(substream); - - msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_PREPARE, - GFP_KERNEL); + } + } + + rc =3D virtsnd_pcm_dev_prepare(vss, epoch); + if (rc) { + if (pause_restart && rc !=3D -ECANCELED) + virtsnd_pcm_pause_restart_fail(vss, false, epoch); + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->reset_epoch =3D=3D epoch && !vss->resetting) { + vss->xfer_xrun =3D true; + vss->stopped =3D true; + } + } + return rc; + } + + scoped_guard(pcm_stream_lock_irqsave, substream) { + guard(spinlock_irqsave)(&vss->lock); + if (epoch !=3D vss->reset_epoch || vss->resetting) + return -ECANCELED; + vss->release_required =3D true; + vss->buffer_bytes =3D snd_pcm_lib_buffer_bytes(substream); + vss->hw_ptr =3D 0; + vss->suspended =3D false; + vss->msg_count =3D 0; + virtsnd_pcm_msg_reset(vss); + memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect)); + vss->pcm_indirect.sw_buffer_size =3D vss->buffer_bytes; + vss->pcm_indirect.hw_buffer_size =3D vss->buffer_bytes; + if (vss->capture_staging) { + vss->capture_boundary =3D substream->runtime->boundary; + rc =3D virtsnd_pcm_capture_refresh_mode_locked(vss, + &free_running); + if (rc) { + vss->xfer_xrun =3D true; + vss->stopped =3D true; + return rc; + } + } + vss->prepared =3D true; + vss->stopped =3D false; + vss->xfer_xrun =3D false; + } + + return rc; +} + +static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + + int rc; + + guard(mutex)(&vss->lifecycle_lock); + rc =3D virtsnd_pcm_prepare_locked(substream); + if (rc) { + guard(pcm_stream_lock_irqsave)(substream); + guard(spinlock_irqsave)(&vss->lock); + vss->preparing =3D false; + } else if (vss->period_work_quiesced) { + enable_work(&vss->elapsed_period); + vss->period_work_quiesced =3D false; + } + return rc; +} + +static int virtsnd_pcm_pause_restart_start(struct virtio_pcm_substream *vs= s, + bool resume) +{ + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + struct virtio_snd_msg *msg; + enum virtio_pcm_pause_restart_state state; + bool reset_required; + bool invalid_state; + bool xrun; + int rc =3D 0; + u64 epoch; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting) + return -ECANCELED; + epoch =3D vss->reset_epoch; + state =3D vss->pause_restart_state; + xrun =3D vss->xfer_xrun; + reset_required =3D + state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED || + virtsnd_pcm_pause_restart_transitioning(state); + invalid_state =3D + (resume && + (state !=3D VIRTSND_PCM_PAUSE_RESTART_RELEASED || + !vss->stopped || vss->msg_count || + !vss->runtime_active)) || + (!resume && + (state !=3D VIRTSND_PCM_PAUSE_RESTART_PREPARED || + vss->stopped)); + } + if (reset_required) { + virtsnd_pcm_pause_restart_fail(vss, true, epoch); + return -EIO; + } + if (xrun) { + virtsnd_pcm_pause_restart_failed(vss, epoch); + return -EPIPE; + } + if (invalid_state) { + virtsnd_pcm_pause_restart_fail(vss, false, epoch); + return -EBADFD; + } + + if (resume) { + rc =3D virtsnd_pcm_pause_prepare(vss, epoch); + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_failed(vss, epoch); + return rc; + } + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) { + rc =3D -ECANCELED; + } else { + vss->release_required =3D true; + if (vss->pause_restart_state !=3D + VIRTSND_PCM_PAUSE_RESTART_PREPARED || + vss->msg_count || !vss->runtime_active) { + rc =3D -EBADFD; + } else { + /* RX may complete before START responds to this PREPARE. */ + vss->prepared =3D true; + vss->stopped =3D false; + vss->capture_started =3D false; + vss->capture_empty =3D 0; + } + } + } + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_failed(vss, epoch); + return rc; + } + } + + scoped_guard(spinlock_irqsave, &queue->lock) { + guard(spinlock)(&vss->lock); + if (vss->resetting || vss->reset_epoch !=3D epoch) { + rc =3D -ECANCELED; + } else if (vss->xfer_xrun) { + rc =3D -EPIPE; + } else if (vss->pause_restart_state !=3D + VIRTSND_PCM_PAUSE_RESTART_PREPARED || + vss->msg_count || !vss->runtime_active) { + rc =3D -EBADFD; + } else { + rc =3D virtsnd_pcm_capture_send(vss); + if (!rc) + vss->xfer_enabled =3D true; + } + } + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_failed(vss, epoch); + return rc; + } + + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_START, GFP_KERNEL= , epoch); + if (!msg) { + virtsnd_pcm_pause_restart_failed(vss, epoch); + return -ENOMEM; + } + + rc =3D virtsnd_pcm_pause_restart_send(vss, msg, + BIT(VIRTSND_PCM_PAUSE_RESTART_PREPARED), + VIRTSND_PCM_PAUSE_RESTART_START_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_RUNNING); + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_failed(vss, epoch); + return rc; + } + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->stopped =3D false; + } + virtsnd_pcm_schedule_period_elapsed(vss); + + return 0; +} + +static int virtsnd_pcm_pause_restart_pause(struct virtio_pcm_substream *vs= s) +{ + struct virtio_snd_msg *msg; + enum virtio_pcm_pause_restart_state state; + bool xrun; + int rc; + u64 epoch; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting) + return -ECANCELED; + epoch =3D vss->reset_epoch; + state =3D vss->pause_restart_state; + xrun =3D vss->xfer_xrun; + if (state !=3D VIRTSND_PCM_PAUSE_RESTART_RELEASED) { + vss->xfer_enabled =3D false; + vss->stopped =3D true; + } + } + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RELEASED) { + if (xrun) { + virtsnd_pcm_pause_restart_failed(vss, epoch); + return -EPIPE; + } + return 0; + } + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT) { + /* Trigger holds the PCM lock; this helper never cancels work. */ + rc =3D virtsnd_pcm_release_wait(vss, epoch); + if (!rc) { + guard(spinlock_irqsave)(&vss->lock); + rc =3D vss->xfer_xrun ? -EPIPE : 0; + } + if (rc && rc !=3D -ECANCELED) + virtsnd_pcm_pause_restart_failed(vss, epoch); + return rc; + } + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED || + virtsnd_pcm_pause_restart_transitioning(state)) { + virtsnd_pcm_pause_restart_fail(vss, true, epoch); + return -EIO; + } + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RUNNING) { + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_STOP, + GFP_KERNEL, epoch); + if (!msg) { + virtsnd_pcm_pause_restart_failed(vss, epoch); + return -ENOMEM; + } + + rc =3D virtsnd_pcm_pause_restart_send(vss, msg, + BIT(VIRTSND_PCM_PAUSE_RESTART_RUNNING), + VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_STOPPED); + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_failed(vss, epoch); + return rc; + } + state =3D VIRTSND_PCM_PAUSE_RESTART_STOPPED; + } else if (state !=3D VIRTSND_PCM_PAUSE_RESTART_STOPPED && + state !=3D VIRTSND_PCM_PAUSE_RESTART_PREPARED) { + virtsnd_pcm_pause_restart_failed(vss, epoch); + return -EBADFD; + } + + rc =3D virtsnd_pcm_pause_release_flush(vss, + BIT(VIRTSND_PCM_PAUSE_RESTART_STOPPED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_PREPARED), epoch); + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_failed(vss, epoch); + return rc; + } + + return 0; +} + +static int virtsnd_pcm_pause_restart_stop(struct virtio_pcm_substream *vss, + bool suspend) +{ + struct virtio_snd_msg *msg; + enum virtio_pcm_pause_restart_state state; + int rc; + u64 epoch; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting) + return -ECANCELED; + epoch =3D vss->reset_epoch; + if (suspend) + vss->suspended =3D true; + if (!vss->prepared && !vss->msg_count) + return 0; + vss->xfer_enabled =3D false; + vss->stopped =3D true; + state =3D vss->pause_restart_state; + } + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED || + virtsnd_pcm_pause_restart_transitioning(state)) { + virtsnd_pcm_pause_restart_fail(vss, true, epoch); + return -EIO; + } + if (state !=3D VIRTSND_PCM_PAUSE_RESTART_RUNNING) + return 0; + + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_STOP, GFP_KERNEL,= epoch); + if (!msg) { + virtsnd_pcm_pause_restart_failed(vss, epoch); + return -ENOMEM; + } + + rc =3D virtsnd_pcm_pause_restart_send(vss, msg, + BIT(VIRTSND_PCM_PAUSE_RESTART_RUNNING), + VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_STOPPED); + if (rc && rc !=3D -ECANCELED) + virtsnd_pcm_pause_restart_failed(vss, epoch); + + return rc; +} + +static int virtsnd_pcm_stop_device(struct virtio_pcm_substream *vss, u64 e= poch) +{ + struct virtio_snd_msg *msg; + int rc; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + if (vss->control_uncertain) + return -EIO; + if (!vss->device_running) + return 0; + } + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_STOP, + GFP_KERNEL, epoch); if (!msg) return -ENOMEM; - - rc =3D virtsnd_ctl_msg_send_sync(vss->snd, msg); + rc =3D virtsnd_pcm_send_sync(vss, msg); if (rc) return rc; - - scoped_guard(spinlock_irqsave, &vss->lock) { - vss->xfer_xrun =3D false; - vss->release_required =3D true; - } + guard(spinlock_irqsave)(&vss->lock); + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->device_running =3D false; return 0; } =20 -/* Keep ACK closed through the core's locked pointer reset. */ -static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) -{ - int rc; - - virtsnd_pcm_close_ack(substream, true); - rc =3D virtsnd_pcm_prepare_device(substream); - if (rc) - virtsnd_pcm_close_ack(substream, false); +static int virtsnd_pcm_start_queue(struct virtio_pcm_substream *vss, u64 e= poch) +{ + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + int rc =3D 0; + + guard(spinlock_irqsave)(&queue->lock); + guard(spinlock)(&vss->lock); + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + if (vss->stopped || !vss->prepared || vss->xfer_xrun) + return vss->xfer_xrun ? -EPIPE : -EBADFD; + if (vss->control_uncertain) + return -EIO; + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) { + if (vss->capture_staging) { + rc =3D vss->runtime_active ? virtsnd_pcm_capture_send(vss) : -EBADFD; + } else if (!vss->capture_rx_seeded) { + rc =3D virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes); + if (!rc) + vss->capture_rx_seeded =3D true; + } + } + if (!rc) + vss->xfer_enabled =3D true; return rc; } =20 @@ -428,74 +1130,237 @@ static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int co= mmand) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - struct virtio_snd *snd =3D vss->snd; - struct virtio_snd_queue *queue; struct virtio_snd_msg *msg; int rc =3D 0; + bool running; + u64 epoch; + + scoped_guard(spinlock_irqsave, &vss->lock) + epoch =3D vss->reset_epoch; + + if (virtsnd_pcm_pause_restart_enabled(vss)) { + switch (command) { + case SNDRV_PCM_TRIGGER_START: + return virtsnd_pcm_pause_restart_start(vss, false); + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + return virtsnd_pcm_pause_restart_start(vss, true); + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + return virtsnd_pcm_pause_restart_pause(vss); + case SNDRV_PCM_TRIGGER_STOP: + return virtsnd_pcm_pause_restart_stop(vss, false); + case SNDRV_PCM_TRIGGER_SUSPEND: + return virtsnd_pcm_pause_restart_stop(vss, true); + default: + return -EINVAL; + } + } =20 switch (command) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - queue =3D virtsnd_pcm_queue(vss); - - scoped_guard(spinlock_irqsave, &queue->lock) { - guard(spinlock)(&vss->lock); - if (vss->preparing || !vss->prepared) - return -EBADFD; - if (vss->stopped) { - substream->runtime->stop_operating =3D true; - return -EBADFD; - } - if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && - !vss->capture_rx_seeded) { - rc =3D virtsnd_pcm_msg_send(vss, 0, vss->buffer_bytes); - if (!rc) - vss->capture_rx_seeded =3D true; - } - if (rc) { - vss->stopped =3D true; - substream->runtime->stop_operating =3D true; - return rc; - } - vss->xfer_enabled =3D true; + scoped_guard(spinlock_irqsave, &vss->lock) + running =3D vss->device_running; + /* Preserve queued requests and unread RX data across native pause. */ + rc =3D virtsnd_pcm_start_queue(vss, epoch); + if (rc) { + virtsnd_pcm_start_failed(vss, epoch); + return rc; + } + + if (running) { + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) + virtsnd_pcm_schedule_period_elapsed(vss); + return 0; } =20 msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_START, - GFP_KERNEL); + GFP_KERNEL, epoch); if (!msg) { - virtsnd_pcm_start_failed(vss); + virtsnd_pcm_start_failed(vss, epoch); return -ENOMEM; } =20 - rc =3D virtsnd_ctl_msg_send_sync(snd, msg); - if (rc) - virtsnd_pcm_start_failed(vss); - + rc =3D virtsnd_pcm_send_sync(vss, msg); + if (rc && rc !=3D -ECANCELED) + virtsnd_pcm_start_failed(vss, epoch); + + if (!rc) { + guard(spinlock_irqsave)(&vss->lock); + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->device_running =3D true; + } + if (!rc && vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) + virtsnd_pcm_schedule_period_elapsed(vss); return rc; case SNDRV_PCM_TRIGGER_SUSPEND: - vss->suspended =3D true; + scoped_guard(spinlock_irqsave, &vss->lock) + vss->suspended =3D true; fallthrough; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: scoped_guard(spinlock_irqsave, &vss->lock) { + if (!vss->prepared) + return 0; if (command !=3D SNDRV_PCM_TRIGGER_PAUSE_PUSH) vss->stopped =3D true; vss->xfer_enabled =3D false; - } - - msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_STOP, - GFP_KERNEL); - if (!msg) - return -ENOMEM; - - return virtsnd_ctl_msg_send_sync(snd, msg); + if (!vss->device_running) + return 0; + } + + rc =3D virtsnd_pcm_stop_device(vss, epoch); + if (rc && command !=3D SNDRV_PCM_TRIGGER_PAUSE_PUSH) { + guard(spinlock_irqsave)(&vss->lock); + + if (vss->reset_epoch =3D=3D epoch && !vss->resetting) { + vss->xfer_xrun =3D true; + substream->runtime->stop_operating =3D true; + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) + virtsnd_pcm_schedule_period_elapsed(vss); + } + } + if (rc && command =3D=3D SNDRV_PCM_TRIGGER_PAUSE_PUSH) { + if (virtsnd_pcm_start_queue(vss, epoch)) { + guard(spinlock_irqsave)(&vss->lock); + if (vss->reset_epoch =3D=3D epoch && !vss->resetting) { + vss->xfer_xrun =3D true; + vss->stopped =3D true; + substream->runtime->stop_operating =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + } + } else if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE) { + virtsnd_pcm_schedule_period_elapsed(vss); + } + } + return rc; default: return -EINVAL; } + +} + +static int +virtsnd_pcm_pause_restart_sync_stop(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + unsigned int js =3D msecs_to_jiffies(virtsnd_msg_timeout_ms); + enum virtio_pcm_pause_restart_state state; + struct virtio_snd_msg *msg; + long waited; + int rc; + u64 epoch; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting) + return -ECANCELED; + epoch =3D vss->reset_epoch; + vss->xfer_enabled =3D false; + vss->stopped =3D true; + state =3D vss->pause_restart_state; + } + cancel_work_sync(&vss->elapsed_period); + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RESET_REQUIRED || + virtsnd_pcm_pause_restart_transitioning(state)) { + virtsnd_pcm_pause_restart_fail(vss, true, epoch); + dev_err(&vss->snd->vdev->dev, + "SID %u: PCM state is unknown; device reprobe required\n", + vss->sid); + return -EIO; + } + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_NONE || + state =3D=3D VIRTSND_PCM_PAUSE_RESTART_CONFIGURED) { + if (virtsnd_pcm_msg_pending_num(vss)) { + virtsnd_pcm_pause_restart_fail(vss, true, epoch); + return -EIO; + } + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->stopped =3D false; + } + return 0; + } + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RUNNING) { + msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_STOP, + GFP_KERNEL, epoch); + if (!msg) { + virtsnd_pcm_pause_restart_fail(vss, false, epoch); + return -ENOMEM; + } + + rc =3D virtsnd_pcm_pause_restart_send(vss, msg, + BIT(VIRTSND_PCM_PAUSE_RESTART_RUNNING), + VIRTSND_PCM_PAUSE_RESTART_STOP_SUBMITTED, + VIRTSND_PCM_PAUSE_RESTART_STOPPED); + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_fail(vss, false, epoch); + return rc; + } + state =3D VIRTSND_PCM_PAUSE_RESTART_STOPPED; + } + + if (state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RELEASE_SENT || + state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RELEASED) { + waited =3D wait_event_timeout(vss->msg_empty, + !virtsnd_pcm_msg_pending_num(vss), js); + if (waited <=3D 0) { + rc =3D waited ? (int)waited : -ETIMEDOUT; + virtsnd_pcm_pause_restart_fail(vss, false, epoch); + return rc; + } + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + if (vss->msg_count) { + rc =3D -ETIMEDOUT; + } else { + vss->pause_restart_state =3D + VIRTSND_PCM_PAUSE_RESTART_RELEASED; + vss->prepared =3D false; + vss->release_required =3D false; + vss->stopped =3D false; + rc =3D 0; + } + } + if (rc) + virtsnd_pcm_pause_restart_fail(vss, false, epoch); + return rc; + } + + if (state !=3D VIRTSND_PCM_PAUSE_RESTART_PREPARED && + state !=3D VIRTSND_PCM_PAUSE_RESTART_STOPPED) { + virtsnd_pcm_pause_restart_fail(vss, true, epoch); + return -EIO; + } + + rc =3D virtsnd_pcm_pause_release_flush(vss, + BIT(VIRTSND_PCM_PAUSE_RESTART_PREPARED) | + BIT(VIRTSND_PCM_PAUSE_RESTART_STOPPED), epoch); + if (rc) { + if (rc =3D=3D -ECANCELED) + return rc; + virtsnd_pcm_pause_restart_fail(vss, false, epoch); + return rc; + } + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->prepared =3D false; + vss->stopped =3D false; + } + + return 0; } =20 /** - * virtsnd_pcm_sync_stop() - Synchronous PCM substream stop. + * virtsnd_pcm_release_locked() - Retire the closed PCM generation. * @substream: Kernel ALSA substream. * * The function can be called both from the upper level or from the driver @@ -504,7 +1369,7 @@ * Context: Process context. Takes and releases the VirtIO substream spinl= ock. * Return: 0 on success, -errno on failure. */ -static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream) +static int virtsnd_pcm_release_locked(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); struct virtio_snd *snd =3D vss->snd; @@ -512,35 +1377,54 @@ unsigned int js =3D msecs_to_jiffies(virtsnd_msg_timeout_ms); bool need_release; int rc; - - virtsnd_pcm_block_ack(substream); - - scoped_guard(spinlock_irqsave, &vss->lock) { + u64 epoch; + + lockdep_assert_held(&vss->lifecycle_lock); + /* Keep late RX completions from requeuing work after cancellation. */ + if (!vss->period_work_quiesced) { + disable_work_sync(&vss->elapsed_period); + vss->period_work_quiesced =3D true; + } + if (virtsnd_pcm_pause_restart_enabled(vss)) + return virtsnd_pcm_pause_restart_sync_stop(substream); + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting) + return -ECANCELED; + epoch =3D vss->reset_epoch; vss->xfer_enabled =3D false; need_release =3D vss->release_required; if (need_release) vss->stopped =3D true; } - cancel_work_sync(&vss->elapsed_period); + /* A rejected STOP leaves the device running and must precede RELEASE. */ + rc =3D virtsnd_pcm_stop_device(vss, epoch); + if (rc) { + substream->runtime->stop_operating =3D true; + return rc; + } =20 if (need_release) { msg =3D virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_RELEASE, - GFP_KERNEL); + GFP_KERNEL, epoch); if (!msg) { - scoped_guard(pcm_stream_lock_irqsave, substream) - substream->runtime->stop_operating =3D true; + substream->runtime->stop_operating =3D true; return -ENOMEM; } =20 - rc =3D virtsnd_ctl_msg_send_sync(snd, msg); + rc =3D virtsnd_pcm_send_sync(vss, msg); if (rc) { - scoped_guard(pcm_stream_lock_irqsave, substream) - substream->runtime->stop_operating =3D true; + substream->runtime->stop_operating =3D true; return rc; } =20 - scoped_guard(spinlock_irqsave, &vss->lock) + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; vss->release_required =3D false; + vss->device_running =3D false; + vss->prepared =3D false; + } } =20 /* @@ -549,22 +1433,48 @@ * Thus, we consider the absence of I/O messages in the queue as an * indication that the substream has been released. */ - rc =3D wait_event_interruptible_timeout(vss->msg_empty, + rc =3D wait_event_timeout(vss->msg_empty, !virtsnd_pcm_msg_pending_num(vss), js); if (rc <=3D 0) { dev_warn(&snd->vdev->dev, "SID %u: failed to flush I/O queue\n", vss->sid); - scoped_guard(pcm_stream_lock_irqsave, substream) - substream->runtime->stop_operating =3D true; + substream->runtime->stop_operating =3D true; =20 return !rc ? -ETIMEDOUT : rc; } =20 - scoped_guard(spinlock_irqsave, &vss->lock) + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->resetting || vss->reset_epoch !=3D epoch) + return -ECANCELED; + vss->prepared =3D false; vss->stopped =3D false; + } =20 return 0; +} + +/* Close ACK admission before any sleep; prepare owns the gate through RES= ET. */ +static int virtsnd_pcm_sync_stop_locked(struct snd_pcm_substream *substrea= m) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + bool preparing; + int rc; + + lockdep_assert_held(&vss->lifecycle_lock); + scoped_guard(pcm_stream_lock_irqsave, substream) { + guard(spinlock_irqsave)(&vss->lock); + preparing =3D vss->preparing; + vss->preparing =3D true; + vss->prepared =3D false; + vss->xfer_enabled =3D false; + } + rc =3D virtsnd_pcm_release_locked(substream); + scoped_guard(pcm_stream_lock_irqsave, substream) { + guard(spinlock_irqsave)(&vss->lock); + vss->preparing =3D preparing; + } + return rc; } =20 /** @@ -579,14 +1489,16 @@ virtsnd_pcm_pb_pointer(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - - scoped_guard(spinlock_irqsave, &vss->lock) - if (vss->xfer_xrun) + size_t hw_ptr; + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (!vss->prepared || vss->preparing || vss->xfer_xrun) return SNDRV_PCM_POS_XRUN; + hw_ptr =3D vss->hw_ptr; + } =20 return snd_pcm_indirect_playback_pointer(substream, - &vss->pcm_indirect, - vss->hw_ptr); + &vss->pcm_indirect, hw_ptr); } =20 /** @@ -601,14 +1513,69 @@ virtsnd_pcm_cp_pointer(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - - scoped_guard(spinlock_irqsave, &vss->lock) - if (vss->xfer_xrun) + struct snd_pcm_runtime *runtime =3D substream->runtime; + size_t hw_ptr =3D 0; + u64 total; + bool free_running; + bool xrun; + + if (vss->capture_staging) { + scoped_guard(spinlock_irqsave, &vss->lock) { + if (!vss->runtime_active || vss->preparing || vss->suspended || + (!vss->prepared && + !(runtime->state =3D=3D SNDRV_PCM_STATE_PAUSED && + virtsnd_pcm_pause_restart_enabled(vss) && + vss->pause_restart_state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RELEAS= ED))) + return SNDRV_PCM_POS_XRUN; + xrun =3D vss->xfer_xrun; + if (!xrun && + virtsnd_pcm_capture_refresh_mode_locked(vss, + &free_running)) + xrun =3D true; + if (!xrun && !virtsnd_pcm_capture_counters_valid(vss)) + xrun =3D true; + if (!xrun) { + total =3D vss->capture_callback_snapshot_active ? + vss->capture_callback_snapshot : + vss->capture_copied_total; + /* A full-ring observation gap is modulo-ambiguous. */ + if (total < vss->capture_pointer_seen_total || + total - vss->capture_pointer_seen_total >=3D + vss->buffer_bytes) { + xrun =3D true; + vss->xfer_xrun =3D true; + } else { + vss->capture_pointer_seen_total =3D total; + if (vss->capture_reset_observing) { + size_t period =3D snd_pcm_lib_period_bytes(substream); + + vss->capture_acked_total =3D total; + vss->capture_ready =3D 0; + vss->capture_notified_periods =3D + period ? div64_ul(total, period) : 0; + } + hw_ptr =3D virtsnd_pcm_ring_offset(total, vss->buffer_bytes); + } + } + if (xrun) + vss->xfer_xrun =3D true; + } + + if (xrun) return SNDRV_PCM_POS_XRUN; =20 + return bytes_to_frames(substream->runtime, hw_ptr); + } + + scoped_guard(spinlock_irqsave, &vss->lock) { + if (vss->xfer_xrun || !vss->prepared || vss->preparing) + return SNDRV_PCM_POS_XRUN; + vss->capture_pointer_seen_total =3D vss->capture_copied_total; + hw_ptr =3D vss->hw_ptr; + } + return snd_pcm_indirect_capture_pointer(substream, - &vss->pcm_indirect, - vss->hw_ptr); + &vss->pcm_indirect, hw_ptr); } =20 struct virtsnd_pcm_transfer { @@ -623,7 +1590,9 @@ struct virtsnd_pcm_transfer *transfer =3D container_of(rec, struct virtsnd_pcm_transfer, indirect); =20 - if (transfer->error) + if (transfer->error || + (substream->stream =3D=3D SNDRV_PCM_STREAM_CAPTURE && + substream->runtime->state =3D=3D SNDRV_PCM_STATE_DRAINING)) return; =20 transfer->error =3D virtsnd_pcm_msg_send(vss, rec->sw_data, bytes); @@ -635,6 +1604,48 @@ vss->xfer_enabled =3D false; vss->stopped =3D true; vss->substream->runtime->stop_operating =3D true; +} + +static int virtsnd_pcm_pb_ack_locked(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + struct virtsnd_pcm_transfer transfer =3D {}; + int rc; + + scoped_guard(spinlock_irqsave, &queue->lock) { + guard(spinlock)(&vss->lock); + if (vss->preparing) + return -EBADFD; + /* A latched XRUN must not re-arm sync_stop once teardown began. */ + if (vss->stopped && vss->xfer_xrun) + return -EBADFD; + if (vss->xfer_xrun) { + rc =3D -EPIPE; + } else if (!vss->prepared || vss->stopped) { + return -EBADFD; + } else { + transfer.indirect =3D vss->pcm_indirect; + rc =3D snd_pcm_indirect_playback_transfer(substream, + &transfer.indirect, + virtsnd_pcm_trans_copy); + } + if (!rc) + rc =3D transfer.error; + if (rc) + virtsnd_pcm_transfer_failed(vss); + else + vss->pcm_indirect =3D transfer.indirect; + } + if (rc) { + dev_err(&vss->snd->vdev->dev, + "SID %u: playback ACK transfer failed: %d\n", + vss->sid, rc); + /* ALSA treats -EPIPE as an XRUN in ACK and pointer paths. */ + return -EPIPE; + } + + return 0; } =20 /* The caller holds the PCM lock, also used by DROP and state transitions.= */ @@ -647,41 +1658,176 @@ 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; } } =20 +/* The PCM lock serializes ACK with prepare's accounting reset. */ static int virtsnd_pcm_pb_ack(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); - struct virtsnd_pcm_transfer transfer =3D {}; int rc; =20 + if (vss->indirect_reset_observing) + return 0; rc =3D virtsnd_pcm_ack_state(substream); if (rc) return rc; - + return virtsnd_pcm_pb_ack_locked(substream); +} + +static int virtsnd_pcm_capture_staged_ack(struct virtio_pcm_substream *vss, + struct snd_pcm_runtime *runtime) +{ + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + int rc =3D 0; + + /* + * The RX callback holds the queue lock while draining completions. + * Account ALSA's read under the substream lock alone so completions later + * in that drain observe the newly released ring space. + */ + scoped_guard(spinlock_irqsave, &vss->lock) { + snd_pcm_uframes_t appl_ptr; + snd_pcm_uframes_t appl_diff; + snd_pcm_uframes_t ack_frames; + snd_pcm_sframes_t diff; + size_t bytes; + u64 outstanding; + u64 next_acked_total; + bool free_running; + + if (!vss->runtime_active || vss->preparing || !runtime || !runtime->cont= rol || + !runtime->dma_area || vss->suspended) + return -EBADFD; + /* Preserve DRAINING tails only when no transfer XRUN is latched. */ + if (vss->stopped && vss->xfer_xrun) + return -EBADFD; + if (vss->xfer_xrun) + return -EPIPE; + if (!vss->prepared && + !(runtime->state =3D=3D SNDRV_PCM_STATE_PAUSED && + virtsnd_pcm_pause_restart_enabled(vss) && + vss->pause_restart_state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RELEASED= )) + return -EBADFD; + if (vss->stopped && runtime->state !=3D SNDRV_PCM_STATE_DRAINING && + !(runtime->state =3D=3D SNDRV_PCM_STATE_PAUSED && + virtsnd_pcm_pause_restart_enabled(vss) && + vss->pause_restart_state =3D=3D VIRTSND_PCM_PAUSE_RESTART_RELEASED= )) + return -EBADFD; + rc =3D virtsnd_pcm_capture_refresh_mode_locked(vss, + &free_running); + if (rc || !virtsnd_pcm_capture_counters_valid(vss)) { + vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + return -EPIPE; + } + + appl_ptr =3D runtime->control->appl_ptr; + diff =3D appl_ptr - vss->pcm_indirect.appl_ptr; + if (diff < -(snd_pcm_sframes_t)(runtime->boundary / 2)) + diff +=3D runtime->boundary; + if (diff < 0) { + vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + return -EPIPE; + } + appl_diff =3D diff; + ack_frames =3D appl_diff - appl_diff % runtime->min_align; + if (ack_frames > runtime->buffer_size || + (appl_diff > runtime->buffer_size && + appl_diff - runtime->buffer_size >=3D runtime->min_align)) { + vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + return -EPIPE; + } + + /* ACK includes any subperiod tail already copied to the ring. */ + bytes =3D frames_to_bytes(runtime, ack_frames); + outstanding =3D vss->capture_copied_total - + vss->capture_acked_total; + if ((!free_running && bytes > vss->capture_ready) || + (free_running && + (bytes > vss->buffer_bytes || bytes > outstanding)) || + check_add_overflow(vss->capture_acked_total, (u64)bytes, + &next_acked_total) || + next_acked_total > vss->capture_pointer_seen_total) { + vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + return -EPIPE; + } + + vss->capture_acked_total =3D next_acked_total; + if (!free_running) + vss->capture_ready -=3D bytes; + /* Keep any unaligned frame tail for the next acknowledgment. */ + vss->pcm_indirect.appl_ptr +=3D ack_frames; + if (vss->pcm_indirect.appl_ptr >=3D runtime->boundary) + vss->pcm_indirect.appl_ptr -=3D runtime->boundary; + } + + /* Preserve the queue -> substream lock order while refilling requests. */ scoped_guard(spinlock_irqsave, &queue->lock) { guard(spinlock)(&vss->lock); - if (vss->preparing || !vss->prepared) + if (vss->runtime_active && vss->xfer_enabled && + !vss->xfer_xrun) { + rc =3D virtsnd_pcm_capture_send(vss); + if (rc) { + vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + } + } + } + + if (rc) + dev_warn_ratelimited(&vss->snd->vdev->dev, + "SID %u: failed to refill capture queue after ACK: %d\n", + vss->sid, rc); + + /* The ACK is committed; a refill failure is reported by the next XRUN. */ + return 0; +} + +static int virtsnd_pcm_cp_ack_locked(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + struct snd_pcm_runtime *runtime =3D substream->runtime; + struct virtsnd_pcm_transfer transfer =3D {}; + int rc; + + if (vss->capture_staging) { + rc =3D virtsnd_pcm_capture_staged_ack(vss, runtime); + if (rc =3D=3D -EPIPE) { + guard(spinlock_irqsave)(&vss->lock); + virtsnd_pcm_transfer_failed(vss); + } + return rc; + } + + scoped_guard(spinlock_irqsave, &queue->lock) { + guard(spinlock)(&vss->lock); + if (vss->preparing) return -EBADFD; - if (vss->stopped) + /* A latched XRUN must not re-arm sync_stop once teardown began. */ + if (vss->stopped && vss->xfer_xrun) return -EBADFD; if (vss->xfer_xrun) { rc =3D -EPIPE; } else { + if (!vss->prepared || + (vss->stopped && + runtime->state !=3D SNDRV_PCM_STATE_DRAINING)) + return -EBADFD; transfer.indirect =3D vss->pcm_indirect; - rc =3D snd_pcm_indirect_playback_transfer(substream, + rc =3D snd_pcm_indirect_capture_transfer(substream, &transfer.indirect, virtsnd_pcm_trans_copy); - if (!rc) - rc =3D transfer.error; - } + } + if (!rc) + rc =3D transfer.error; if (rc) virtsnd_pcm_transfer_failed(vss); else @@ -689,80 +1835,252 @@ } if (rc) { dev_err(&vss->snd->vdev->dev, - "SID %u: playback ACK transfer failed: %d\n", + "SID %u: capture ACK transfer failed: %d\n", vss->sid, rc); + /* ALSA treats -EPIPE as an XRUN in ACK and pointer paths. */ return -EPIPE; } =20 return 0; } =20 +/* Every linked member's prepare closes the ACK gate under its PCM lock. */ static int virtsnd_pcm_cp_ack(struct snd_pcm_substream *substream) { struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); - struct virtsnd_pcm_transfer transfer =3D {}; int rc; =20 + /* RESET observes the pointer before returning discarded RX space. */ + if (vss->indirect_reset_observing) + return 0; rc =3D virtsnd_pcm_ack_state(substream); if (rc) return rc; - + return virtsnd_pcm_cp_ack_locked(substream); +} + +static int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + + guard(mutex)(&vss->lifecycle_lock); + scoped_guard(pcm_stream_lock_irqsave, substream) { + snd_pcm_state_t state =3D substream->runtime->state; + + /* + * Trigger holds the PCM lock through its control requests. Wait + * for it, then leave a replacement active generation alone. + * lifecycle_lock keeps prepare from replacing an inactive one + * until sync_stop_locked has finished its teardown. + */ + if (state =3D=3D SNDRV_PCM_STATE_RUNNING || + state =3D=3D SNDRV_PCM_STATE_PAUSED || + state =3D=3D SNDRV_PCM_STATE_PREPARED || + (state =3D=3D SNDRV_PCM_STATE_DRAINING && + substream->stream =3D=3D SNDRV_PCM_STREAM_PLAYBACK)) + return 0; + guard(spinlock_irqsave)(&vss->lock); + if (!vss->stopped) + return 0; + } + return virtsnd_pcm_sync_stop_locked(substream); +} + +static void virtsnd_pcm_capture_reset_error(struct virtio_pcm_substream *v= ss, + struct snd_pcm_runtime *runtime) +{ + size_t position =3D frames_to_bytes(runtime, runtime->status->hw_ptr); + size_t period =3D snd_pcm_lib_period_bytes(vss->substream); + + vss->preparing =3D false; + vss->pcm_indirect.appl_ptr =3D runtime->status->hw_ptr; + vss->pcm_indirect.sw_ready =3D 0; + if (vss->capture_staging) { + vss->hw_ptr =3D position; + vss->capture_copied_total =3D position; + vss->capture_acked_total =3D position; + vss->capture_pointer_seen_total =3D position; + vss->capture_ready =3D 0; + vss->capture_notified_periods =3D period ? position / period : 0; + vss->capture_callback_snapshot_active =3D false; + } + if (vss->xfer_xrun) { + virtsnd_pcm_transfer_failed(vss); + virtsnd_pcm_schedule_period_elapsed(vss); + } +} + +/* + * The PCM lock covers indirect accounting and core completion. Once lib_r= eset + * changes hw_ptr, complete the core rebase even if refilling fails. The l= atched + * XRUN rejects subsequent pointer/ACK operations; the worker publishes XR= UN to + * paused streams too. RESET success therefore does not imply stream recov= ery. + */ +static int virtsnd_pcm_indirect_reset(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + struct virtsnd_pcm_transfer transfer =3D {}; + snd_pcm_uframes_t paused_ptr =3D 0; + bool paused =3D runtime->state =3D=3D SNDRV_PCM_STATE_PAUSED; + int rc; + + /* Only PCM-locked callbacks access this observation flag. */ + vss->indirect_reset_observing =3D true; + rc =3D snd_pcm_lib_reset(substream); + vss->indirect_reset_observing =3D false; + if (rc) + return rc; + guard(spinlock_irqsave)(&queue->lock); + guard(spinlock)(&vss->lock); + vss->preparing =3D false; + if (vss->resetting || vss->xfer_xrun) { + virtsnd_pcm_capture_reset_error(vss, runtime); + return 0; + } + if (!snd_pcm_running(substream) && !paused) { + vss->pcm_indirect.appl_ptr =3D runtime->status->hw_ptr; + vss->pcm_indirect.sw_ready =3D 0; + return 0; + } + if (!vss->prepared || vss->stopped) { + vss->xfer_xrun =3D true; + virtsnd_pcm_capture_reset_error(vss, runtime); + return 0; + } + + transfer.indirect =3D vss->pcm_indirect; + if (paused) { + /* Observe and reclaim paused completions in one queue transaction. */ + vss->capture_pointer_seen_total =3D vss->capture_copied_total; + vss->indirect_reset_observing =3D true; + paused_ptr =3D snd_pcm_indirect_capture_pointer(substream, + &transfer.indirect, vss->hw_ptr); + vss->indirect_reset_observing =3D false; + if (paused_ptr =3D=3D SNDRV_PCM_POS_XRUN) { + vss->xfer_xrun =3D true; + virtsnd_pcm_capture_reset_error(vss, runtime); + return 0; + } + runtime->status->hw_ptr =3D paused_ptr; + } + transfer.indirect.sw_ready =3D 0; + /* Consume no application delta while returning discarded RX space. */ + transfer.indirect.appl_ptr =3D runtime->control->appl_ptr; + rc =3D snd_pcm_indirect_capture_transfer(substream, &transfer.indirect, + virtsnd_pcm_trans_copy); + if (!rc) + rc =3D transfer.error; + if (rc) { + vss->xfer_xrun =3D true; + virtsnd_pcm_capture_reset_error(vss, runtime); + return 0; + } + + /* RESET discards all samples through the observed software pointer. */ + transfer.indirect.sw_ready =3D 0; + transfer.indirect.appl_ptr =3D runtime->status->hw_ptr; + vss->pcm_indirect =3D transfer.indirect; + { + size_t period =3D snd_pcm_lib_period_bytes(substream); + + vss->capture_notified_periods =3D period ? + div64_ul(vss->capture_pointer_seen_total, period) : 0; + } + return 0; +} + +/* The core holds the stream lock through the matching appl_ptr reset. */ +static int virtsnd_pcm_reset(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + size_t position; + size_t period; + int rc; + + /* Reclaim RX space atomically with the pointer observation. */ + scoped_guard(spinlock_irqsave, &vss->lock) + vss->capture_reset_observing =3D true; + rc =3D snd_pcm_lib_reset(substream); + scoped_guard(spinlock_irqsave, &vss->lock) + vss->capture_reset_observing =3D false; + if (rc || !vss->capture_staging) + return rc; + + guard(spinlock_irqsave)(&queue->lock); + guard(spinlock)(&vss->lock); + vss->preparing =3D false; + if (vss->resetting || vss->xfer_xrun) { + virtsnd_pcm_capture_reset_error(vss, runtime); + return 0; + } + if (snd_pcm_running(substream)) { + /* Keep completions arriving after the core's pointer observation. */ + vss->capture_acked_total =3D vss->capture_pointer_seen_total; + vss->capture_ready =3D vss->capture_free_running ? 0 : + vss->capture_copied_total - vss->capture_acked_total; + } else { + position =3D frames_to_bytes(runtime, runtime->status->hw_ptr); + vss->hw_ptr =3D position; + period =3D snd_pcm_lib_period_bytes(substream); + vss->capture_copied_total =3D position; + vss->capture_acked_total =3D position; + vss->capture_pointer_seen_total =3D position; + vss->capture_ready =3D 0; + vss->capture_notified_periods =3D period ? position / period : 0; + } + vss->capture_callback_snapshot =3D 0; + vss->capture_callback_snapshot_active =3D false; + vss->pcm_indirect.appl_ptr =3D runtime->status->hw_ptr; + if (snd_pcm_running(substream) && vss->xfer_enabled && vss->prepared) { + rc =3D virtsnd_pcm_capture_send(vss); + if (rc) { + vss->xfer_xrun =3D true; + virtsnd_pcm_schedule_period_elapsed(vss); + } + } + /* Complete the matching core pointer rebase before reporting XRUN. */ + return 0; +} + +/* RESET can discard partial fills only after the transport returns owners= hip. */ +static int virtsnd_pcm_playback_reset(struct snd_pcm_substream *substream) +{ + struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime =3D substream->runtime; + struct virtio_snd_queue *queue =3D virtsnd_pcm_queue(vss); + size_t position; + int rc; + + /* The PCM lock excludes new ACK submissions through the entire reset. */ scoped_guard(spinlock_irqsave, &queue->lock) { guard(spinlock)(&vss->lock); - if (vss->preparing || !vss->prepared) - return -EBADFD; - /* Keep only the post-STOP capture tail available during drain. */ - if (vss->stopped && - (vss->xfer_xrun || - substream->runtime->state !=3D SNDRV_PCM_STATE_DRAINING)) - return -EBADFD; - if (vss->xfer_xrun) { - rc =3D -EPIPE; - } else { - transfer.indirect =3D vss->pcm_indirect; - rc =3D snd_pcm_indirect_capture_transfer(substream, - &transfer.indirect, - virtsnd_pcm_trans_copy); - if (!rc) - rc =3D transfer.error; - } - if (rc) - virtsnd_pcm_transfer_failed(vss); - else - vss->pcm_indirect =3D transfer.indirect; - } - if (rc) { - dev_err(&vss->snd->vdev->dev, - "SID %u: capture ACK transfer failed: %d\n", - vss->sid, rc); - return -EPIPE; - } - + if (vss->msg_count) + return -EBUSY; + } + vss->indirect_reset_observing =3D true; + rc =3D snd_pcm_lib_reset(substream); + vss->indirect_reset_observing =3D false; + if (rc) + return rc; + guard(spinlock_irqsave)(&queue->lock); + guard(spinlock)(&vss->lock); + position =3D frames_to_bytes(runtime, runtime->status->hw_ptr) % + vss->buffer_bytes; + vss->preparing =3D false; + vss->hw_ptr =3D position; + vss->pcm_indirect.hw_io =3D position; + vss->pcm_indirect.hw_data =3D position; + vss->pcm_indirect.sw_io =3D position; + vss->pcm_indirect.sw_data =3D position; + vss->pcm_indirect.hw_ready =3D 0; + vss->pcm_indirect.sw_ready =3D 0; + vss->pcm_indirect.appl_ptr =3D runtime->status->hw_ptr; + virtsnd_pcm_msg_reset(vss); return 0; -} - -/** - * virtsnd_pcm_reset() - Reset PCM pointers and complete prepare admission. - * @substream: Kernel ALSA substream. - * - * Context: PCM stream lock held. - * Return: 0 on success, -errno on failure. - */ -static int virtsnd_pcm_reset(struct snd_pcm_substream *substream) -{ - struct virtio_pcm_substream *vss =3D snd_pcm_substream_chip(substream); - int rc; - - rc =3D snd_pcm_lib_reset(substream); - scoped_guard(spinlock_irqsave, &vss->lock) { - if (vss->preparing) { - vss->prepared =3D !rc; - vss->preparing =3D false; - } - } - - return rc; } =20 /* PCM substream operators map. */ @@ -774,11 +2092,11 @@ .hw_params =3D virtsnd_pcm_hw_params, .hw_free =3D virtsnd_pcm_hw_free, .prepare =3D virtsnd_pcm_prepare, - .reset =3D virtsnd_pcm_reset, .trigger =3D virtsnd_pcm_trigger, .sync_stop =3D virtsnd_pcm_sync_stop, .pointer =3D virtsnd_pcm_pb_pointer, .ack =3D virtsnd_pcm_pb_ack, + .reset =3D virtsnd_pcm_playback_reset, }, { .open =3D virtsnd_pcm_open, @@ -787,10 +2105,38 @@ .hw_params =3D virtsnd_pcm_hw_params, .hw_free =3D virtsnd_pcm_hw_free, .prepare =3D virtsnd_pcm_prepare, - .reset =3D virtsnd_pcm_reset, .trigger =3D virtsnd_pcm_trigger, .sync_stop =3D virtsnd_pcm_sync_stop, .pointer =3D virtsnd_pcm_cp_pointer, .ack =3D virtsnd_pcm_cp_ack, }, }; + +/* Selected at open; the staging policy remains fixed for that runtime. */ +static const struct snd_pcm_ops virtsnd_pcm_capture_staged_ops =3D { + .open =3D virtsnd_pcm_open, + .close =3D virtsnd_pcm_close, + .ioctl =3D snd_pcm_lib_ioctl, + .reset =3D virtsnd_pcm_reset, + .hw_params =3D virtsnd_pcm_hw_params, + .hw_free =3D virtsnd_pcm_hw_free, + .prepare =3D virtsnd_pcm_prepare, + .trigger =3D virtsnd_pcm_trigger, + .sync_stop =3D virtsnd_pcm_sync_stop, + .pointer =3D virtsnd_pcm_cp_pointer, + .ack =3D virtsnd_pcm_cp_ack, +}; + +static const struct snd_pcm_ops virtsnd_pcm_capture_indirect_ops =3D { + .open =3D virtsnd_pcm_open, + .close =3D virtsnd_pcm_close, + .ioctl =3D snd_pcm_lib_ioctl, + .reset =3D virtsnd_pcm_indirect_reset, + .hw_params =3D virtsnd_pcm_hw_params, + .hw_free =3D virtsnd_pcm_hw_free, + .prepare =3D virtsnd_pcm_prepare, + .trigger =3D virtsnd_pcm_trigger, + .sync_stop =3D virtsnd_pcm_sync_stop, + .pointer =3D virtsnd_pcm_cp_pointer, + .ack =3D virtsnd_pcm_cp_ack, +}; From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.210.180 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845949; b=qaw//RG/WxA6sF2vV2V5yeovuepUC7JPxWVaaPbFFA/w87IcJHhh39pyPQpb1Fc1rXgl jRedaK3tzMWTmD8pkoTYyuyGi3HMqmq2v7rp2ghC6uRKeOjbjn5Bb+BNv1hSF7z45FvSZ GD/UhBEAjTXIEI8zHEawjNutHpbOmpGdwp7gvspZINSfxZ0iuFoNubQC3cJw8lsF22+Rn OICU/FVpjpI0nvMTA2DBVZRBvBaqInHyrqq5JD4deMrx7XMtZGiylw80kPTlEIoT/7ps5 iSFe8hV3HX213XOkPJKtN56fkvKSrwt9BqEzz7WM7lkf2eJ8X3nZRJil/13TurDHw9w== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845949; 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=ND/K4UdIV95I9PJFcgncTwO/Ff+v0DFt7H1UGo6AynY=; b=cBu77hZvvJ+g0w38vDK9Xb8EpVuYB9QhUBkMgo0bBpyCo8BXxNKEoNtABzQVYn53PqQ/ 9HdYmkVa6IfvT5K8cairp0HWmdXYf0aRp83Ihr57NyJC8BKj1bbavC9CZ2byjKnbI6UEL sKomtlhVjZZz95HJMnD4kZ6aXY1KA2pfx5EQYKLk9gysK6CbqXX6wewWxQP6kw2jdepHD ofweiVpbKUpLH7QIdMeO14n08ywD/2/NRO6kCd4PX70hYoUtj9VXjrWg2STcpC19h3F19 vbl2rgsMC3YglrY7hX3EFgl0SQuijfDdiCOwJQmWFhsVT2x4m5ncnU6WFEVH9qw0n8w== 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=h4MG+WAU; arc=none smtp.remote-ip=209.85.210.180 Received: from mail-pf1-f180.google.com (mail-pf1-f180.google.com [209.85.210.180]) (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 EC9EC606EEB1 for ; Thu, 5 Nov 2026 02:32:28 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org EC9EC606EEB1 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=h4MG+WAU Received: by mail-pf1-f180.google.com with SMTP id d2e1a72fcca58-89f85edd210so651002b3a.2 for ; Wed, 04 Nov 2026 18:32:28 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845948; x=1794450748; 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=ND/K4UdIV95I9PJFcgncTwO/Ff+v0DFt7H1UGo6AynY=; b=h4MG+WAUIg17ayp2jqseAit3lnCAASZn9oBrxo06jUVEVyzrP5pajRT4SgFbgwFahs lXUbg+ClgfxJ0b4rxjD4sgcXczw+CjE4jDyOfjurh+Acyeq2RVQHm5Kj+KElr+b2Gzd8 b0C/+AmK7AxNxWEn8uwASsIY66kpWBlAdk/emVZ0sQOWPc/9lV8zw2wUPLWIqdMa/7MB hRDsX4seWw7aJb0TvSYSjJdCoj/hB0pQe1oW8sXseYkUWAhjyNu9iyf8IFa5EjylSNQR aJQ9BmlepHHi53X8jtJ1TCS+au64Ukr9u77j3ZpyAjRCPshnOwQ217OtHO4Mfz12zXRH wgew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845948; x=1794450748; 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=ND/K4UdIV95I9PJFcgncTwO/Ff+v0DFt7H1UGo6AynY=; b=1RpQFAAKIxnQ32tH4YGLqx2mQ4BP/gnGPuXRDZ2sssH4Wwn+yBlzcQ3eC8W8uhHRqX 5KnwvH7CQawT0ZPQLuMfrQJBWSwa47l4Ay6j4yyEbXmq/k/oHOo92ltd8WfBGhntPfHP QvtBErw/8wxN0bjC0AsfIo4FEl//DWPOt7rZ5G29JMoeJTxoP7fsHHVPGZWyBrU+28cq y1lsh6xRhVbBSU5oxFbhsJjk1JrURy1ITbxoCkoDuVJb6wW4Su8/Z88niDh0SqXH3P9b YNBnU+lhPj+Lf09VMLDMT/GfoLHAKdxD3wcRSCtaNVVetb6ddDb6z7YNUr6mqx85c7Fd 6rxg== X-Forwarded-Encrypted: i=1; AKwUvBz7zJ81zPJAh0rCpwag2icRrrjheJYE2cP7rOUe/kDLkFAzLabAIjXr6flo+mZQnqg+AGMjEzMMVDoiBPA=@vger.kernel.org X-Gm-Message-State: APuU4Z/GcCbTUsLXiFajY1+Blsh+R8Bw3MG9D4OGEuhoPyw0eqMQ+fJN pyyfVy79bc0iJ6T8jp8Uk1YkvDIsUJTJL1+26HwrsLCVEuqTxOP9T8cR X-Gm-Gg: AYBFou0YBSlHuDueSRKjO4H1K4FmiRo73N3mtjjnmb35x9hN3lrbMqSobVEMLV7iaFH trZ14cg42jXSgr5drfN2XTfqYfJQXrnLSN1QDVrvSEcDjzci1azP1aysCajo664973WNpJmgSRI dhsPKdIu6cMmODR1wpBSFbm51QiiPgsgbWSERjIfd0xfcRxm6j84rwYmF+/2FMlLmXAjSTugUXZ j2gVAVSNF3FswPVx9RFWd+/P8XJ9D4g0XA3tGOQ7PH98IIrFTSkPPl6EiHQ43ysDvCfshO/5sNI CkzjfndbWhqmJwAsx9FJwqWtmHOESEle3o+fbwm43MKUpFq5iCPRo/aBHqcixPaldFxr+eL0285 01gDfA2qRQ4YuBR2jy13F4HzE22MnT7zZ6nBXnKZ0cv3O+WrhoTXCp3FvMexQHqi9DEUH4l/NSP W/jt9Yep8aXtUrqk5oWGvcZeVEtWGhoNQBqbBNUN51DtvXi+h44D6FxMeb5UaSrT22XQQJjPdwG vDrUl4kuGZh9MJwUaBZaUM71DmtNVEk2hqaSyEGCxq2DxMyM06tjQEJ4mtbMa/UtinftnD0Awah CZZPD9q6C/Fy8PY6V8lk8FlYWPCxv0Khc8JfS7EdAGoKFaiLJ94hOUrkaLew5Ubo9vhKsI4VYVg ZyU7h/ZIb+Qc6xotvJdXpV3UzXLp8Sg+W9S9sj2h66V4ujNg= X-Received: by 2002:a17:90b:3148:b0:3a8:13d6:f740 with SMTP id 98e67ed59e1d1-3ae897364c0mr14242904a91.22.1793845948373; Wed, 04 Nov 2026 18:32:28 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.25 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:27 -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 v16 11/12] ALSA: virtio: prioritize staged capture period work Date: Thu, 5 Nov 2026 11:31:39 +0900 Message-ID: <20261105023140.18373-12-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Queue staged capture period notifications on system_highpri_wq through the common scheduling helper. This allows capture notifications to make progress when ordinary workqueue workers are delayed by CPU contention. Playback and ordinary capture keep schedule_work(). This preserves the existing priority proposal without mixing RESET or lifecycle changes into it. It provides no hard real-time deadline and cannot prevent a userspace reader from overrunning its buffer. Assisted-by: LLM Signed-off-by: Joonho Lee --- sound/virtio/virtio_pcm.h | 4 ++++ 1 file changed, 4 insertions(+) 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 @@ -169,6 +169,10 @@ static inline bool virtsnd_pcm_schedule_period_elapsed(struct virtio_pcm_substream *vss) { + if (vss->direction =3D=3D SNDRV_PCM_STREAM_CAPTURE && + vss->capture_staging) + return queue_work(system_highpri_wq, &vss->elapsed_period); + return schedule_work(&vss->elapsed_period); } From nobody Thu Nov 5 05:12:14 2026 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.216.44 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793845953; b=WfLk7IbafC7A783B36I5fkoA7jtAMN6X9Cqqh8E0ALH5R+PySRVJntr/QJHpb3FB+6AE EMdAgJ+qtlLwwTaOQZn06SrydDEDLXCLTORpwYp75Jz0qMT3TV3Kt60KN/95B3b9pJH+O wyD9wK2U7eeIzNWgXKz7EHZtqUHxCCCflZ/AdOQ0n8o+2wG760C7Z7glHUsUA6bFWoskV 1TCX8eb7XDdAlnr/1nyNh+JFjZZ5jX4sKJn5wjcZae+33ytKoZOmRUltRZI9PgxTOBjva PkECRmqAsaFAewsOqCvX/k0z3pgRmGVoCP0qhTk2nrJ0lNebcUuhjvEGmzGUHEa0SeQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793845953; 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=GCeNfDJOwvZXuY8dgM3rlag3FgqE7583M6Aa4tPPS8I=; b=4ktrFpTwAtTitAOvF4PtYcN7ONmggBPfgrx9DN3v1XnEA8uq4noxruZtsnevhUzD/VzU F0zXH6N5ynhoDzNdKmVs8RcHI1NcZJoxfoPUjRanC1H5mMdbm0XVIJE0/LiF161b/XUGk GDDJm6ukemGCOkcXVjZlO3o0vze5UV9EvooctzHmmGlL1mTDbtU3uY/bQRyB6qvMPAq8v W11A//9c6/uWUggIFuFbrOWTu2fvtzBIx2mMniuN5oDwMQG+uhHmUW79gor8+2mXmA640 GxY1Ynf26g1MaS3IS8MM2QKuXuB7igtlx5r1Ryj+wtReK2aOpwMoNYpYwDxs8Kg/7PA== 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=JoJAcVpJ; arc=none smtp.remote-ip=209.85.216.44 Received: from mail-pj1-f44.google.com (mail-pj1-f44.google.com [209.85.216.44]) (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 36543606A339 for ; Thu, 5 Nov 2026 02:32:32 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 36543606A339 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, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=JoJAcVpJ Received: by mail-pj1-f44.google.com with SMTP id 98e67ed59e1d1-3b08cf16decso968622a91.0 for ; Wed, 04 Nov 2026 18:32:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793845952; x=1794450752; 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=GCeNfDJOwvZXuY8dgM3rlag3FgqE7583M6Aa4tPPS8I=; b=JoJAcVpJOhyXee/JWB/PoQB3MtMG0HfUat9AsQ6cBMlU8S6JPeIQJbDuYbmUVLqJ9C lrlXSOC65FYzLGRJTdED2xImiZt4SQ4+PC++D2eKebKZO0NNd1tmGVB2wfevoGPRKO8z n2M557KvKztmp/W7ss4S3PlJQNcyOdq8ED15D0y4eWpswzKooypvVGtAoB52ZavJFlv1 H7QA+ft+doBXkTPMaN8gYdY5p2uASU8royawd534q1uV3Xxg6nB+1KpMWycSyDxi/jjz nT7HOWloATHa6lvV1xLxJNFIkmQzINNt3NMIc5OGGm/oUEmtSUIOtjhb0Y3I5nS8peHj CluA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793845952; x=1794450752; 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=GCeNfDJOwvZXuY8dgM3rlag3FgqE7583M6Aa4tPPS8I=; b=e2HPMUg4hqOiDeB3zzTVWyUM5c3peCWAurmbfvti1T/JKw3PLawIE6uv0B/UGGHJEv tidhL5DGiG+9L+D/Oq1be9Rv+IDOMfz7Yv+Q+tRqt1RKB0CQGTdbqOYnHnKmWrLXzm9f CIbZInetqGEz69YUB2B6rDHyDeEsMrw8dN0AbpcXDDrtauDEnGEvx8uM8wOYXo8UzHGh FX6+zBu0QnSf/VLzmWRfpjrpS6TKiANalLEOpQS0WsXuRL5DzAbKkTlomgnTFddif1W0 abj4OUt1/F5Yoqd0GmTCi3ORsif8dvdWGiclaxfUK4ZZSLrIf0eg2nc6IBOK2x3hhTRS u1GA== X-Forwarded-Encrypted: i=1; AKwUvBw4OLfH4hvflNemH5VkL0iXfWBYioUwRPMkRbrbb1lnudtM1QryPT4z2rz/QhS0lFRGjLP4tt7jQTvmRHs=@vger.kernel.org X-Gm-Message-State: APuU4Z/+DsSfy19hsgLBHJpj9P94aJ6A67ttSl5PG8nC6GESxCemX8ka DWgXe0DswOUTAtAEa32GYeZ19ysbfv4/PJNHTwRR+VE+0MTRQ0h4nO5F X-Gm-Gg: AYBFou1pfyF5SDLZpk8aLAKvD26BcMI8dtgA2EvaNWT+H9aZqcuL0lU+NH13pgzqhdj a796j5CtSXf+ICY3eO0TEorwmBTl19NHgpKWWc3X6hEXyxxQKbX/oGBFrASQ9O2AACytUioFQ7/ +Z2dqURS1xtNiKZOjtidFeVNbPOZs+rGXPJfSGJZx94ZcJJbrYdwdWJe0eoeFUZBKzhBP/G40Br Ih6rEZdZ0Sdyhe+W5t19iFnTjaYDWbOJvAABlYC6k69vwhNBqXgzUf3d061URTnChZUoLo9MpQX F+UMHns6fnPkIG9R3lmTX+Uw9jGrsBk+U/cM0gg4HaZ1JSXAnyFwk/x8SLbOkyIu9HsXXo24/nM y2RbxAKVJbs+f9prXndDgzdnjMd56LsLeKG2WfpDxkzTJxBPkmQyfLpAfSJCYGh1Ne6sr9NsPyu Eo+u5Zu4wF8DvNpRTUYB+qgII3xARg5/Nn7pNjQnRjkybQsKkppwg7hMaKfb4Hc266KtCcw/LQr UDZyuoSYWVZxAaZynEATDAKdKcCQ4wuR161OgRyRsabEwmNDMzNgUcdGFHj9mscmSagArnS0hKR Ij4Ua1uB9B2oblVnuIcag+eITM4Jc1VbT3AAQz2+ZIR4yrIdAljzdbQeNqVlUqeasKPx1cQ+NuK E5t0dQwvdDxRkpd6/vdfO3PQVnZKl2phptSknTH2CMo82i1YWjKsIn5S4KPP61CzfRpoO X-Received: by 2002:a17:90b:1845:b0:3ac:9d14:feb1 with SMTP id 98e67ed59e1d1-3ae8976dcfcmr14443930a91.43.1793845952031; Wed, 04 Nov 2026 18:32:32 -0800 (PST) Received: from localhost.localdomain ([183.96.175.225]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3b0b323cd3csm1292775a91.3.2026.11.04.18.32.28 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 04 Nov 2026 18:32:31 -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 v16 12/12] docs: describe virtio-sound capture options Date: Thu, 5 Nov 2026 11:31:40 +0900 Message-ID: <20261105023140.18373-13-mynameisjune111@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20261105023140.18373-1-mynameisjune111@gmail.com> References: <20261105023140.18373-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: quoted-printable Content-Type: text/plain; charset="utf-8" Document the five opt-in virtio_snd capture module parameters. State that capture_allow_empty, capture_allow_short, capture_packed_status and capture_pause_restart require capture_staging, and use the ALSA kernel parameter restriction tag for each entry. Assisted-by: LLM Signed-off-by: Joonho Lee --- Documentation/admin-guide/kernel-parameters.txt | 29 ++++++++++++++++++++= +++ 1 file changed, 29 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentatio= n/admin-guide/kernel-parameters.txt --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -8344,6 +8344,35 @@ =20 Can be used multiple times for multiple devices. =20 + virtio_snd.capture_allow_empty=3D [HW,ALSA] + Retry a bounded number of empty startup capture replies + when virtio_snd.capture_staging=3D1. + Format: + Default: false. Validate with the target device. + + virtio_snd.capture_allow_short=3D [HW,ALSA] + Accept non-empty, frame-aligned short capture replies + when virtio_snd.capture_staging=3D1. + Format: + Default: false. Validate with the target device. + + virtio_snd.capture_packed_status=3D [HW,ALSA] + Read status immediately after the returned capture data + when virtio_snd.capture_staging=3D1. + Format: + Default: false. Validate with the target device. + + virtio_snd.capture_pause_restart=3D [HW,ALSA] + Release and reprepare capture streams across pause when + virtio_snd.capture_staging=3D1. + Format: + Default: false. Validate with the target device. + + virtio_snd.capture_staging=3D [HW,ALSA] + Enable staged capture using request-owned buffers. + Format: + Default: false. Validate with the target device. + vga=3D [BOOT,X86-32] Select a particular video mode See Documentation/arch/x86/boot.rst and Documentation/admin-guide/svga.rst.