From nobody Tue Feb 10 00:44:42 2026 Received: from esa3.hgst.iphmx.com (esa3.hgst.iphmx.com [216.71.153.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DCBB938884E for ; Thu, 29 Jan 2026 12:16:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=216.71.153.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769689006; cv=none; b=fOYtBW9394Iwrwzab9hW/FP6fHm2U8+q+AWmvVbnqWhYpmjiHM1o3+O8jbgQVL2e5jKxW8vp5w9tFR7VKXz3n+gXOB23Em8UNtQyUsQECm4QgD0CVttJl1JJxTReiuN9Zv9uCI10/uq/fjmDaiQQSjiqR9oI1TXzrpqHTXI8gkY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769689006; c=relaxed/simple; bh=JMkdvNMJSlH436HKF5aGctvMhpbIyLyE7TXVNQEAl6Y=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=La8J0XZN0i8gvaljYtJZngY8ErYU9xGo7k5as7ohxZFVg+AaqejfDg/gCK4muej9UV3bTF2/xvjqPOijB2Lvku2R/T9heLDXJDq4/o/CibZ4ERLyhUCJDBLkMJUPTRVxXBnhSkVEcwwm8+ZC7pztedQFDSOoJki1ejdwUEsmmuw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=wdc.com; spf=pass smtp.mailfrom=wdc.com; dkim=pass (2048-bit key) header.d=wdc.com header.i=@wdc.com header.b=lqktm03s; arc=none smtp.client-ip=216.71.153.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=wdc.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=wdc.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=wdc.com header.i=@wdc.com header.b="lqktm03s" DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=wdc.com; i=@wdc.com; q=dns/txt; s=dkim.wdc.com; t=1769689002; x=1801225002; h=from:to:cc:subject:date:message-id:mime-version: content-transfer-encoding; bh=JMkdvNMJSlH436HKF5aGctvMhpbIyLyE7TXVNQEAl6Y=; b=lqktm03sibkm00SbRY08pSo1CnYhVwUIRW8H98iKYLRT8GHb0rvknEHB Hpvo3qy669AwYTQKWmwdcDxf8edZVFFGZCOXKbnxuz9LbZ9TLWMLm92AZ RjJ/NUYa6DfgvE4BcOZJs7KE7kPcHT48jOzb1jKH6aiIo3b4NB3Zw9U0/ +gq18EzZJuBxVLidHGvW5muIfSyg6nU0prfj9PUna/n7n46f9ENbYWm5D 2nBL6ioWCncu035x4/KIMOQkSIo55C9J/x2NV5FToB0wYVrztMFc/9nMM enGQMQA2dpnLDHB8tWXTMRcPdOedciI38hkqVPXwyVshg1IC2u8QerfMk w==; X-CSE-ConnectionGUID: pp3rqBcYQ4OODtMozWrSHg== X-CSE-MsgGUID: zioyPAa0RZimPMTG2IAPbg== X-IronPort-AV: E=Sophos;i="6.21,260,1763395200"; d="scan'208";a="140358008" Received: from h199-255-45-14.hgst.com (HELO uls-op-cesaep01.wdc.com) ([199.255.45.14]) by ob1.hgst.iphmx.com with ESMTP; 29 Jan 2026 20:16:35 +0800 IronPort-SDR: 697b4fa4_iI8QP61UjR38nNi4z3lBovSrz46PuLXa7ncqE0bKEW9jNsh +dkvWKFLwejANd21bW+O/y9RWaLHXnOxkiYLXEA== Received: from uls-op-cesaip01.wdc.com ([10.248.3.36]) by uls-op-cesaep01.wdc.com with ESMTP/TLS/ECDHE-RSA-AES128-GCM-SHA256; 29 Jan 2026 04:16:37 -0800 WDCIronportException: Internal Received: from wdap-6j0m9urvwf.ad.shared (HELO neo.fritz.box) ([10.224.28.90]) by uls-op-cesaip01.wdc.com with ESMTP; 29 Jan 2026 04:16:34 -0800 From: Johannes Thumshirn To: Cc: Alexander Graf , Johannes Thumshirn , "Michael S. Tsirkin" , Jason Wang , Xuan Zhuo , =?UTF-8?q?Eugenio=20P=C3=A9rez?= , virtualization@lists.linux.dev (open list:VIRTIO CORE), linux-kernel@vger.kernel.org (open list) Subject: [PATCH v2] virtio_ring: Add READ_ONCE annotations for device-writable fields Date: Thu, 29 Jan 2026 13:15:58 +0100 Message-ID: <20260129121604.745681-1-johannes.thumshirn@wdc.com> X-Mailer: git-send-email 2.52.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Alexander Graf KCSAN reports data races when accessing virtio ring fields that are concurrently written by the device (host). These are legitimate concurrent accesses where the CPU reads fields that the device updates via DMA-like mechanisms. Add accessor functions that use READ_ONCE() to properly annotate these device-writable fields and prevent compiler optimizations that could in theory break the code. This also serves as documentation showing which fields are shared with the device. The affected fields are: - Split ring: used->idx, used->ring[].id, used->ring[].len - Packed ring: desc[].flags, desc[].id, desc[].len Signed-off-by: Alexander Graf [jth: Add READ_ONCE in virtqueue_kick_prepare_split ] Co-developed-by: Johannes Thumshirn Signed-off-by: Johannes Thumshirn Reviewed-by: Alexander Graf --- Changes to v1: - Updated comments (mst, agraf) - Moved _read suffix to prefix in newly introduced functions (mst) - Update my minor contribution to Co-developed-by (agraf) - Add "in theory" to changelog --- drivers/virtio/virtio_ring.c | 69 ++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index ddab68959671..66802d11d30e 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -222,6 +222,48 @@ struct vring_virtqueue { #endif }; =20 +/* + * Accessors for device-writable fields in virtio rings. + * These fields are concurrently written by the device and read by the dri= ver. + * Use READ_ONCE() to prevent compiler optimizations and document the + * intentional data race and prevent KCSAN warnings. + */ +static inline u16 vring_read_used_idx(const struct vring_virtqueue *vq) +{ + return virtio16_to_cpu(vq->vq.vdev, + READ_ONCE(vq->split.vring.used->idx)); +} + +static inline u32 vring_read_used_id(const struct vring_virtqueue *vq, u16= idx) +{ + return virtio32_to_cpu(vq->vq.vdev, + READ_ONCE(vq->split.vring.used->ring[idx].id)); +} + +static inline u32 vring_read_used_len(const struct vring_virtqueue *vq, u1= 6 idx) +{ + return virtio32_to_cpu(vq->vq.vdev, + READ_ONCE(vq->split.vring.used->ring[idx].len)); +} + +static inline u16 vring_read_packed_desc_flags(const struct vring_virtqueu= e *vq, + u16 idx) +{ + return le16_to_cpu(READ_ONCE(vq->packed.vring.desc[idx].flags)); +} + +static inline u16 vring_read_packed_desc_id(const struct vring_virtqueue *= vq, + u16 idx) +{ + return le16_to_cpu(READ_ONCE(vq->packed.vring.desc[idx].id)); +} + +static inline u32 vring_read_packed_desc_len(const struct vring_virtqueue = *vq, + u16 idx) +{ + return le32_to_cpu(READ_ONCE(vq->packed.vring.desc[idx].len)); +} + static struct vring_desc_extra *vring_alloc_desc_extra(unsigned int num); static void vring_free(struct virtqueue *_vq); =20 @@ -736,9 +778,10 @@ static bool virtqueue_kick_prepare_split(struct virtqu= eue *_vq) LAST_ADD_TIME_INVALID(vq); =20 if (vq->event) { - needs_kick =3D vring_need_event(virtio16_to_cpu(_vq->vdev, - vring_avail_event(&vq->split.vring)), - new, old); + u16 event =3D virtio16_to_cpu(_vq->vdev, + READ_ONCE(vring_avail_event(&vq->split.vring))); + + needs_kick =3D vring_need_event(event, new, old); } else { needs_kick =3D !(vq->split.vring.used->flags & cpu_to_virtio16(_vq->vdev, @@ -808,8 +851,7 @@ static void detach_buf_split(struct vring_virtqueue *vq= , unsigned int head, =20 static bool more_used_split(const struct vring_virtqueue *vq) { - return vq->last_used_idx !=3D virtio16_to_cpu(vq->vq.vdev, - vq->split.vring.used->idx); + return vq->last_used_idx !=3D vring_read_used_idx(vq); } =20 static void *virtqueue_get_buf_ctx_split(struct virtqueue *_vq, @@ -838,10 +880,8 @@ static void *virtqueue_get_buf_ctx_split(struct virtqu= eue *_vq, virtio_rmb(vq->weak_barriers); =20 last_used =3D (vq->last_used_idx & (vq->split.vring.num - 1)); - i =3D virtio32_to_cpu(_vq->vdev, - vq->split.vring.used->ring[last_used].id); - *len =3D virtio32_to_cpu(_vq->vdev, - vq->split.vring.used->ring[last_used].len); + i =3D vring_read_used_id(vq, last_used); + *len =3D vring_read_used_len(vq, last_used); =20 if (unlikely(i >=3D vq->split.vring.num)) { BAD_RING(vq, "id %u out of range\n", i); @@ -923,8 +963,7 @@ static bool virtqueue_poll_split(struct virtqueue *_vq,= unsigned int last_used_i { struct vring_virtqueue *vq =3D to_vvq(_vq); =20 - return (u16)last_used_idx !=3D virtio16_to_cpu(_vq->vdev, - vq->split.vring.used->idx); + return (u16)last_used_idx !=3D vring_read_used_idx(vq); } =20 static bool virtqueue_enable_cb_delayed_split(struct virtqueue *_vq) @@ -1701,10 +1740,10 @@ static void detach_buf_packed(struct vring_virtqueu= e *vq, static inline bool is_used_desc_packed(const struct vring_virtqueue *vq, u16 idx, bool used_wrap_counter) { - bool avail, used; u16 flags; + bool avail, used; =20 - flags =3D le16_to_cpu(vq->packed.vring.desc[idx].flags); + flags =3D vring_read_packed_desc_flags(vq, idx); avail =3D !!(flags & (1 << VRING_PACKED_DESC_F_AVAIL)); used =3D !!(flags & (1 << VRING_PACKED_DESC_F_USED)); =20 @@ -1751,8 +1790,8 @@ static void *virtqueue_get_buf_ctx_packed(struct virt= queue *_vq, last_used_idx =3D READ_ONCE(vq->last_used_idx); used_wrap_counter =3D packed_used_wrap_counter(last_used_idx); last_used =3D packed_last_used(last_used_idx); - id =3D le16_to_cpu(vq->packed.vring.desc[last_used].id); - *len =3D le32_to_cpu(vq->packed.vring.desc[last_used].len); + id =3D vring_read_packed_desc_id(vq, last_used); + *len =3D vring_read_packed_desc_len(vq, last_used); =20 if (unlikely(id >=3D vq->packed.vring.num)) { BAD_RING(vq, "id %u out of range\n", id); --=20 2.52.0