From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f42.google.com (mail-pj2-f42.google.com [74.125.227.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C4FC5221DA for ; Fri, 18 Sep 2026 18:25:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755933; cv=none; b=h2Eu67dgj/gOWH9VturrE0P5dbCz17/ZSjjvV8bg1rwQVM6WIE+sj+YPz+WMhIF3rtXN9kUWEsyLWPwGxcJh06XPhUak8XcBabFRkbnGjmpwwI36A+1nDvMErWDum3S0y1Doy7Zv582bMirOJh+SxWtZ33SyCbJ7Z7q8HhRQJd4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755933; c=relaxed/simple; bh=FwBxD9C+mtpAY3FvuFjka8wR/91ggAo5qXxEvQFo4r8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cMARmaRXMrrE2Rd+5d1oLYvnK5xj+8P6mlO1ygQEuDHmLCopbd4DjAYIIM5FVgO7l5N6ltyFfD1XiPrDAQriNLP72pM7VeanXttu5DZ3aovS4ovspVqwHfAsNsoqfkaBirgoRKf+CfoaC9EmjlKn9z6pXk2DdkrwiXEAiG9BpyM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=iyCT3uuL; arc=none smtp.client-ip=74.125.227.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="iyCT3uuL" Received: by mail-pj2-f42.google.com with SMTP id d9443c01a7336-2ddb44ad1c1so11305955ad.3 for ; Fri, 18 Sep 2026 11:25:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755930; x=1790360730; 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=JoyOc8CCoSjGkrWB9rL6T/fUpbguLCUgVumFgPvb6SI=; b=iyCT3uuLPseT5dBjBbZdM+cfyLZnZQ0dLpT7vuP0AMM+DEWtKF8jcCvhynuxtAIfs3 RSmZ7PbmQyzvBr15YeWLtFlObWV0wqBacjuTR9qNtd2cXJEOvm+5uOOALoQP9at7gq3N p03t4BKl1qqcxPWUrneO0y60ub5fhcItYk4HulNctIodwimT1JXFd0SHyOGp3xDGxmGm cu/HT4xaecAYK+pYmibj3vMGD+rozvgzkVbsbtycH4pZWLTk4tZ/owB5bqTcjw8H56oa /wrrKJu6F7D2imhV0/+MY8Y/a/Noajd0pK5+6iakfP5ug8JSuXPL9FmcuntI2S72+6ak H1og== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755930; x=1790360730; 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=JoyOc8CCoSjGkrWB9rL6T/fUpbguLCUgVumFgPvb6SI=; b=aukLbYbosJItYbfu9sdP0rjeH0SXU8c6VH20IizwmC+gNoA3+urXSZioR4gCog3tI3 hL72xrW7cEenlFBX9YQ8v435NljeeQB2cPRVQDkmxYmFCl0atilZq+7kjWANJAZmuTFs IPl6cxR4PNChTSFkmHZltm+wOvxlcq37s5mopSlyQRtxHHc4YNnBoOMDO5+egDikp3Rr Yj4fyzLuf9g2tzDRO8JTe8PR6MRvr3Mxj9O03n15gotB+nqFbTAA4ctg8vdqWt0g8mpn PlZlbsQhwvZ7hRcE/CkZizgmk0U1EnJvZrmqUEXBddJOwYgSz6u6P+v9gXCsc190ACWf 55Eg== X-Forwarded-Encrypted: i=1; AKwUvBwSnRxOODjBAPuabeFDm6vjilv5NiqHQHpRRrYSjZaFo2OHI1o8FpXUnBnEdPFbGzaFc1VN3eAopmkECiE=@vger.kernel.org X-Gm-Message-State: AFuF++la9iHMG/+31xNIunzXQcjX+9V0rY0CgEyRRDPwmRaVdWFj4LaU usCdcO2wbuuZ0fhPMv+z0pKlaiHBRu4ZFmuAR49a+BNYuRIobMUFIprF X-Gm-Gg: AYBFou062cftmM6XtHlKSL8cihVr+qvkjS2BvVxKjdEVkigrOmoc8xgiAlIrYwQVByZ tbP6DBDCD0fbGnY4sPWYoTsOdUYGagYPnevHSDEEtMCpfNB5mb4OyuFe79EW9UMM9l3BTYbb8M0 IruMj+BfgK0qEPIk8olpr9jxQL84UCGddjwM64k7/yi0TtHYzaipexxIgQVH1+CAoNxPc46V/uF nVsAQtwoj0ldPgI8159waaWHlqVSCps/FYuXPuEK+p+37uDJjprhIjRXP47djnvINqEQUvFuZOw evbqXKraXfQkDDkmRtkrXnO/sYeIDglVcj9WbX7WcbGdVxYnmZ/Bhm+LrTXj7YWgdO4OBXSMHCJ onOT2XLjRapE7SIlcmtJMXpolDcNk2H4+80JpW5jM0lMu0mvSsO4N5Y2ByyqQz1rxsDz6afCskk ZhN44/oLbtbNUIUdypDopl4a98Bk11k5YvvH+2yy1EQDE4GHcAmkPVQCRJEql6aW/dMbXCq+ZXJ Os8EKhGvkHVA8tH3oB6i23KXmj2iszfbxn6Sk3Bh9g5kCL0KhVu0n4lqaB1u4kFgtjt/6jFSjcW uH/dmNv3 X-Received: by 2002:a17:903:1a0b:b0:2dd:7549:39d0 with SMTP id d9443c01a7336-2ddb1b86d77mr59804075ad.18.1789755929744; Fri, 18 Sep 2026 11:25:29 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:25:28 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device Date: Sat, 19 Sep 2026 03:24:39 +0900 Message-ID: <499ecb38dc0b58b979ca1f38d2d30b4ef56cf825.1789753020.git.kimjinseob88@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Add a binding for the generic Open Sensor Fusion host interface. Open Sensor Fusion devices report capabilities and samples over an OSF protocol stream. Sensor channels are discovered at runtime from capability reports instead of being described individually in Device Tree. The protocol version is discovered at runtime from the OSF frame header. OSF GREEN is a product identity, and OSF0 is a wire-format magic value, so neither is used as the Linux compatible string. Reviewed-by: Conor Dooley Signed-off-by: Jinseob Kim --- .../bindings/iio/opensensorfusion,osf.yaml | 52 +++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + MAINTAINERS | 6 +++ 3 files changed, 60 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/opensensorfusion,= osf.yaml diff --git a/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yam= l b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml new file mode 100644 index 000000000000..3998390828cc --- /dev/null +++ b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/opensensorfusion,osf.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Open Sensor Fusion Sensor Aggregation Hub + +maintainers: + - Jinseob Kim + +description: | + This binding documents the generic Open Sensor Fusion host interface. Op= en + Sensor Fusion is a sensor aggregation hub. The hub exposes an OSF protoc= ol + data stream over its host interface and reports capabilities and samples= for + multiple sensor classes. The actual sensor channels are discovered at ru= ntime + from OSF capability reports instead of describing them in Device Tree. T= he + protocol version is discovered at runtime. + + Public project documentation is available at: + + https://github.com/opensensorfusion + + OSF0, protocol_major, and protocol_minor are wire-protocol details + exchanged in OSF frames. + +allOf: + - $ref: /schemas/serial/serial-peripheral-props.yaml# + +properties: + compatible: + const: opensensorfusion,osf + + vcc-supply: + description: + Regulator supplying power to the Open Sensor Fusion device. + +required: + - compatible + - vcc-supply + +unevaluatedProperties: false + +examples: + - | + serial { + sensor { + compatible =3D "opensensorfusion,osf"; + vcc-supply =3D <&vcc_sensor>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Docum= entation/devicetree/bindings/vendor-prefixes.yaml index ba2002969373..0ec4e481c64b 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1267,6 +1267,8 @@ patternProperties: description: OpenPandora GmbH "^openrisc,.*": description: OpenRISC.io + "^opensensorfusion,.*": + description: Open Sensor Fusion "^openwrt,.*": description: OpenWrt "^option,.*": diff --git a/MAINTAINERS b/MAINTAINERS index 214aeee7642e..9463b8111d52 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20511,6 +20511,12 @@ F: Documentation/process/maintainer-devicetree.rst F: arch/*/boot/dts/ F: include/dt-bindings/ =20 +OPEN SENSOR FUSION +M: Jinseob Kim +S: Maintained +F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml +K: opensensorfusion + OPENCOMPUTE PTP CLOCK DRIVER M: Vadim Fedorenko L: netdev@vger.kernel.org --=20 2.43.0 From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f12.google.com (mail-pj2-f12.google.com [74.125.227.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6E29D5221F7 for ; Fri, 18 Sep 2026 18:25:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755938; cv=none; b=aqdxHkjcE+hy410id1BtbfDOgcAHOeOiVR7i1Qj2l91jymtnqlWNMnBsT/oWxVqmxgVJ6erudFp8NU0/x541DDYQSFo6LK5WQZiWwU8S54NEFKQTe/eRx125l+HoxZB1oQ0j8tiGwh9egCud7HoLfs3nJ72DFxV9+kXctPo+UwU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755938; c=relaxed/simple; bh=R9dGmC+v2bw7NAh5ww8FtZ986UH1u92ISE+NGE6Rr5s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kd7Nrai2QINSGHla1s6aT5vIys1FZsGgIomjf+ME0GPHkzRAbg52pb3zaLQdELx5IB6JcomXSUSuQtYsIUtfvApPlw78IYTPZQ9vlWPHH2gu+F6yHM9+k7PT2xbEIEPxS48DQom28jf4oXxqKZrleeeearbsiZZB8+/85c3cIh8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=BuDhUrlx; arc=none smtp.client-ip=74.125.227.140 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="BuDhUrlx" Received: by mail-pj2-f12.google.com with SMTP id d9443c01a7336-2dd77300825so11319295ad.1 for ; Fri, 18 Sep 2026 11:25:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755934; x=1790360734; 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=s2CKXsU1UIOkbMCPUX3VEuk4lYZb15gx+txcbf/aJKQ=; b=BuDhUrlxhnKebOyxuOHfj0fzA4OdpGJp11GULD0VVXcPu3aVt1licKZgF/rwY+ewJC WUMX7fGpTcsjjU3gmtm/dH9hmk77t1Tpl+CjfSSIJiUdBEZRmAigflBnSgQYtbWOLI1A CtDmqYFyK7e8L28Xjg0AMFWhUiHzycvFeQLugBIEHcb/Bbyglpjer3CNQIR1v0lUPsW3 med8s7ToVe3xsl59BYWaAUVZPUlTuNeZ12r3vsHJNnX1/tDsH+5xQHMOpdd2nYSYbCHl WCogODA9FYSMc6I1cjBpw6zUgsAuP0MBKO+lpJYPa/nSpsB3OHngeKVmVvxIPSUCXkO6 hp/A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755934; x=1790360734; 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=s2CKXsU1UIOkbMCPUX3VEuk4lYZb15gx+txcbf/aJKQ=; b=2ZT1ucVOjt3AeJynz5Zb663ctaPqj/uOJ74ptfG+6yWJ0kA7VQjmpD03+WivUwrLb3 DQlTESmr/33v10b6CgRhyeGI6cR8oSEboNMUfGMZ5eDYaHIWLoVi8CspcYea6rv7r+f/ nnskfn4S0DAGc9uS3Y7iCQf+0aXpX8pX42z4GxEeN5uzvjLdMZlIFzpZxujXfKm8LRxF bkwtog8xP7iCK51sRTuPerP1Rv5yPRNP4n3MoZ2YlMcnO1RXJsCCFe+57df2mVNMR/j9 5r2wQlpz41/zvDEGIFSMruH3NXKioQKRHVycp5ukBZWM85BvSpjrIlT4zwAN1N1379jT WHzA== X-Forwarded-Encrypted: i=1; AKwUvBxVVCkZRPEuv6TbKKh+nchj7B0atQX7ibq9NT5thhkX51+e58yti2cY4scSB9V5lqT2s6JTLQhCTQGdQKM=@vger.kernel.org X-Gm-Message-State: AFuF++nWlzpofq7wOt0BQ7xtK61qms3TAkc1H2xGMiJQbpAuI2YlGZsH SeA7x1LPo0lrskwuvUdOfW9PVKgtAIbOjR6nMa0+HpzUFlYPAJIUSZ52 X-Gm-Gg: AYBFou0pnsTbpX3iVWgw4hZ8T4+w3BIW5a0y3hRMV/7oR2TDwKuQ8m86rCF4OLW0FxP 7edEACeVzB3AXYQ9kTFwOzLUGJ+/s7IKEIo/MqJKedxolDG+GvZoXlaxsYIdiTYuIAA/b3ZfSn/ d7lfMtg4KMKjQ8p2TYU1Y5ezv1wEqPR1S18xAMZOI7A5iBvewKOaLxiaH/3S4KcpjY3pYmtGmPS KSCHVdp/vd8nek/h/t2T3zpOq9jDrF8l6FiqmFL3Xu2ND+YIMCBGwcD9bRHxQ5d0gpoRwHv06yW rVcQDtHv3JBwLjlRQM88UbqpNNbo26TSrYWyn2oO6AA7mwD8eVLXMqxhkIfyTGsYWSFHdEyY6mb yHLInyz/Pq/bBEdl8yIayeiTN+bysOobTSWDORGEV3tkivo3fCpDHQ+7BzpjsYgbY9xg+Yck1y6 ejMVT9ne8rNwBNqZ7AzkmPUvYIJuRC/aY2ldzak6fZ9LkFX5KuhYkdoUJdB2/YnG+Y6d/XXFrqz jrcyp9ZR6Uas+PCTQdoDlL50IjIspDwn7waPINjW/tIi+45qLKvuZ60CDSj7gtBS+6OpzS5rITR 3ONlRo/a X-Received: by 2002:a17:902:8496:b0:2dd:c053:a6f6 with SMTP id d9443c01a7336-2ddc053a766mr7512155ad.40.1789755934291; Fri, 18 Sep 2026 11:25:34 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:25:33 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview Date: Sat, 19 Sep 2026 03:24:40 +0900 Message-ID: <9fdbc924bf3387a4d33d1258ea21ac33abef65bb.1789753020.git.kimjinseob88@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Document the supported OSF receiver profile, runtime sensor discovery, IIO channel units, scale validation and session lifetime. Link the project-maintained fixed wire specification instead of duplicating its layouts and compatibility policy. Explain host IIO timestamps and the explicit rebind needed after a changed inventory. Assisted-by: LLM Signed-off-by: Jinseob Kim --- Documentation/iio/index.rst | 1 + Documentation/iio/open-sensor-fusion.rst | 77 ++++++++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 79 insertions(+) create mode 100644 Documentation/iio/open-sensor-fusion.rst diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index b02b879b053a..c2b7963348fd 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -40,4 +40,5 @@ Industrial I/O Kernel Drivers adxl345 bno055 ep93xx_adc + open-sensor-fusion opt4060 diff --git a/Documentation/iio/open-sensor-fusion.rst b/Documentation/iio/o= pen-sensor-fusion.rst new file mode 100644 index 000000000000..03ab36aca5f8 --- /dev/null +++ b/Documentation/iio/open-sensor-fusion.rst @@ -0,0 +1,77 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Open Sensor Fusion +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO dr= iver +receives device-to-host frames over UART and discovers sensor channels from +capability reports. Device Tree describes the hub using the +``opensensorfusion,osf`` compatible; individual sensors are discovered at +runtime. See the binding in +``Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml``. + +The OSF Device-to-Host 0.0 specification is maintained by the Open Sensor = Fusion +project. The canonical specification_ defines the wire layout, compatibili= ty +rules, reserved fields and physical units. This document describes the Lin= ux +receiver profile and its mapping to IIO. + +.. _specification: https://github.com/opensensorfusion/opensensorfusion-pr= otocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/spec/osf-d2h-0.0.md + +Supported receive profile +------------------------- + +The driver supports the fixed OSF0 frame layout with protocol major versio= n 0. +Compatible minor versions use the same known message layouts. The decoder +requires exact lengths for known messages, validates CRC and bounds before +interpreting payloads, and tolerates reserved padding. Unsupported majors = and +unknown message types are ignored. Unsupported capability entries are skip= ped +individually. A CRC-valid application rejection consumes the complete fram= e; +an invalid frame candidate is resynchronized without trusting its payload = length. + +The receive path handles sensor samples, complete capability reports and +device status. Its limits are 4096 bytes per frame and 16 supported sensor +descriptors. These are implementation limits, not protocol-wide maxima. + +IIO mapping +----------- + +The supported sensor classes map to: + +* accelerometer: ``IIO_ACCEL`` X/Y/Z, in m/s^2; +* gyroscope: ``IIO_ANGL_VEL`` X/Y/Z, in rad/s; +* magnetometer: ``IIO_MAGN`` X/Y/Z, in gauss; +* temperature: ``IIO_TEMP``, in millidegrees Celsius. + +Each registered channel exposes signed ``RAW`` data and descriptor-derived +``SCALE``. Multiplying RAW by SCALE gives a value in the channel's IIO uni= t. +Buffered samples use signed 32-bit values with native CPU endianness. Acti= ve +channels are packed into the scan, with initialized padding before an opti= onal +64-bit timestamp. + +Discovery and session lifetime +------------------------------ + +The driver validates all supported descriptors before registering IIO devi= ces. +An empty or entirely unsupported initial report leaves discovery open for a +later supported report. Supported descriptors must advertise a nonzero sca= le. +A sample's channel count, format and scale must match its registered descr= iptor +before it can update the latest RAW cache or be published to a buffer. A +rejected sample preserves previously accepted data. + +Repeated reports are compared by sensor type and index, independently of e= ntry +order. Reserved padding, advisory flags and unsupported additions do not c= hange +the supported inventory. A supported key appearing or disappearing, or a +change in its channel count, format or scale, faults the bound session. + +After a session fault, new cache updates and buffer publication stop, and +direct RAW reads fail. Existing IIO devices remain until driver teardown. +An equivalent report does not clear the fault; explicit unbind/rebind star= ts +fresh discovery. No fault-specific userspace ABI is exposed. + +Timestamps +---------- + +Buffered samples use the selected IIO host clock when they are pushed into +the buffer. The device timestamp is not correlated with that clock and is = not +used as the IIO buffer timestamp. Device timestamp or sequence discontinui= ties +alone do not start a new host session. diff --git a/MAINTAINERS b/MAINTAINERS index 9463b8111d52..26b9b2862724 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20515,6 +20515,7 @@ OPEN SENSOR FUSION M: Jinseob Kim S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml +F: Documentation/iio/open-sensor-fusion.rst K: opensensorfusion =20 OPENCOMPUTE PTP CLOCK DRIVER --=20 2.43.0 From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f42.google.com (mail-pj2-f42.google.com [74.125.227.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DB325524B06 for ; Fri, 18 Sep 2026 18:25:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755941; cv=none; b=dXkSuowGqf0gh9Oe6WEtl5RloHL5AL8K8IGi2aSnDWWmw8tieJ8mjnbQXRlsNZrOprVheLqgfOoDJaOIG/JODUAEDaAnSYRDKkHD07rDBFhybkrR9pUmG/dNE9nrW+Fd7730CLZr97owNXqFMD9rcaMu1R9Tpvc2v+o7mR8r5xU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755941; c=relaxed/simple; bh=lFG/OXnzFUC57DA5/v8yjvyQ/fznoUiq0nIpvGhk8iE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mcgBwiUNjmIOaDTWBnGy+pAYc2oqKK0QixqjIdW+3CppeOv8/2eT+otjh9S4se7QS42EDUYiRWDsc4AHrvfKdagJcgEROV77qypuN6vgXES3oAYB8yw2c/XP7UlxWNDVAhN2zqiy5MJegDSz2TqZLEnaornl5cTlhTwWDAPHWY0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=QhRT1JYc; arc=none smtp.client-ip=74.125.227.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="QhRT1JYc" Received: by mail-pj2-f42.google.com with SMTP id d9443c01a7336-2ddb44ad1c1so11307595ad.3 for ; Fri, 18 Sep 2026 11:25:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755939; x=1790360739; 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=PemEyMvIlx9kZWRfwL95Q7dPAbGIySrWEWl9bpGslDg=; b=QhRT1JYcnRrxJ10ulWIL3wGzIwR6CzOHkdTsgwIbmbaqbsx2kV/G5Osd5FugM8/Hp7 mg3DgvboI4xBQ6t7PxT+0czC2Ig0lLAA1IoZJsa/E/8EqtyvnPvFLpr5WjbwprnUJGl5 CUeU/u8LpXtYfO1+y/c1LaF0LXEywOw+CVaiTUPH94W8/z0kiONIPB62F1rVYwfmVSu+ OX2slVa+ttCajVVvzQRPOML6u7gPHyY29T4InyANBsByWnPVCuvgpl106JDdDXOztXlv x6Nv8IXMCCqdMXV1C+Hv0HcSb1oM/o7rXY6OTMtkHpvqVilHUj21WhZi/Asuj8CEnfcj KU9A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755939; x=1790360739; 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=PemEyMvIlx9kZWRfwL95Q7dPAbGIySrWEWl9bpGslDg=; b=L4sNFQwilZYQ2HQ+5grsM1VUzbbcMaJhzGNpeooVy2rddHsTw6m96oW+NYCRnymPRT BVfAhbBPwqs4C46i5yx8gZNOgSJTtR2Dk+WuvlYbwpLN4PnRaiaQSCaKLySSqXm342n4 Mv3lNAZcf7B85y7dZkxhHaiG77xTfZdMhscUv8GQJ3iKydW95+ZbB/r5XtmWLgSIWxnJ cXUJFCI5klR0+VFj35GUUjw/Pq0bbw/M7FodO+EFm8vOZH5kJ2QgXiXWDvRXyI50gK4s 6Mil8srmBaWKUp0rCjU6QSNVBszRP/8mt3n09yvgYRTNs5k26gK6xsYZx4WOSNNd6qD7 i2wA== X-Forwarded-Encrypted: i=1; AKwUvBw9xHMKcdbrwSgc1Y+e0xMij9rauuPpSreTs8nFqMVnxRyyRETPKbE0wnPJR2vI8eJp2JKvkFqZBxFAfpw=@vger.kernel.org X-Gm-Message-State: AFuF++mOCS3lZYSwIhsmmsLW0wlJvOh662Nf0oX9Ebe8jp45qmSJ/xRK 28H/GUeRDD8bniUWxk6PDe8y49W2WN1ojsz+8XmF7CELxXLs6OQEMz4v X-Gm-Gg: AYBFou2HvUzw7K0iKue3c6cawkzxBoV4TnG8hOAMn9Yi+4RIz/GqoXvjjAYrFjmxTbh 3X64iDAfFESl5puirk61potX9MO0om2pB+f9bpwc1QiDzCSRNaD+UqRL4IRksAJkxXCzqpNg6GF 7lvAe7EpoP6RCGelCEp560K51R1CvlqSyb7PU5VtGzhaqDwz8gk0DGDHOYCC1WlNOXOY9D1n6O5 UwA6ESi9z9PL42m4YeIwxW5F4AkYOGJHknIHJ8z7Pdq2iV8kL62UJ4tjZHDQE44FeBDk5JdRyW6 vuCghTu31TLCQfi5CAJsggSyZ3ItJEL7C/xKswW6V9s6i+nCS7AXOmO8m3XXwqjKaHwQytrdg4Q tehroA272J/oCY5nAdE4AFBO4uWja0sz6JM0YHGpaU14A2D1+4lE+P7hCU5WsmgGMCcGUNEj09w wP1bOCcBVkPBRwr73I2Diic+qxMVvX9iDmshmAstJGF03aldiUrjEk3p7MSHXkzM7oECWGsOaej cv6r7y3OXeIj9syX7glIiCAWpYgPSDA69+Zmqq93M9Yz1XzD9pWxPI906gPe6Pdk3lPkxZ7LjRD DqMri4k1 X-Received: by 2002:a17:902:b48b:b0:2dd:c0ff:e735 with SMTP id d9443c01a7336-2ddc0ffea0amr4123635ad.71.1789755938827; Fri, 18 Sep 2026 11:25:38 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:25:38 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 3/8] iio: osf: add protocol decoding Date: Sat, 19 Sep 2026 03:24:41 +0900 Message-ID: X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Add helpers for decoding Open Sensor Fusion frame headers and supported message payloads. Validate the fixed OSF0 envelope, payload bounds and CRC before exposing decoded frame contents. Require exact known payload lengths and decode capability entries structurally so the core can apply support policy. Tolerate reserved padding as required by the fixed protocol contract. Use explicit little-endian wire storage sizes and designated initializers for decoded output structures. Assisted-by: LLM Signed-off-by: Jinseob Kim --- MAINTAINERS | 1 + drivers/iio/opensensorfusion/osf_protocol.c | 215 ++++++++++++++++++++ drivers/iio/opensensorfusion/osf_protocol.h | 101 +++++++++ 3 files changed, 317 insertions(+) create mode 100644 drivers/iio/opensensorfusion/osf_protocol.c create mode 100644 drivers/iio/opensensorfusion/osf_protocol.h diff --git a/MAINTAINERS b/MAINTAINERS index 26b9b2862724..bf56918efc00 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20516,6 +20516,7 @@ M: Jinseob Kim S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml F: Documentation/iio/open-sensor-fusion.rst +F: drivers/iio/opensensorfusion/osf_protocol.* K: opensensorfusion =20 OPENCOMPUTE PTP CLOCK DRIVER diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/open= sensorfusion/osf_protocol.c new file mode 100644 index 000000000000..e0d7c7a9ebd7 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_protocol.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "osf_protocol.h" + +#define OSF_CRC32_INIT GENMASK(31, 0) +#define OSF_CRC32_XOROUT GENMASK(31, 0) + +static bool osf_sensor_type_valid(u16 sensor_type) +{ + return sensor_type >=3D OSF_SENSOR_ACCELEROMETER && + sensor_type <=3D OSF_SENSOR_PROXIMITY; +} + +static u32 osf_crc32_ieee(const u8 *buf, size_t len) +{ + return crc32_le(OSF_CRC32_INIT, buf, len) ^ OSF_CRC32_XOROUT; +} + +int osf_protocol_decode_frame(const u8 *buf, size_t len, + struct osf_frame *frame, size_t *frame_len) +{ + u32 expected_crc; + u32 actual_crc; + u32 payload_len; + size_t total_len; + + if (!buf || !frame || !frame_len) + return -EINVAL; + + if (len < OSF_FRAME_MIN_LEN) + return -EMSGSIZE; + + if (get_unaligned_le32(buf) !=3D OSF_FRAME_MAGIC) + return -EPROTO; + + if (get_unaligned_le16(buf + 6) !=3D OSF_FRAME_HEADER_LEN) + return -EPROTO; + + payload_len =3D get_unaligned_le32(buf + 10); + if (payload_len > len - OSF_FRAME_MIN_LEN) + return -EMSGSIZE; + + total_len =3D OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN; + expected_crc =3D osf_crc32_ieee(buf, OSF_FRAME_HEADER_LEN + payload_len); + actual_crc =3D get_unaligned_le32(buf + OSF_FRAME_HEADER_LEN + payload_le= n); + + if (actual_crc !=3D expected_crc) + return -EBADMSG; + + frame->protocol_major =3D buf[4]; + frame->protocol_minor =3D buf[5]; + frame->message_type =3D get_unaligned_le16(buf + 8); + frame->payload_len =3D payload_len; + frame->sequence =3D get_unaligned_le64(buf + 14); + frame->timestamp_us =3D get_unaligned_le64(buf + 22); + frame->flags =3D get_unaligned_le32(buf + 30); + frame->reserved =3D get_unaligned_le32(buf + 34); + frame->payload =3D buf + OSF_FRAME_HEADER_LEN; + frame->crc =3D actual_crc; + *frame_len =3D total_len; + + return 0; +} + +int osf_protocol_decode_sensor_sample(const struct osf_frame *frame, + struct osf_sensor_sample *sample) +{ + u16 channel_count; + u16 sample_format; + u16 sensor_type; + size_t expected_len; + const u8 *payload; + + if (!frame || !sample || !frame->payload) + return -EINVAL; + + if (frame->message_type !=3D OSF_MSG_SENSOR_SAMPLE) + return -EPROTO; + + if (frame->payload_len < OSF_SENSOR_SAMPLE_BASE_LEN) + return -EMSGSIZE; + + payload =3D frame->payload; + sensor_type =3D get_unaligned_le16(payload); + channel_count =3D get_unaligned_le16(payload + 4); + sample_format =3D get_unaligned_le16(payload + 6); + + if (!osf_sensor_type_valid(sensor_type)) + return -EPROTO; + + if (!channel_count) + return -EPROTO; + + if (sample_format !=3D OSF_SAMPLE_FORMAT_S32) + return -EPROTO; + + expected_len =3D OSF_SENSOR_SAMPLE_BASE_LEN + channel_count * sizeof(__le= 32); + if (frame->payload_len !=3D expected_len) + return -EMSGSIZE; + + *sample =3D (struct osf_sensor_sample) { + .sensor_type =3D sensor_type, + .sensor_index =3D get_unaligned_le16(payload + 2), + .channel_count =3D channel_count, + .sample_format =3D sample_format, + .scale_nano =3D get_unaligned_le32(payload + 8), + .samples =3D payload + OSF_SENSOR_SAMPLE_BASE_LEN, + }; + + return 0; +} + +int osf_protocol_sensor_sample_value(const struct osf_sensor_sample *sampl= e, + u16 index, s32 *value) +{ + if (!sample || !sample->samples || !value) + return -EINVAL; + + if (index >=3D sample->channel_count) + return -ERANGE; + + /* Samples are little-endian two's-complement signed values. */ + *value =3D get_unaligned_le32(sample->samples + index * sizeof(__le32)); + + return 0; +} + +int osf_protocol_decode_device_status(const struct osf_frame *frame, + struct osf_device_status *status) +{ + const u8 *payload; + + if (!frame || !status || !frame->payload) + return -EINVAL; + + if (frame->message_type !=3D OSF_MSG_DEVICE_STATUS) + return -EPROTO; + + if (frame->payload_len !=3D OSF_DEVICE_STATUS_LEN) + return -EMSGSIZE; + + payload =3D frame->payload; + *status =3D (struct osf_device_status) { + .uptime_s =3D get_unaligned_le32(payload), + .status_flags =3D get_unaligned_le32(payload + 4), + .error_flags =3D get_unaligned_le32(payload + 8), + .dropped_frames =3D get_unaligned_le32(payload + 12), + }; + + return 0; +} + +int osf_protocol_decode_capability_report(const struct osf_frame *frame, + struct osf_capability_report *report) +{ + u16 capability_count; + size_t expected_len; + const u8 *payload; + + if (!frame || !report || !frame->payload) + return -EINVAL; + + if (frame->message_type !=3D OSF_MSG_CAPABILITY_REPORT) + return -EPROTO; + + if (frame->payload_len < OSF_CAP_REPORT_BASE_LEN) + return -EMSGSIZE; + + payload =3D frame->payload; + capability_count =3D get_unaligned_le16(payload); + + expected_len =3D OSF_CAP_REPORT_BASE_LEN + + capability_count * OSF_CAP_SENSOR_ENTRY_LEN; + if (frame->payload_len !=3D expected_len) + return -EMSGSIZE; + + *report =3D (struct osf_capability_report) { + .capability_count =3D capability_count, + .entries =3D payload + OSF_CAP_REPORT_BASE_LEN, + }; + + return 0; +} + +int osf_protocol_decode_capability_entry(const struct osf_capability_report + *report, u16 index, + struct osf_capability_entry *entry) +{ + const u8 *payload; + + if (!report || !report->entries || !entry) + return -EINVAL; + + if (index >=3D report->capability_count) + return -ERANGE; + + payload =3D report->entries + index * OSF_CAP_SENSOR_ENTRY_LEN; + *entry =3D (struct osf_capability_entry) { + .sensor_type =3D get_unaligned_le16(payload), + .sensor_index =3D get_unaligned_le16(payload + 2), + .channel_count =3D get_unaligned_le16(payload + 4), + .sample_format =3D get_unaligned_le16(payload + 6), + .scale_nano =3D get_unaligned_le32(payload + 8), + .flags =3D get_unaligned_le32(payload + 12), + .reserved =3D get_unaligned_le32(payload + 16), + }; + + return 0; +} diff --git a/drivers/iio/opensensorfusion/osf_protocol.h b/drivers/iio/open= sensorfusion/osf_protocol.h new file mode 100644 index 000000000000..2b616f2d5197 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_protocol.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_PROTOCOL_H +#define _OSF_PROTOCOL_H + +#include +#include + +#define OSF_PROTOCOL_MAJOR 0 +#define OSF_PROTOCOL_MINOR 0 +#define OSF_FRAME_HEADER_LEN 38 +#define OSF_FRAME_CRC_LEN 4 +#define OSF_FRAME_MIN_LEN (OSF_FRAME_HEADER_LEN + OSF_FRAME_CRC_LEN) +#define OSF_FRAME_MAGIC 0x3046534f /* "OSF0", little-endian */ + +#define OSF_SENSOR_SAMPLE_BASE_LEN 16 +#define OSF_DEVICE_STATUS_LEN 20 +#define OSF_CAP_REPORT_BASE_LEN 4 +#define OSF_CAP_SENSOR_ENTRY_LEN 20 +#define OSF_CAPABILITY_FLAGS_MASK GENMASK(1, 0) + +enum osf_message_type { + OSF_MSG_SENSOR_SAMPLE =3D 0x0001, + OSF_MSG_DEVICE_STATUS =3D 0x0002, + OSF_MSG_CAPABILITY_REPORT =3D 0x0003, +}; + +enum osf_sensor_type { + OSF_SENSOR_ACCELEROMETER =3D 0x0001, + OSF_SENSOR_GYROSCOPE =3D 0x0002, + OSF_SENSOR_MAGNETOMETER =3D 0x0003, + OSF_SENSOR_BAROMETER =3D 0x0004, + OSF_SENSOR_TEMPERATURE =3D 0x0005, + OSF_SENSOR_HUMIDITY =3D 0x0006, + OSF_SENSOR_AMBIENT_LIGHT =3D 0x0007, + OSF_SENSOR_PROXIMITY =3D 0x0008, +}; + +enum osf_sample_format { + OSF_SAMPLE_FORMAT_S32 =3D 0x0001, +}; + +struct osf_frame { + u8 protocol_major; + u8 protocol_minor; + u16 message_type; + u32 payload_len; + u64 sequence; + u64 timestamp_us; + u32 flags; + u32 reserved; + /* payload points into the caller-owned frame buffer. */ + const u8 *payload; + u32 crc; +}; + +struct osf_sensor_sample { + u16 sensor_type; + u16 sensor_index; + u16 channel_count; + u16 sample_format; + u32 scale_nano; + const u8 *samples; +}; + +struct osf_device_status { + u32 uptime_s; + u32 status_flags; + u32 error_flags; + u32 dropped_frames; +}; + +struct osf_capability_report { + u16 capability_count; + const u8 *entries; +}; + +struct osf_capability_entry { + u16 sensor_type; + u16 sensor_index; + u16 channel_count; + u16 sample_format; + u32 scale_nano; + u32 flags; + u32 reserved; +}; + +int osf_protocol_decode_frame(const u8 *buf, size_t len, + struct osf_frame *frame, size_t *frame_len); +int osf_protocol_decode_sensor_sample(const struct osf_frame *frame, + struct osf_sensor_sample *sample); +int osf_protocol_decode_device_status(const struct osf_frame *frame, + struct osf_device_status *status); +int osf_protocol_decode_capability_report(const struct osf_frame *frame, + struct osf_capability_report *report); +int osf_protocol_decode_capability_entry(const struct osf_capability_report + *report, u16 index, + struct osf_capability_entry *entry); +int osf_protocol_sensor_sample_value(const struct osf_sensor_sample *sampl= e, + u16 index, s32 *value); + +#endif --=20 2.43.0 From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 09719526A9F for ; Fri, 18 Sep 2026 18:25:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755946; cv=none; b=vD3sJOdJcM/oQHkYYhhMN0Kui0Ipsd2IWE5uCyCpYFYGiDhU0wQ3UsEtVjQisrcF1ZsTO9JbJNSyGtvoKNjftbA7xjO2Q/JgC+VgVHCCxP0K28k3DuvZnoaiXuv8QoWx+N7igoCOKNNms2Fq6A4vrbaIEXJv2D5IQabtWYfJKdk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755946; c=relaxed/simple; bh=MjMcqz6xoV0VSqRppIZ2oRmAw1qJ2BDvFPFFe5vuq00=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZJxsasKyPdrcxuyILETCFHh1pJ4J0G5Ifb/gnqClcT2Ov+BhHIi5aqx+IuDyyNWvRMmFIPieAOKKXUYlgdWeZ3/Fy92zfbZl0Uy20t6LnB46r33zZNgHmk3SxF91SpuXG6eFc5clomLbKEqmxyANia6AoWZ6V9r3HCaXG4KyONg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OIX551gT; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OIX551gT" Received: by mail-pj2-f13.google.com with SMTP id d9443c01a7336-2dd88a115ebso9654175ad.2 for ; Fri, 18 Sep 2026 11:25:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755943; x=1790360743; 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=FW5+Vpv4nV1ZrPbbHCVgHohBuMbLnRMFYjUMGwx4I18=; b=OIX551gTwrPFFYz8JuScT1o+oFgUoUYuf6tTdArmXpicvRVvxrOcU5Fr7ce2Spjh2s xWQLOFV2EQn4pDkNEbBujCM7yHnM6VFDNS2PsN9SjopD/lEl/t9FFZ+O3DUjlz4s7Miv TNxlIRytXtFDt8mrkDAIsJzCwXQ5yRm8Sgbi6WAXfmCmQKPdYMeTSip49+dTl20se+8X PTDKE7/ObExqui7x5VCU2wToQWoAUe/s4yZA7bqsm6hekWo9Ny1OsaV9MHd3m26+1aC6 KoRQI3WMAAhKxBxP1lst9Z+8nVNABgNoSXRwWjNzz4a9hHaJaIDrVtcYlE6IgU45QWuQ DVew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755943; x=1790360743; 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=FW5+Vpv4nV1ZrPbbHCVgHohBuMbLnRMFYjUMGwx4I18=; b=HnaZVOtP6gFnc/E8xs7lE4dhJQ60pKxx6fQcuJOoM9tmXmgfY0IBMlvpPKTx77XdGZ 7bQwdXbTG2EhfAWUkL3Iokf8QiIjUejFHEbjUzEclxinXHlznsgZLHbtKFtMijD1G6Xt qgYj5vXDzSnF9HX9SjD6ehE45dOkrfFqD4kbhSK+8utb69DNeatxEXnPFO7gRp8wbUXj LFpwM6Qg/GXyngctLWYl0DoWorEZnNbtOxMt1jmpCn1c6Y2uCqosDUP69ZSKhkIAJDSw F0wvrtsLfMuSBWYnCmQtObzoBMc47YIBFN7v8I4A1Z7UfZAxuGCk+2RQdTHGqnuP3SwH O1iQ== X-Forwarded-Encrypted: i=1; AKwUvBxYYfM2J4PE8oLWQDo/UFpqpZbAHeheS67Lu0m8HJYbFl6PLWeZYTT7IWhBsWK+/umMvUuOvSrZBSbVlkE=@vger.kernel.org X-Gm-Message-State: AFuF++nmW0sy6agMs5f9h3rJDCNDOoJgIm9qMKNwpBvn2V6cxukvvD0P JibyfcCk362W6b5mWfkl66ybpYU2EJPj7PO+QKhqQyv6AiPBtrIvlWa+ X-Gm-Gg: AYBFou3LSp3d0Fb7OtlBBvaeztPs3uoEYO6B0NTyeh1uj59wvVP2FRFjGm8Z4YAQ+zf yo+lzDBlgMAfoNKiptdJJCeV47WgQxVhxpBjPU1eApO4pj4Fvo6yknckPSUlrwFZxxfUTJ2Ctlt dwExuWALxFTj03wNxf/9yO46V8X2yK4lN5u87C0wZp8DdNhIVAnm5ESmr9kGlHXa/QsqseIHw9s mNAFm0ovPYZCTNB6lJtF2YccTxHV7g7GBHTdYu0PJFZDxLBvGZ1sNG35kws838W0hfvcYux8CB0 PPXx7FJ8rUw4kGR+HpgmjydDeVHEyRCHzJEwuH1Zt/dgvsjNWCSq5E2W7JnTrzQ4f38mShBPbZb MoB6AfsI3K/8tPTtau5llQVoeD4aphw/cxbAS/bJF5yCKV7EqXUxsJ6drxewbNKWA+qTyUvDwVt ve4Erl3l5z/cH2z65mMrCWSADNFgL5jcIXQyiin+p43EPgog9u7tqBef6XeXW8+NIbK480OB4+Y nLVP2R3tbr+1mwYlPkJIDoSokyJx0NONHllj560sKgtf+n6vUSTvfhVJUIVz74f98xYRVlHJihO tcU+SfXx X-Received: by 2002:a17:902:f705:b0:2dd:ad74:ac34 with SMTP id d9443c01a7336-2ddb1bbeb4bmr69721235ad.29.1789755943296; Fri, 18 Sep 2026 11:25:43 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:25:42 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 4/8] iio: osf: add validated stream parser Date: Sat, 19 Sep 2026 03:24:42 +0900 Message-ID: X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Add a UART byte-stream parser for Open Sensor Fusion frames. The parser searches for the OSF0 wire magic, keeps partial frames buffered, checks header length and payload bounds, and passes complete candidate frames to a registered frame callback. Candidates rejected before validation drop only the current head byte before resynchronizing, so a corrupted unvalidated payload length cannot make the parser skip later valid frames. CRC-valid validated frames are consumed in full and classified as handled, ignored, or rejected. Use a direct callback member with an opaque context and keep explicit statistics for validated outcomes and framing failures. Assisted-by: LLM Signed-off-by: Jinseob Kim --- MAINTAINERS | 1 + drivers/iio/opensensorfusion/osf_stream.c | 231 ++++++++++++++++++++++ drivers/iio/opensensorfusion/osf_stream.h | 53 +++++ 3 files changed, 285 insertions(+) create mode 100644 drivers/iio/opensensorfusion/osf_stream.c create mode 100644 drivers/iio/opensensorfusion/osf_stream.h diff --git a/MAINTAINERS b/MAINTAINERS index bf56918efc00..bad05db854cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20517,6 +20517,7 @@ S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml F: Documentation/iio/open-sensor-fusion.rst F: drivers/iio/opensensorfusion/osf_protocol.* +F: drivers/iio/opensensorfusion/osf_stream.* K: opensensorfusion =20 OPENCOMPUTE PTP CLOCK DRIVER diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opense= nsorfusion/osf_stream.c new file mode 100644 index 000000000000..e262415e69b7 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_stream.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "osf_protocol.h" +#include "osf_stream.h" + +#define OSF_STREAM_MAGIC_LEN sizeof(__le32) +#define OSF_STREAM_MAX_PAYLOAD_LEN \ + (OSF_STREAM_MAX_FRAME_LEN - OSF_FRAME_HEADER_LEN - OSF_FRAME_CRC_LEN) + +static void osf_stream_discard(struct osf_stream *stream, size_t count) +{ + if (count >=3D stream->len) { + stream->len =3D 0; + return; + } + + memmove(stream->buf, stream->buf + count, stream->len - count); + stream->len -=3D count; +} + +static void osf_stream_drop_invalid_head(struct osf_stream *stream) +{ + osf_stream_discard(stream, 1); +} + +static bool osf_stream_magic_prefix_match(const u8 *buf, size_t len) +{ + for (size_t i =3D 0; i < len; i++) { + if (buf[i] !=3D (u8)(OSF_FRAME_MAGIC >> (i * 8))) + return false; + } + + return true; +} + +static size_t osf_stream_discard_to_magic(struct osf_stream *stream) +{ + size_t old_len =3D stream->len; + size_t keep_len; + + for (size_t i =3D 0; i + OSF_STREAM_MAGIC_LEN <=3D stream->len; i++) { + if (get_unaligned_le32(stream->buf + i) =3D=3D OSF_FRAME_MAGIC) { + if (i) + osf_stream_discard(stream, i); + return i; + } + } + + /* + * Keep a final 1-3 byte OSF_FRAME_MAGIC prefix so a magic split + * across receive_buf() calls can be completed by the next chunk. + */ + keep_len =3D min(stream->len, OSF_STREAM_MAGIC_LEN - 1); + while (keep_len) { + size_t offset =3D stream->len - keep_len; + + if (osf_stream_magic_prefix_match(stream->buf + offset, keep_len)) { + if (offset) + osf_stream_discard(stream, offset); + return offset; + } + keep_len--; + } + + stream->len =3D 0; + return old_len; +} + +static int osf_stream_process(struct osf_stream *stream) +{ + size_t discarded; + size_t frame_len; + u32 payload_len; + int frame_result; + int first_err =3D 0; + + while (stream->len) { + discarded =3D osf_stream_discard_to_magic(stream); + if (discarded) { + stream->stats.bad_magic_resyncs++; + stream->stats.dropped_bytes +=3D discarded; + if (!first_err) + first_err =3D -EPROTO; + } + + if (!stream->len) + break; + + if (stream->len < OSF_FRAME_HEADER_LEN) + break; + + if (get_unaligned_le16(stream->buf + 6) !=3D OSF_FRAME_HEADER_LEN) { + stream->stats.dropped_bytes++; + osf_stream_drop_invalid_head(stream); + if (!first_err) + first_err =3D -EPROTO; + continue; + } + + payload_len =3D get_unaligned_le32(stream->buf + 10); + if (payload_len > OSF_STREAM_MAX_PAYLOAD_LEN) { + stream->stats.dropped_bytes++; + osf_stream_drop_invalid_head(stream); + if (!first_err) + first_err =3D -EMSGSIZE; + continue; + } + + frame_len =3D OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN; + if (stream->len < frame_len) + break; + + frame_result =3D stream->receive_frame(stream->frame_context, + stream->buf, frame_len); + if (frame_result < 0) { + if (frame_result =3D=3D -EBADMSG) + stream->stats.bad_crc_frames++; + + /* + * Decoding failed before the frame was validated; + * payload_len is still untrusted. Drop only the current + * head and resynchronize. + */ + stream->stats.dropped_bytes++; + osf_stream_drop_invalid_head(stream); + if (!first_err) + first_err =3D frame_result; + continue; + } + + /* Count exactly one outcome for every validated frame. */ + stream->stats.validated_frames++; + switch (frame_result) { + case OSF_STREAM_FRAME_HANDLED: + stream->stats.handled_frames++; + break; + case OSF_STREAM_FRAME_IGNORED: + stream->stats.ignored_frames++; + break; + case OSF_STREAM_FRAME_REJECTED: + stream->stats.rejected_frames++; + break; + default: + /* + * Preserve the validated boundary without scanning the + * payload for another magic value. + */ + stream->stats.rejected_frames++; + if (!first_err) + first_err =3D -EPROTO; + break; + } + osf_stream_discard(stream, frame_len); + } + + return first_err; +} + +void osf_stream_init(struct osf_stream *stream, + int (*receive_frame)(void *context, const u8 *buf, + size_t len), + void *frame_context) +{ + if (!stream) + return; + + stream->receive_frame =3D receive_frame; + stream->frame_context =3D frame_context; + stream->len =3D 0; + memset(&stream->stats, 0, sizeof(stream->stats)); +} + +void osf_stream_reset(struct osf_stream *stream) +{ + if (!stream) + return; + + stream->len =3D 0; + memset(&stream->stats, 0, sizeof(stream->stats)); +} + +int osf_stream_receive_bytes(struct osf_stream *stream, + const u8 *buf, size_t len) +{ + size_t copy_len; + size_t space; + int first_err =3D 0; + int ret; + + if (!stream || !stream->receive_frame || (!buf && len)) + return -EINVAL; + + if (!len) + return osf_stream_process(stream); + + /* + * Continue processing this receive_buf() chunk after recoverable + * framing errors so later valid frames do not wait for another callback. + * first_err retains the first diagnostic return, while the serdev + * callback reports the full byte count consumed. Every validated + * callback result is consumed in full by osf_stream_process(). + */ + while (len) { + space =3D OSF_STREAM_MAX_FRAME_LEN - stream->len; + if (!space) { + stream->stats.dropped_bytes++; + osf_stream_discard(stream, 1); + if (!first_err) + first_err =3D -EMSGSIZE; + continue; + } + + copy_len =3D min(len, space); + memcpy(stream->buf + stream->len, buf, copy_len); + stream->len +=3D copy_len; + buf +=3D copy_len; + len -=3D copy_len; + + ret =3D osf_stream_process(stream); + if (ret && !first_err) + first_err =3D ret; + } + + return first_err; +} diff --git a/drivers/iio/opensensorfusion/osf_stream.h b/drivers/iio/opense= nsorfusion/osf_stream.h new file mode 100644 index 000000000000..9087533b2d04 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_stream.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_STREAM_H +#define _OSF_STREAM_H + +#include + +#define OSF_STREAM_MAX_FRAME_LEN 4096 + +/** + * enum osf_stream_frame_result - validated frame callback result + * @OSF_STREAM_FRAME_HANDLED: frame was processed successfully + * @OSF_STREAM_FRAME_IGNORED: frame was valid but unsupported or ignored + * @OSF_STREAM_FRAME_REJECTED: validated application processing failed + * + * A frame callback returns a negative errno only when a candidate could n= ot + * be validated and the parser may perform one-byte resynchronization. + * Every nonnegative result must be one of these values. Such a result mea= ns + * the CRC-valid frame boundary is trusted, so the parser must consume the + * full frame. + */ +enum osf_stream_frame_result { + OSF_STREAM_FRAME_HANDLED, + OSF_STREAM_FRAME_IGNORED, + OSF_STREAM_FRAME_REJECTED, +}; + +struct osf_stream_stats { + u64 validated_frames; + u64 handled_frames; + u64 ignored_frames; + u64 rejected_frames; + u64 bad_magic_resyncs; + u64 bad_crc_frames; + u64 dropped_bytes; +}; + +struct osf_stream { + int (*receive_frame)(void *context, const u8 *buf, size_t len); + void *frame_context; + u8 buf[OSF_STREAM_MAX_FRAME_LEN]; + size_t len; + struct osf_stream_stats stats; +}; + +void osf_stream_init(struct osf_stream *stream, + int (*receive_frame)(void *context, const u8 *buf, + size_t len), + void *frame_context); +void osf_stream_reset(struct osf_stream *stream); +int osf_stream_receive_bytes(struct osf_stream *stream, + const u8 *buf, size_t len); + +#endif --=20 2.43.0 From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A9CFE52758F for ; Fri, 18 Sep 2026 18:25:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755950; cv=none; b=VAMj5F8TDNetmXGkH6R4lIlJvPwMq0pGoNOK8JnhRCflTSwQjb/I62hS1ASPhLrhocoyihTXYt+MVQBqqFeUU4JIakDBrNLIELVFaeMA+ABbmAxr99UW2q7OXGdmNhu/dxo/0vrj0/e7VcJfO12kBTc1nvGYVwkl5QZOg6lORy0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755950; c=relaxed/simple; bh=UenmADVUAUDfeXZXbonm0hu1Jl7PjlTML8crQpyheZU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Gg2qhl6eFT2h6f/NWEHkRH1tTSyS3dv00pnWxnmPZ+Ank8SEq/GYy6GOf6ZI6VJ5/OJv8rmS6D9jqJ2cxC482YKJvMy8NHJYYv+mrimR2Nf+ABBlw6eT4rVlmQAk/n6AGGttuDIgzoq/7P30Wm9r2HdbWEwFAY4R4+riW410nDw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=K3ojjENb; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="K3ojjENb" Received: by mail-pj2-f13.google.com with SMTP id d9443c01a7336-2dd77300825so11322105ad.1 for ; Fri, 18 Sep 2026 11:25:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755948; x=1790360748; 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=3lkqG4yWnBqwjVGs0AZjcoYifhcHekb9XrWlB8YylUU=; b=K3ojjENbD4gUIm4Q42E4dtn5rdGIqDQzx6NO1ADXSkvtRFXYuYCag6ST+GGANlBOoG vTL8Zv6zQpGYiGLW4KuwMwdhYY1fc5c+5CALygBo+tI1JycU16+0jRZu19I0RWjH9ZQq jccajotsaKxo9pn9/v/uDgvNbeoKNBHtrbazqDhUINkUH6ecVk59yQbTif1v/FVBifsb GNaFTQgD5r9NILi0Dv+fpzkuhEsVZ36qZeTGmIi1pSJjyBQRZtYOr4Le50kk9cgB+HpM g9DkV6ZiUCvkL49ZAa/5PW+kDNYCRMy594I/ToPdnMmldswRrLJ69gRWeDkpIwdkfbLG gPLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755948; x=1790360748; 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=3lkqG4yWnBqwjVGs0AZjcoYifhcHekb9XrWlB8YylUU=; b=lPBfdXmcBfKi7vGxrVhU3PWjEKVkSVnY2CUxhr/UTiotAphkVPpio0DJWxL514LKqA aOb7lgSxuymDz8y+YIWGB232S9aBAw/76dNyk4XdlDM1vBE9HfffVUSm4uyBhVIRMlj7 8deUt6eBOZRTLz9DKxZvug/JAD0LAoLbp9UDLJV7pcmcfrpq01jATw3/6YnyQd3okDwq vAh6YayIwdXjZZPLmuWohzzUSA5gE/MujYoO0asn/AyFmZQ4oL3lGzzt/TtEnsqAT0wG QT/yQ7/DRSVQEwdpwjYQ00uvQ08Nk8xOw1fD2LvFdJcsPSbCoIO+KZfA/FPNYuX/YaRh Bi4Q== X-Forwarded-Encrypted: i=1; AKwUvBxXwTmYc9xsNdxEf7ETIzurDxwWBqm1ba8Em9A50l4AmagvKAjwAYGsWVDzvpFyrOe4r4QH2Mo9T7leSrs=@vger.kernel.org X-Gm-Message-State: AFuF++kKi2KXEhpMNQxdVx0BEHjkMTqVtnIhu5lDslSgulkW0LzzQkVN SVynyQVh/mqZSQNjx/k0hoy3pyHu8TQJXpLuR9hF+Hk4Xm4wk1H1qLXq X-Gm-Gg: AYBFou0TBvVZXiAAcg0U59CMvLSP/s5WTBX6ivygw39dvEPWnag/NtoL2oEioEMSTCo d4EWDcgblSo+pg4QnlPkIdou1sAu61+W929BFcc5umkM77S87Iua1EN3ckFE1U+2QtsCqdACq5J moRHmU8PkZh2P0e8MHmvlyIHCUe4R0RLfCke3lvtJgk3rVawV6EHy6SuwVBq5WSoMweYfAa76HY QCRHXNqbqivkwsqXwM0eUTvFcgiZlpc6jsJseYNgDvBQc6Iis8PtXytdWm8YUt76JGSA4RupWkq QeHM439sNBB7xd5WijUd0aO4pRW6oCChKpayFDwRxk9AxWuMqGWth6HcRxN/utD1hDu9hzfIWCU g1aBydsr2UMppN1EAjSW/gb+N+EjdFpU/QvZ8b4puAVVG5P8c8+7y7RzYsn0ForLDJaEo1z/wEk s9Rfv511sLAfBBleOiLE8NFsAS+KLPNdtiYccWL5zlpsu7cda9+VbEK1f2J1O4WzksYkPefVTQj 431ywN7/FVHinqC70/K0XMF44DocFEweba0m0XCThybosWlMeRqhJHq5ew8MKUYAeY18//XbJib +Q0ohMDd X-Received: by 2002:a17:903:b87:b0:2dd:ad73:c93c with SMTP id d9443c01a7336-2ddb1bed468mr73811075ad.29.1789755947743; Fri, 18 Sep 2026 11:25:47 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:25:47 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 5/8] iio: osf: add UART transport and core receive path Date: Sat, 19 Sep 2026 03:24:43 +0900 Message-ID: <5eaeefa79fc2ba956bf34f691c0dc65592e5c4c8.1789753020.git.kimjinseob88@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Connect the stream parser to a serdev receiver and add core frame dispatch with an owned device-status cache. Preserve the distinction between unvalidated candidates and validated application rejections, so malformed status payloads do not cause byte-wise resynchronization. Open and configure the UART before enabling the supply. A managed release action closes and drains the receive producer before resetting the parser and disabling a successfully enabled supply. This transport base processes device-status frames and ignores message types without an application handler. It does not register IIO sensor devices. The following patch adds capability discovery and sample publication to IIO, including the publication gate and IIO teardown. Assisted-by: LLM Signed-off-by: Jinseob Kim --- MAINTAINERS | 3 +- drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/opensensorfusion/Kconfig | 12 ++ drivers/iio/opensensorfusion/Makefile | 5 + drivers/iio/opensensorfusion/osf_core.c | 101 +++++++++++++++ drivers/iio/opensensorfusion/osf_core.h | 29 +++++ drivers/iio/opensensorfusion/osf_serdev.c | 143 ++++++++++++++++++++++ 8 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 drivers/iio/opensensorfusion/Kconfig create mode 100644 drivers/iio/opensensorfusion/Makefile create mode 100644 drivers/iio/opensensorfusion/osf_core.c create mode 100644 drivers/iio/opensensorfusion/osf_core.h create mode 100644 drivers/iio/opensensorfusion/osf_serdev.c diff --git a/MAINTAINERS b/MAINTAINERS index bad05db854cf..fbbf5f06f1a5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20516,8 +20516,7 @@ M: Jinseob Kim S: Maintained F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml F: Documentation/iio/open-sensor-fusion.rst -F: drivers/iio/opensensorfusion/osf_protocol.* -F: drivers/iio/opensensorfusion/osf_stream.* +F: drivers/iio/opensensorfusion/ K: opensensorfusion =20 OPENCOMPUTE PTP CLOCK DRIVER diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 652557a5b851..89bb5b65c761 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -102,6 +102,7 @@ source "drivers/iio/light/Kconfig" source "drivers/iio/magnetometer/Kconfig" source "drivers/iio/multiplexer/Kconfig" source "drivers/iio/orientation/Kconfig" +source "drivers/iio/opensensorfusion/Kconfig" source "drivers/iio/test/Kconfig" if IIO_TRIGGER source "drivers/iio/trigger/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index f03a4100c800..a51c4446ee09 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -38,6 +38,7 @@ obj-y +=3D light/ obj-y +=3D magnetometer/ obj-y +=3D multiplexer/ obj-y +=3D orientation/ +obj-y +=3D opensensorfusion/ obj-y +=3D position/ obj-y +=3D potentiometer/ obj-y +=3D potentiostat/ diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorf= usion/Kconfig new file mode 100644 index 000000000000..fa25ad24bef6 --- /dev/null +++ b/drivers/iio/opensensorfusion/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config OPEN_SENSOR_FUSION + tristate "Open Sensor Fusion UART receiver" + depends on SERIAL_DEV_BUS + select CRC32 + help + Build the Open Sensor Fusion UART receive path. + + The driver receives and validates OSF protocol frames over a serdev + UART and caches device status reported by the sensor hub. + Message types without an application handler are ignored. diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensor= fusion/Makefile new file mode 100644 index 000000000000..940c82eddc2e --- /dev/null +++ b/drivers/iio/opensensorfusion/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_OPEN_SENSOR_FUSION) +=3D open-sensor-fusion.o + +open-sensor-fusion-y :=3D osf_core.o osf_protocol.o osf_serdev.o osf_strea= m.o diff --git a/drivers/iio/opensensorfusion/osf_core.c b/drivers/iio/opensens= orfusion/osf_core.c new file mode 100644 index 000000000000..e6812cef4d8c --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_core.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +#include "osf_core.h" +#include "osf_stream.h" + +#define OSF_RESERVED_MSG_FIRST 0x7f00 +#define OSF_RESERVED_MSG_LAST 0x7fff +#define OSF_VENDOR_PRIVATE_FIRST 0x8000 + +void osf_core_init(struct osf_device *osf, struct device *dev) +{ + *osf =3D (struct osf_device) { + .dev =3D dev, + }; +} + +static int osf_core_handle_device_status(struct osf_device *osf, + const struct osf_frame *frame) +{ + struct osf_device_status status; + int ret; + + ret =3D osf_protocol_decode_device_status(frame, &status); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed device status: %d\n", + ret); + return ret; + } + + osf->status_cache =3D (struct osf_status_cache) { + .uptime_s =3D status.uptime_s, + .status_flags =3D status.status_flags, + .error_flags =3D status.error_flags, + .dropped_frames =3D status.dropped_frames, + .sequence =3D frame->sequence, + .valid =3D true, + }; + osf->last_sequence =3D frame->sequence; + + return OSF_STREAM_FRAME_HANDLED; +} + +int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t l= en) +{ + struct osf_frame frame; + size_t frame_len; + int ret; + + ret =3D osf_protocol_decode_frame(buf, len, &frame, &frame_len); + if (ret) + return ret; + + if (frame_len !=3D len) + return -EMSGSIZE; + + if (frame.protocol_major !=3D OSF_PROTOCOL_MAJOR) { + dev_dbg_ratelimited(osf->dev, + "ignoring unsupported protocol major %u\n", + frame.protocol_major); + return OSF_STREAM_FRAME_IGNORED; + } + + switch (frame.message_type) { + case OSF_MSG_DEVICE_STATUS: + ret =3D osf_core_handle_device_status(osf, &frame); + break; + default: + if (frame.message_type >=3D OSF_RESERVED_MSG_FIRST && + frame.message_type <=3D OSF_RESERVED_MSG_LAST) { + dev_dbg_ratelimited(osf->dev, + "ignoring reserved message type %#x\n", + frame.message_type); + return OSF_STREAM_FRAME_IGNORED; + } + if (frame.message_type >=3D OSF_VENDOR_PRIVATE_FIRST) { + dev_dbg_ratelimited(osf->dev, + "ignoring vendor message type %#x\n", + frame.message_type); + return OSF_STREAM_FRAME_IGNORED; + } + + dev_dbg_ratelimited(osf->dev, + "ignoring unsupported message type %#x\n", + frame.message_type); + return OSF_STREAM_FRAME_IGNORED; + } + + /* + * Handler failures are validated application rejections. Keep the + * trusted frame boundary and let the stream consume the complete frame. + */ + if (ret < 0) + return OSF_STREAM_FRAME_REJECTED; + + return ret; +} diff --git a/drivers/iio/opensensorfusion/osf_core.h b/drivers/iio/opensens= orfusion/osf_core.h new file mode 100644 index 000000000000..7095e3f967fc --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_core.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_CORE_H +#define _OSF_CORE_H + +#include + +#include "osf_protocol.h" + +struct device; + +struct osf_status_cache { + u32 uptime_s; + u32 status_flags; + u32 error_flags; + u32 dropped_frames; + u64 sequence; + bool valid; +}; + +struct osf_device { + struct device *dev; + struct osf_status_cache status_cache; + u64 last_sequence; +}; + +void osf_core_init(struct osf_device *osf, struct device *dev); +int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t l= en); + +#endif diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opense= nsorfusion/osf_serdev.c new file mode 100644 index 000000000000..8a747c01ff9d --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_serdev.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_stream.h" + +#define OSF_SERDEV_BAUD 115200 + +struct osf_serdev { + struct serdev_device *serdev; + struct regulator *vcc; + bool vcc_enabled; + struct osf_device osf; + struct osf_stream stream; + /* Protects the parser and all RX counters. */ + struct mutex rx_lock; +}; + +static int osf_serdev_receive_frame(void *context, const u8 *buf, size_t l= en) +{ + struct osf_device *osf =3D context; + + return osf_core_receive_frame(osf, buf, len); +} + +static size_t osf_serdev_receive_buf(struct serdev_device *serdev, + const u8 *buf, size_t count) +{ + struct osf_serdev *osf_uart =3D serdev_device_get_drvdata(serdev); + const struct osf_stream_stats *stats; + u64 validated_before; + int ret; + + guard(mutex)(&osf_uart->rx_lock); + validated_before =3D osf_uart->stream.stats.validated_frames; + ret =3D osf_stream_receive_bytes(&osf_uart->stream, buf, count); + stats =3D &osf_uart->stream.stats; + + if (ret || stats->validated_frames !=3D validated_before) + dev_dbg_ratelimited(&serdev->dev, + "rx count=3D%zu validated=3D%llu handled=3D%llu ignored=3D%llu rej= ected=3D%llu bad_magic=3D%llu bad_crc=3D%llu dropped=3D%llu ret=3D%d\n", + count, stats->validated_frames, + stats->handled_frames, stats->ignored_frames, + stats->rejected_frames, + stats->bad_magic_resyncs, + stats->bad_crc_frames, + stats->dropped_bytes, ret); + + return count; +} + +static const struct serdev_device_ops osf_serdev_ops =3D { + .receive_buf =3D osf_serdev_receive_buf, +}; + +static void osf_serdev_release(void *data) +{ + struct osf_serdev *osf_uart =3D data; + + /* The TTY controller drains RX work on close; hold no RX lock. */ + serdev_device_close(osf_uart->serdev); + osf_stream_reset(&osf_uart->stream); + if (osf_uart->vcc_enabled) + regulator_disable(osf_uart->vcc); +} + +static int osf_serdev_probe(struct serdev_device *serdev) +{ + struct device *dev =3D &serdev->dev; + struct osf_serdev *osf_uart; + unsigned int baudrate; + int ret; + + osf_uart =3D devm_kzalloc(dev, sizeof(*osf_uart), GFP_KERNEL); + if (!osf_uart) + return -ENOMEM; + + osf_uart->vcc =3D devm_regulator_get(dev, "vcc"); + if (IS_ERR(osf_uart->vcc)) + return dev_err_probe(dev, PTR_ERR(osf_uart->vcc), + "failed to get vcc regulator\n"); + + mutex_init(&osf_uart->rx_lock); + osf_uart->serdev =3D serdev; + osf_core_init(&osf_uart->osf, dev); + osf_stream_init(&osf_uart->stream, osf_serdev_receive_frame, + &osf_uart->osf); + + serdev_device_set_drvdata(serdev, osf_uart); + serdev_device_set_client_ops(serdev, &osf_serdev_ops); + + ret =3D serdev_device_open(serdev); + if (ret) + return ret; + + ret =3D devm_add_action_or_reset(dev, osf_serdev_release, osf_uart); + if (ret) + return ret; + + baudrate =3D serdev_device_set_baudrate(serdev, OSF_SERDEV_BAUD); + if (baudrate !=3D OSF_SERDEV_BAUD) + /* Keep accepting controller rounding after reporting the mismatch. */ + dev_warn_probe(dev, -EINVAL, "requested %u baud, controller set %u\n", + OSF_SERDEV_BAUD, baudrate); + + serdev_device_set_flow_control(serdev, false); + + ret =3D regulator_enable(osf_uart->vcc); + if (ret) + return dev_err_probe(dev, ret, "failed to enable vcc regulator\n"); + osf_uart->vcc_enabled =3D true; + + return 0; +} + +static const struct of_device_id osf_serdev_of_match[] =3D { + { .compatible =3D "opensensorfusion,osf" }, + { } +}; +MODULE_DEVICE_TABLE(of, osf_serdev_of_match); + +static struct serdev_device_driver osf_serdev_driver =3D { + .probe =3D osf_serdev_probe, + .driver =3D { + .name =3D "open-sensor-fusion-uart", + .of_match_table =3D osf_serdev_of_match, + }, +}; +module_serdev_device_driver(osf_serdev_driver); + +MODULE_DESCRIPTION("Open Sensor Fusion UART receiver"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 37FBC5275AD for ; Fri, 18 Sep 2026 18:25:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755955; cv=none; b=mRAOZ5UXC4P2v4MtJT1BemttD+gqCbPbtUAWkBq4FG6B3aeNke+BgFco0OZKWqx9w76/rbU/I3W9rhBpqGmKtFsuX9am/7wrmCEuADz84UkNCgc4q/P+df9Fh5YZEfE6hSokPzKmsxhuGj75PTz6TpBbF/phAmkENvoJuZOX2OE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755955; c=relaxed/simple; bh=2OGhOg4x8E+oiLi6DIb0pGpF3aPtbsqrEd6Kyowp3Dw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=C5J2g4dJ7xQOioXur/Pphlr7X40hMMnjiMY/Q3wYQz+6umqozkMZPzZVtDQWd1+ykZUccktTnG3G0c0I2rEEJz98tNESD2TMJGazKQ1a6XDGaRQk9+E0lse2Wl5f8k5QvW4vtaiZhpPFoWcDmU06YZ1RquWHKAwCYmYl2udhjGA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=gEO8EiPE; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="gEO8EiPE" Received: by mail-pj2-f13.google.com with SMTP id d9443c01a7336-2d93ff61046so10585605ad.3 for ; Fri, 18 Sep 2026 11:25:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755952; x=1790360752; 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=bjLqYREv7hzeTClbjazYWzBGvHYiFH3EC3usZTg/nxc=; b=gEO8EiPEAIuium84kTBGjjJzXI5SVdgthfDjuey0T9jJEYmntxtGdaYhVi4z0gOY/b LkZEir4ySGfLaCTJ+vEAlIQ3Xa9JgCdVCdCKnj24xHQ+nGpcZlg2jemfVvDxUCTqithq 5xEFv3iypoTYATyRZnTgwVMIvrJIzG1NuXZi5pzkSMFJZV5Fs6Rd1pNDh+5kmmYBuQ5X dL/J+rn16j0RtAyiIQoPHoJ+GLqjq/TGUYb+DbeL06FsK64uW3N0MUbk0a1DsFExSCIX B22b6Gq6zPXG8Ix6+jrzJWtDXWFwu7wQcucUfr3hnKgPN/821EwGZgQLXBLwYOL4m0M9 OYEw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755952; x=1790360752; 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=bjLqYREv7hzeTClbjazYWzBGvHYiFH3EC3usZTg/nxc=; b=tMpg9NVK6g6V0QkzEDmnFyzyAvTHlklIDkJuwNQhiPAwIP8tnVau9+nk+2XcsAbuK0 11IpBN6PGzGaUDegk1KYLKc3TmYc457g0akhCoHTrawTgy9xj5UYIX7xsHa4YZw2jbTD +MRSTxy4Smt+hi9272WrontBUvBEwNE4m0ZddqvR9cVb8QmjuYiaBzaANryN8AutxxJS aVPhxfG2BPIuV6yqt/f19Nj+GT4NMEPP9xihfxuCejqbggoqDx4NmF/jTR/BuMPGtBoX Gmw+vy3BUhuVByIon3ByimZm5B2cd/yYb696UZmk3spGs61avQrJ2hTq7pqBRGlAIp4i FQfw== X-Forwarded-Encrypted: i=1; AKwUvBztS35M8TSmhx4v5DjGgm2Llx/SZR9gPQcfqXVkxpj1O8rB/BJ55QhdzrTRR0XQPy8Ipv7gCfIveyBz1Pc=@vger.kernel.org X-Gm-Message-State: AFuF++ncC/b/QfOYx2MJV55MlPkd4WI2IMv9V8UUrjcDFXGRLA4aD0BK SbHPofNq2zlX+6IhWPipaUYVamY+4s3nnEcn9hQVVbcE3GGN/gbLKztg X-Gm-Gg: AYBFou1mzAZV/KF3JhJdNXAhcXQYq2kfMm4I7f80E/un1mHONvsPdHHcdFkmAz30Xlj jLIpGxR0wzZFB4Oh/A+QV0zF6J1htXDnJ3ATdvS7EPFxMMq4nsQCp9mvYpqugs9MngAvA+zEzXC TG6WxD5p+eEPnAVye9YWeyAFlDw7i2TN+VGZrJNY7QZfU0ZrLZSmlXSt0pfNMBm4Du3QI4BJeQO /cdOuX/+Hj58sUSthc19APLgB4qRVFYkXcy5juiU1QnXKQhpZ7jkgrwg7PuKIb1Ayhq1GjLSNBt tiAf5Ml3NkoO3eB9PPzDxGBuztUTvcM+oF0A5jckYGkaM21yqA0SC4+rZSj1QEDY03IuG8xl6OT SBdxS8R70D9l5iHSEQC1d62mRPYv96Bd0M3DNm9MokcCibkGgQiIKRtThuIrLrTJNWlCkiV2EH1 4ac8FA7BYoEmzbq6iDB/x2/9/9upZ9WTOpy6pGTSBYvwkeZ5MSaXjbt668XAbhx7aDkZseupjzN 4j9Bp8phaY7j5lAnRb6QSWIPMXHS/uF09fBDHYcadcI56i74q/TTSmeSbn8r+knr+pByw/+saCb rKTw4Mu7 X-Received: by 2002:a17:902:ccd1:b0:2dd:40cb:a04c with SMTP id d9443c01a7336-2ddb1b7cc6fmr68702465ad.19.1789755952231; Fri, 18 Sep 2026 11:25:52 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:25:51 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 6/8] iio: osf: add IIO devices from capability reports Date: Sat, 19 Sep 2026 03:24:44 +0900 Message-ID: X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Register IIO devices from supported capability descriptors and expose signed raw samples, descriptor scales and software buffers. Keep early capabilities owned by the core until UART and supply setup is complete, and unregister children after the receive producer has stopped. Validate nonzero descriptor scales and sample/descriptor equality, leave discovery open after empty or unsupported inventories, and compare repeated descriptors independently of order. Fault a bound session if a descriptor changes meaning, gating cache and buffer publication together. Add initialized active-scan packing and buffer producer quiescence. The production implementation is now complete; subsequent patches only add the existing core and IIO KUnit suites and their build wiring. Assisted-by: LLM Signed-off-by: Jinseob Kim --- drivers/iio/opensensorfusion/Kconfig | 13 +- drivers/iio/opensensorfusion/Makefile | 3 +- drivers/iio/opensensorfusion/osf_core.c | 364 ++++++++++++++++++++++ drivers/iio/opensensorfusion/osf_core.h | 46 +++ drivers/iio/opensensorfusion/osf_iio.c | 338 ++++++++++++++++++++ drivers/iio/opensensorfusion/osf_iio.h | 22 ++ drivers/iio/opensensorfusion/osf_serdev.c | 11 +- 7 files changed, 788 insertions(+), 9 deletions(-) create mode 100644 drivers/iio/opensensorfusion/osf_iio.c create mode 100644 drivers/iio/opensensorfusion/osf_iio.h diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorf= usion/Kconfig index fa25ad24bef6..f955a1f2993d 100644 --- a/drivers/iio/opensensorfusion/Kconfig +++ b/drivers/iio/opensensorfusion/Kconfig @@ -1,12 +1,15 @@ # SPDX-License-Identifier: GPL-2.0-only =20 config OPEN_SENSOR_FUSION - tristate "Open Sensor Fusion UART receiver" + tristate "Open Sensor Fusion UART IIO driver" depends on SERIAL_DEV_BUS select CRC32 + select IIO_BUFFER + select IIO_KFIFO_BUF help - Build the Open Sensor Fusion UART receive path. + Build the Open Sensor Fusion UART IIO driver. =20 - The driver receives and validates OSF protocol frames over a serdev - UART and caches device status reported by the sensor hub. - Message types without an application handler are ignored. + The driver receives OSF protocol frames over a serdev UART and + registers IIO devices for supported capability entries. It exposes + accelerometer, gyroscope, magnetometer, and temperature samples + through IIO direct reads and software buffers. diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensor= fusion/Makefile index 940c82eddc2e..b4e03b80cfa4 100644 --- a/drivers/iio/opensensorfusion/Makefile +++ b/drivers/iio/opensensorfusion/Makefile @@ -2,4 +2,5 @@ =20 obj-$(CONFIG_OPEN_SENSOR_FUSION) +=3D open-sensor-fusion.o =20 -open-sensor-fusion-y :=3D osf_core.o osf_protocol.o osf_serdev.o osf_strea= m.o +open-sensor-fusion-y :=3D osf_core.o osf_iio.o osf_protocol.o osf_serdev.o= \ + osf_stream.o diff --git a/drivers/iio/opensensorfusion/osf_core.c b/drivers/iio/opensens= orfusion/osf_core.c index e6812cef4d8c..6f83ab485b34 100644 --- a/drivers/iio/opensensorfusion/osf_core.c +++ b/drivers/iio/opensensorfusion/osf_core.c @@ -1,10 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-only =20 +#include #include #include +#include #include =20 #include "osf_core.h" +#include "osf_iio.h" #include "osf_stream.h" =20 #define OSF_RESERVED_MSG_FIRST 0x7f00 @@ -16,6 +19,247 @@ void osf_core_init(struct osf_device *osf, struct devic= e *dev) *osf =3D (struct osf_device) { .dev =3D dev, }; + mutex_init(&osf->latest_lock); +} + +void osf_core_unregister_iio(struct osf_device *osf) +{ + for (unsigned int i =3D 0; i < osf->iio_dev_count; i++) + osf_iio_unregister_sensor(osf->iio_devs[i].indio_dev); + + osf->iio_dev_count =3D 0; +} + +static struct iio_dev *osf_core_find_iio_dev(struct osf_device *osf, + u16 sensor_type, u16 sensor_index) +{ + const struct osf_iio_binding *binding; + + for (unsigned int i =3D 0; i < osf->iio_dev_count; i++) { + binding =3D &osf->iio_devs[i]; + if (binding->sensor_type =3D=3D sensor_type && + binding->sensor_index =3D=3D sensor_index) + return binding->indio_dev; + } + + return NULL; +} + +static struct osf_latest_sample * +osf_core_find_latest_sample(struct osf_device *osf, u16 sensor_type, + u16 sensor_index) +{ + struct osf_latest_sample *latest; + + for (unsigned int i =3D 0; i < osf->latest_sample_count; i++) { + latest =3D &osf->latest_samples[i]; + if (latest->sensor_type =3D=3D sensor_type && + latest->sensor_index =3D=3D sensor_index) + return latest; + } + + if (osf->latest_sample_count >=3D OSF_MAX_CAPABILITIES) + return NULL; + + return &osf->latest_samples[osf->latest_sample_count++]; +} + +static bool +osf_core_capability_supported(const struct osf_capability_entry *entry) +{ + return osf_iio_sensor_supported(entry->sensor_type, + entry->channel_count) && + entry->sample_format =3D=3D OSF_SAMPLE_FORMAT_S32 && + !(entry->flags & ~OSF_CAPABILITY_FLAGS_MASK); +} + +static const struct osf_capability_entry * +osf_core_find_capability(const struct osf_capability_cache *cache, + u16 sensor_type, u16 sensor_index) +{ + for (u16 i =3D 0; i < cache->capability_count; i++) { + const struct osf_capability_entry *entry =3D &cache->entries[i]; + + if (entry->sensor_type =3D=3D sensor_type && + entry->sensor_index =3D=3D sensor_index) + return entry; + } + + return NULL; +} + +static bool +osf_core_capabilities_equal(const struct osf_capability_cache *old, + const struct osf_capability_cache *new) +{ + if (old->capability_count !=3D new->capability_count) + return false; + + for (u16 i =3D 0; i < old->capability_count; i++) { + const struct osf_capability_entry *entry =3D &old->entries[i]; + const struct osf_capability_entry *other; + + other =3D osf_core_find_capability(new, entry->sensor_type, + entry->sensor_index); + /* Type and the supported major version also fix the unit. */ + if (!other || other->channel_count !=3D entry->channel_count || + other->sample_format !=3D entry->sample_format || + other->scale_nano !=3D entry->scale_nano) + return false; + } + + return true; +} + +static bool osf_core_capability_is_duplicate(const struct osf_capability_c= ache *cache, + u16 index) +{ + const struct osf_capability_entry *entry =3D &cache->entries[index]; + + for (u16 i =3D 0; i < index; i++) { + if (cache->entries[i].sensor_type =3D=3D entry->sensor_type && + cache->entries[i].sensor_index =3D=3D entry->sensor_index) + return true; + } + + return false; +} + +static int osf_core_register_capabilities(struct osf_device *osf, + const struct osf_capability_cache *cache) +{ + struct iio_dev *indio_dev; + int ret; + + for (u16 i =3D 0; i < cache->capability_count; i++) { + ret =3D osf_iio_register_sensor(osf->dev, &cache->entries[i], + osf, &indio_dev); + if (ret) + goto err_unregister; + + osf->iio_devs[osf->iio_dev_count++] =3D (struct osf_iio_binding) { + .sensor_type =3D cache->entries[i].sensor_type, + .sensor_index =3D cache->entries[i].sensor_index, + .indio_dev =3D indio_dev, + }; + } + + return 0; + +err_unregister: + osf_core_unregister_iio(osf); + + return ret; +} + +void osf_core_start(struct osf_device *osf) +{ + int ret; + + if (osf->iio_ready) + return; + + osf->iio_ready =3D true; + if (!osf->capability_cache.valid || + !osf->capability_cache.capability_count) + return; + + ret =3D osf_core_register_capabilities(osf, &osf->capability_cache); + if (ret) { + /* As with RX discovery failure, a later report may retry. */ + osf->capability_cache.valid =3D false; + dev_err_ratelimited(osf->dev, + "failed to register pending capabilities: %d\n", + ret); + } +} + +static int osf_core_handle_sensor_sample(struct osf_device *osf, + const struct osf_frame *frame) +{ + const struct osf_capability_entry *entry; + struct osf_latest_sample *latest; + struct osf_sensor_sample sample; + struct iio_dev *indio_dev; + s32 values[OSF_MAX_SAMPLE_CHANNELS] =3D { }; + int ret; + + ret =3D osf_protocol_decode_sensor_sample(frame, &sample); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed sensor sample: %d\n", + ret); + return ret; + } + + indio_dev =3D osf_core_find_iio_dev(osf, sample.sensor_type, + sample.sensor_index); + if (!indio_dev) { + dev_dbg_ratelimited(osf->dev, + "ignoring sample for unregistered sensor %#x:%u\n", + sample.sensor_type, sample.sensor_index); + return OSF_STREAM_FRAME_IGNORED; + } + + entry =3D osf_core_find_capability(&osf->capability_cache, + sample.sensor_type, sample.sensor_index); + if (!entry || sample.channel_count !=3D entry->channel_count || + sample.sample_format !=3D entry->sample_format || + sample.scale_nano !=3D entry->scale_nano) + return -EPROTO; + + if (sample.channel_count > OSF_MAX_SAMPLE_CHANNELS) { + dev_warn_ratelimited(osf->dev, + "rejecting sensor sample with %u channels\n", + sample.channel_count); + return -E2BIG; + } + + for (u16 i =3D 0; i < sample.channel_count; i++) { + ret =3D osf_protocol_sensor_sample_value(&sample, i, &values[i]); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed sample value: %d\n", + ret); + return ret; + } + } + + /* Serialize publication and cache admission with the session fault. */ + guard(mutex)(&osf->latest_lock); + if (osf->session_fault) + return -EPROTO; + + ret =3D osf_iio_push_sample(indio_dev, values, sample.channel_count); + if (ret) { + dev_err_ratelimited(osf->dev, + "failed to push sensor %#x:%u sample: %d\n", + sample.sensor_type, sample.sensor_index, ret); + return ret; + } + + latest =3D osf_core_find_latest_sample(osf, sample.sensor_type, + sample.sensor_index); + if (!latest) { + dev_err_ratelimited(osf->dev, + "latest sample cache full for sensor %#x:%u\n", + sample.sensor_type, + sample.sensor_index); + return -ENOSPC; + } + + memcpy(latest->values, values, sizeof(values)); + latest->sensor_type =3D sample.sensor_type; + latest->sensor_index =3D sample.sensor_index; + latest->channel_count =3D sample.channel_count; + latest->sample_format =3D sample.sample_format; + latest->scale_nano =3D sample.scale_nano; + latest->sequence =3D frame->sequence; + latest->timestamp_us =3D frame->timestamp_us; + latest->valid =3D true; + osf->last_sequence =3D frame->sequence; + + return OSF_STREAM_FRAME_HANDLED; } =20 static int osf_core_handle_device_status(struct osf_device *osf, @@ -45,6 +289,91 @@ static int osf_core_handle_device_status(struct osf_dev= ice *osf, return OSF_STREAM_FRAME_HANDLED; } =20 +static int osf_core_handle_capability_report(struct osf_device *osf, + const struct osf_frame *frame) +{ + struct osf_capability_cache cache =3D { }; + struct osf_capability_report report; + int frame_result; + int ret; + + ret =3D osf_protocol_decode_capability_report(frame, &report); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed capability report: %d\n", + ret); + return ret; + } + + for (u16 i =3D 0; i < report.capability_count; i++) { + struct osf_capability_entry entry; + + ret =3D osf_protocol_decode_capability_entry(&report, i, &entry); + if (ret) { + dev_warn_ratelimited(osf->dev, + "rejecting malformed capability entry: %d\n", + ret); + return ret; + } + + if (!osf_core_capability_supported(&entry)) + continue; + + if (!entry.scale_nano) + return -EPROTO; + + if (cache.capability_count >=3D OSF_MAX_CAPABILITIES) { + dev_warn_ratelimited(osf->dev, + "too many supported capabilities\n"); + return -E2BIG; + } + + cache.entries[cache.capability_count] =3D entry; + if (osf_core_capability_is_duplicate(&cache, cache.capability_count)) { + dev_warn_ratelimited(osf->dev, + "rejecting duplicate capability\n"); + return -EEXIST; + } + cache.capability_count++; + } + + cache.sequence =3D frame->sequence; + cache.valid =3D cache.capability_count !=3D 0; + + if (osf->iio_dev_count) { + if (!osf_core_capabilities_equal(&osf->capability_cache, &cache)) { + guard(mutex)(&osf->latest_lock); + osf->session_fault =3D true; + return -EPROTO; + } + + osf->last_sequence =3D frame->sequence; + return OSF_STREAM_FRAME_IGNORED; + } + + frame_result =3D OSF_STREAM_FRAME_IGNORED; + if (cache.capability_count) { + if (osf->iio_ready) { + ret =3D osf_core_register_capabilities(osf, &cache); + if (ret) { + dev_err_ratelimited(osf->dev, + "failed to register capabilities: %d\n", + ret); + return ret; + } + } + frame_result =3D OSF_STREAM_FRAME_HANDLED; + } else { + dev_dbg_ratelimited(osf->dev, + "ignoring report without supported capabilities\n"); + } + + osf->capability_cache =3D cache; + osf->last_sequence =3D frame->sequence; + + return frame_result; +} + int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t l= en) { struct osf_frame frame; @@ -66,9 +395,15 @@ int osf_core_receive_frame(struct osf_device *osf, cons= t u8 *buf, size_t len) } =20 switch (frame.message_type) { + case OSF_MSG_SENSOR_SAMPLE: + ret =3D osf_core_handle_sensor_sample(osf, &frame); + break; case OSF_MSG_DEVICE_STATUS: ret =3D osf_core_handle_device_status(osf, &frame); break; + case OSF_MSG_CAPABILITY_REPORT: + ret =3D osf_core_handle_capability_report(osf, &frame); + break; default: if (frame.message_type >=3D OSF_RESERVED_MSG_FIRST && frame.message_type <=3D OSF_RESERVED_MSG_LAST) { @@ -99,3 +434,32 @@ int osf_core_receive_frame(struct osf_device *osf, cons= t u8 *buf, size_t len) =20 return ret; } + +int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type, + u16 sensor_index, u16 channel, + s32 *value) +{ + const struct osf_latest_sample *latest; + + if (!osf || !value) + return -EINVAL; + + guard(mutex)(&osf->latest_lock); + if (osf->session_fault) + return -EPROTO; + + for (unsigned int i =3D 0; i < osf->latest_sample_count; i++) { + latest =3D &osf->latest_samples[i]; + if (latest->sensor_type !=3D sensor_type || + latest->sensor_index !=3D sensor_index) + continue; + + if (!latest->valid || channel >=3D latest->channel_count) + break; + + *value =3D latest->values[channel]; + return 0; + } + + return -ENODATA; +} diff --git a/drivers/iio/opensensorfusion/osf_core.h b/drivers/iio/opensens= orfusion/osf_core.h index 7095e3f967fc..1fd8e5db1443 100644 --- a/drivers/iio/opensensorfusion/osf_core.h +++ b/drivers/iio/opensensorfusion/osf_core.h @@ -2,11 +2,35 @@ #ifndef _OSF_CORE_H #define _OSF_CORE_H =20 +#include #include =20 #include "osf_protocol.h" =20 +#define OSF_MAX_SAMPLE_CHANNELS 3 +#define OSF_MAX_CAPABILITIES 16 + struct device; +struct iio_dev; + +struct osf_latest_sample { + u16 sensor_type; + u16 sensor_index; + u16 channel_count; + u16 sample_format; + u32 scale_nano; + s32 values[OSF_MAX_SAMPLE_CHANNELS]; + u64 sequence; + u64 timestamp_us; + bool valid; +}; + +struct osf_capability_cache { + u16 capability_count; + struct osf_capability_entry entries[OSF_MAX_CAPABILITIES]; + u64 sequence; + bool valid; +}; =20 struct osf_status_cache { u32 uptime_s; @@ -17,13 +41,35 @@ struct osf_status_cache { bool valid; }; =20 +struct osf_iio_binding { + u16 sensor_type; + u16 sensor_index; + struct iio_dev *indio_dev; +}; + struct osf_device { struct device *dev; + bool iio_ready; + /* Protects session_fault and latest samples; nests outside buffer_lock. = */ + struct mutex latest_lock; + bool session_fault; + struct osf_latest_sample latest_samples[OSF_MAX_CAPABILITIES]; + unsigned int latest_sample_count; + struct osf_capability_cache capability_cache; struct osf_status_cache status_cache; + struct osf_iio_binding iio_devs[OSF_MAX_CAPABILITIES]; + unsigned int iio_dev_count; u64 last_sequence; }; =20 void osf_core_init(struct osf_device *osf, struct device *dev); +/* Serialize start with receive_frame; pending entries own their data. */ +void osf_core_start(struct osf_device *osf); +/* Stop the receive producer before teardown; init starts a fresh session.= */ +void osf_core_unregister_iio(struct osf_device *osf); int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t l= en); +int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type, + u16 sensor_index, u16 channel, + s32 *value); =20 #endif diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensenso= rfusion/osf_iio.c new file mode 100644 index 000000000000..f4011b8fae14 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_iio.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_iio.h" + +struct osf_iio_sensor_spec { + u16 sensor_type; + u16 channel_count; + const char *name; + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct osf_iio_state { + const struct osf_iio_sensor_spec *spec; + struct iio_buffer *buffer; + /* Serializes pushes with buffer activation and quiescence. */ + struct mutex buffer_lock; + bool buffer_active; + u32 scale_nano; + u16 sensor_index; + struct osf_device *osf; +}; + +struct osf_iio_scan_3axis { + s32 values[3]; + u32 padding; + aligned_s64 timestamp; +}; + +struct osf_iio_scan_1axis { + s32 value; + u32 padding; + aligned_s64 timestamp; +}; + +#define OSF_MOD_CHAN(_type, _mod, _idx) \ + { \ + .type =3D (_type), \ + .modified =3D 1, \ + .channel2 =3D (_mod), \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type =3D BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index =3D (_idx), \ + .scan_type =3D { \ + .sign =3D 's', \ + .realbits =3D 32, \ + .storagebits =3D 32, \ + .endianness =3D IIO_CPU, \ + }, \ + } + +#define OSF_CHAN(_type, _idx) \ + { \ + .type =3D (_type), \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type =3D BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index =3D (_idx), \ + .scan_type =3D { \ + .sign =3D 's', \ + .realbits =3D 32, \ + .storagebits =3D 32, \ + .endianness =3D IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec osf_accel_channels[] =3D { + OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_X, 0), + OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_Y, 1), + OSF_MOD_CHAN(IIO_ACCEL, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec osf_gyro_channels[] =3D { + OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_X, 0), + OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, 1), + OSF_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec osf_mag_channels[] =3D { + OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_X, 0), + OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_Y, 1), + OSF_MOD_CHAN(IIO_MAGN, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec osf_temp_channels[] =3D { + OSF_CHAN(IIO_TEMP, 0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct osf_iio_sensor_spec osf_iio_sensor_specs[] =3D { + { + .sensor_type =3D OSF_SENSOR_ACCELEROMETER, + .channel_count =3D 3, + .name =3D "osf-accel", + .channels =3D osf_accel_channels, + .num_channels =3D ARRAY_SIZE(osf_accel_channels), + }, + { + .sensor_type =3D OSF_SENSOR_GYROSCOPE, + .channel_count =3D 3, + .name =3D "osf-gyro", + .channels =3D osf_gyro_channels, + .num_channels =3D ARRAY_SIZE(osf_gyro_channels), + }, + { + .sensor_type =3D OSF_SENSOR_MAGNETOMETER, + .channel_count =3D 3, + .name =3D "osf-magn", + .channels =3D osf_mag_channels, + .num_channels =3D ARRAY_SIZE(osf_mag_channels), + }, + { + .sensor_type =3D OSF_SENSOR_TEMPERATURE, + .channel_count =3D 1, + .name =3D "osf-temp", + .channels =3D osf_temp_channels, + .num_channels =3D ARRAY_SIZE(osf_temp_channels), + }, +}; + +static const struct osf_iio_sensor_spec * +osf_iio_find_sensor_spec(u16 sensor_type, u16 channel_count) +{ + for (unsigned int i =3D 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) { + if (osf_iio_sensor_specs[i].sensor_type =3D=3D sensor_type && + osf_iio_sensor_specs[i].channel_count =3D=3D channel_count) + return &osf_iio_sensor_specs[i]; + } + + return NULL; +} + +bool osf_iio_sensor_supported(u16 sensor_type, u16 channel_count) +{ + if (osf_iio_find_sensor_spec(sensor_type, channel_count)) + return true; + + return false; +} + +const char *osf_iio_sensor_name(u16 sensor_type) +{ + for (unsigned int i =3D 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) { + if (osf_iio_sensor_specs[i].sensor_type =3D=3D sensor_type) + return osf_iio_sensor_specs[i].name; + } + + return NULL; +} + +static int osf_iio_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + s32 raw; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret =3D osf_core_read_latest_sample(state->osf, + state->spec->sensor_type, + state->sensor_index, + chan->scan_index, &raw); + if (ret) + return ret; + + *val =3D raw; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val =3D state->scale_nano / NANO; + *val2 =3D state->scale_nano % NANO; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info osf_iio_info =3D { + .read_raw =3D osf_iio_read_raw, +}; + +static int osf_iio_buffer_postenable(struct iio_dev *indio_dev) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + + guard(mutex)(&state->buffer_lock); + state->buffer_active =3D true; + + return 0; +} + +static int osf_iio_buffer_predisable(struct iio_dev *indio_dev) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + + /* Wait for the current push before the IIO core changes its buffers. */ + guard(mutex)(&state->buffer_lock); + state->buffer_active =3D false; + + return 0; +} + +static const struct iio_buffer_setup_ops osf_iio_buffer_ops =3D { + .postenable =3D osf_iio_buffer_postenable, + .predisable =3D osf_iio_buffer_predisable, +}; + +int osf_iio_register_sensor(struct device *dev, + const struct osf_capability_entry *entry, + struct osf_device *osf, struct iio_dev **indio_dev) +{ + const struct osf_iio_sensor_spec *spec; + struct osf_iio_state *state; + struct iio_dev *iio_dev; + int ret; + + spec =3D osf_iio_find_sensor_spec(entry->sensor_type, + entry->channel_count); + if (!spec) + return -EOPNOTSUPP; + + if (entry->sample_format !=3D OSF_SAMPLE_FORMAT_S32 || + (entry->flags & ~OSF_CAPABILITY_FLAGS_MASK)) + return -EOPNOTSUPP; + + if (!entry->scale_nano) + return -EINVAL; + + iio_dev =3D iio_device_alloc(dev, sizeof(*state)); + if (!iio_dev) + return -ENOMEM; + + state =3D iio_priv(iio_dev); + state->spec =3D spec; + state->scale_nano =3D entry->scale_nano; + state->sensor_index =3D entry->sensor_index; + state->osf =3D osf; + mutex_init(&state->buffer_lock); + + iio_dev->name =3D spec->name; + iio_dev->info =3D &osf_iio_info; + iio_dev->setup_ops =3D &osf_iio_buffer_ops; + iio_dev->modes =3D INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + iio_dev->channels =3D spec->channels; + iio_dev->num_channels =3D spec->num_channels; + + state->buffer =3D iio_kfifo_allocate(); + if (!state->buffer) { + ret =3D -ENOMEM; + goto err_free_iio; + } + + ret =3D iio_device_attach_buffer(iio_dev, state->buffer); + if (ret) + goto err_free_buffer; + + ret =3D iio_device_register(iio_dev); + if (ret) + goto err_free_buffer; + + *indio_dev =3D iio_dev; + + return 0; + +err_free_buffer: + iio_kfifo_free(state->buffer); +err_free_iio: + iio_device_free(iio_dev); + + return ret; +} + +void osf_iio_unregister_sensor(struct iio_dev *indio_dev) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_kfifo_free(state->buffer); + iio_device_free(indio_dev); +} + +int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values, + u16 channel_count) +{ + struct osf_iio_state *state =3D iio_priv(indio_dev); + s64 timestamp; + + if (channel_count !=3D state->spec->channel_count) + return -EPROTO; + + guard(mutex)(&state->buffer_lock); + if (!state->buffer_active || !iio_buffer_enabled(indio_dev)) + return 0; + + timestamp =3D iio_get_time_ns(indio_dev); + + switch (channel_count) { + case 1: { + struct osf_iio_scan_1axis scan =3D { + .value =3D values[0], + }; + + return iio_push_to_buffers_with_ts(indio_dev, &scan, + sizeof(scan), timestamp); + } + case 3: { + struct osf_iio_scan_3axis scan =3D { + .values =3D { }, + }; + unsigned int channel, index =3D 0; + + /* Pack the active channels; unused storage remains initialized. */ + for_each_set_bit(channel, indio_dev->active_scan_mask, channel_count) + scan.values[index++] =3D values[channel]; + + return iio_push_to_buffers_with_ts(indio_dev, &scan, + sizeof(scan), timestamp); + } + default: + return -EPROTO; + } +} diff --git a/drivers/iio/opensensorfusion/osf_iio.h b/drivers/iio/opensenso= rfusion/osf_iio.h new file mode 100644 index 000000000000..d0745167f8e0 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_iio.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _OSF_IIO_H +#define _OSF_IIO_H + +#include + +#include "osf_protocol.h" + +struct device; +struct iio_dev; +struct osf_device; + +int osf_iio_register_sensor(struct device *dev, + const struct osf_capability_entry *entry, + struct osf_device *osf, struct iio_dev **indio_dev); +void osf_iio_unregister_sensor(struct iio_dev *indio_dev); +int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values, + u16 channel_count); +bool osf_iio_sensor_supported(u16 sensor_type, u16 channel_count); +const char *osf_iio_sensor_name(u16 sensor_type); + +#endif diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opense= nsorfusion/osf_serdev.c index 8a747c01ff9d..3d5e90d83967 100644 --- a/drivers/iio/opensensorfusion/osf_serdev.c +++ b/drivers/iio/opensensorfusion/osf_serdev.c @@ -23,7 +23,7 @@ struct osf_serdev { bool vcc_enabled; struct osf_device osf; struct osf_stream stream; - /* Protects the parser and all RX counters. */ + /* Protects the parser, all RX counters, and the registration gate. */ struct mutex rx_lock; }; =20 @@ -68,9 +68,10 @@ static void osf_serdev_release(void *data) { struct osf_serdev *osf_uart =3D data; =20 - /* The TTY controller drains RX work on close; hold no RX lock. */ + /* The TTY controller drains RX work on close; hold no RX or IIO lock. */ serdev_device_close(osf_uart->serdev); osf_stream_reset(&osf_uart->stream); + osf_core_unregister_iio(&osf_uart->osf); if (osf_uart->vcc_enabled) regulator_disable(osf_uart->vcc); } @@ -121,6 +122,10 @@ static int osf_serdev_probe(struct serdev_device *serd= ev) return dev_err_probe(dev, ret, "failed to enable vcc regulator\n"); osf_uart->vcc_enabled =3D true; =20 + /* No fallible probe steps remain when IIO children become visible. */ + scoped_guard(mutex, &osf_uart->rx_lock) + osf_core_start(&osf_uart->osf); + return 0; } =20 @@ -139,5 +144,5 @@ static struct serdev_device_driver osf_serdev_driver = =3D { }; module_serdev_device_driver(osf_serdev_driver); =20 -MODULE_DESCRIPTION("Open Sensor Fusion UART receiver"); +MODULE_DESCRIPTION("Open Sensor Fusion IIO driver"); MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f20.google.com (mail-pj2-f20.google.com [74.125.227.148]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 36AB051D531 for ; Fri, 18 Sep 2026 18:25:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.148 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755962; cv=none; b=E2sHVBgaacLGOptDJjjhMYy+kflL7k7tB0yl9+qEhCnEl5psBpQAgvoa1Oq9NmLxmOf/qwh1rqHVc7ZmOQ1EVjxvAsmoZdMoIaAsqdACZI709ZvEXpTUGY6q602FQhCnluBGwlDjpWfr5tHS2GntbUepZ9StjNlUDJw6sxRFQbQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755962; c=relaxed/simple; bh=5+FLZ2ugjjCSnIovRXb723CwZceQsHXVv9ie0rMoQks=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MM4Is284pG6GH0J43aaqYLXq/1XUBImICGbumTfJob/8C8JM241PX8Mf4C30F7EJWpETwnaK9pFwP+R21MCKncs9jQ61VsS8/FFIAcAT9MY9ufliWW/RSWlwxZAZtJ8t9mCHMTc0yerKxw1LnbCBHsDSObgSiulvnibzSDeKfYI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=W9ipVQmr; arc=none smtp.client-ip=74.125.227.148 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="W9ipVQmr" Received: by mail-pj2-f20.google.com with SMTP id d9443c01a7336-2d747ed9866so9770695ad.2 for ; Fri, 18 Sep 2026 11:25:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755957; x=1790360757; 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=3Brm1xqgYu5Tn4y8P6eCgzFxrjxB4vBjRgfZ97d2Yjs=; b=W9ipVQmrzbmQiT7yHamrivG+fJ7ZWVW+G4S1nc0D8ZTK8jTppXQT1sunGFdpaoE6ab RoSIytOpiIzCLEUaR3ZrvpWd2t+nEhYfLY71AJke9Oyd3YCEX1K4dYTXhA2OqRygGc0I y9EudGt7TsBYVExrr7FjtIpd4cR4kDJpexlB8ri8jf10rqgU3l+nhc1KfpepWpMtFhg9 qTcbH+u28+mGBN9nbRxGWYpRwiT2LbrKzOE3K6ujs1vamhKiRJdy+LkOrEaDLxdseLJT cNlFrFZpXKJSTsA/Kw9qD1xWI8Kq0QmZWV/JXtHDiQsg2/7RNXDvIPEa3Ze0EgpGJ1D1 gQiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755957; x=1790360757; 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=3Brm1xqgYu5Tn4y8P6eCgzFxrjxB4vBjRgfZ97d2Yjs=; b=UoBTIqymLTbXS/LkfOTrRTr44Vt6MhMu95VKVnl/9R8gjzDENmuS7vU+THKY6QZNP1 gqtxq7kt7+DqjftsL2xwsIIXZLX+f8KTcQ5LvhvhrxB74hvANB/RVhRjo3eG94TV9SJ2 sUEHSJrxgV0+lx0hODg1WTDt5sPm8LK4BoEPpwQdmeZej707JTlJPhqODjLaLhSDfRul scrWMi+XeFphsfMC+74qonT4xLNOOmTvvRiJ8hc2mF+5JzU27pPOog4tgW7uCZxO6dZP fD2HYfm4oZNu+Aup7r0Wy7CFyK2cVLn22Hd18DHhluCiaF0JRshkFZJ/HiKbf7oNf33v gf8w== X-Forwarded-Encrypted: i=1; AKwUvBw4p9kmhWxoOPPng0+8o6xeQmPPtgl2rla6jaZrqITA2HB7i76uG3KZZ8MPIMgx8EBhHf+Yhr/Dh69KPXc=@vger.kernel.org X-Gm-Message-State: AFuF++kFCb4P3K3/nBBmnzXkPgB85Blv3zsmYT4ODNWUK0XXh1R6aerk yuaAgGIZD1YitRXHrY5HQm3VJH8OUuBi2hi9LykwQKOmkXvj51vLYgPb X-Gm-Gg: AYBFou3Ik4JBlJ2gxpJ2HkILoC/xu5LhNfmK/U9/npOoLDncGbDXmpKwk/WoYWKcofE 1+8aIJSUS/iCrUHx2+Hlcw0nbEeo9puD5FIYYDeqL2y0mBT6bo7XyTrYgzHYVq5BzoYSnlQGno4 Nvp7KajDPhc/eEV/UpKX5LRws5bdf1IvgRGGunfpzHdWR/Jv+JsSIrn4j2EjQ2jzVI8M7D72D8I iu8h0G6Ho8cvnYQ4Co6wfm+xlFESw2+xm66QvEtTaw4bqxNJbrr6ihg1E+2HhhH5YLBKuaDaYy7 tjw1hYe7u0fxrz/Yt7y4etb30lRQJnuPSJb9Xu03EFWV3/1lNi1J6xKSdPpZMWmyB/hhZvnMdXY 2FUM5C4ubE1qV0+PmBtFgWxdtUcqbFXvqEozQZ6F7jf4uaZhBu62LSSMYyiHIVJX7gZbCbe4EWa 14lrmNq9RODr4m4jU9k1iXe5Iz/fAub/fYFp0qRcIs4pZcx0qrT5YPAkaK1aC3Hb8zKeQvJksUG CzJlmGE0YQ7h2MhyV6lYzr7scLEy2qor6L6j1zNKvVye6zkhO5g3kA0BcezfewzoHSr/P/n38eb z/DCrUDG X-Received: by 2002:a17:903:2ad0:b0:2d9:216a:cdd3 with SMTP id d9443c01a7336-2ddb1b78d1bmr79809255ad.18.1789755956971; Fri, 18 Sep 2026 11:25:56 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:25:56 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 7/8] iio: osf: add core KUnit tests Date: Sat, 19 Sep 2026 03:24:45 +0900 Message-ID: <6915383a4a73b680e357d1a75106e0cf6e413084.1789753020.git.kimjinseob88@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Add the existing core regression cases for frame handling, sample-cache admission, supported capability discovery, repeated descriptors and session faults. The tests exercise valid, ignored and rejected input without changing the production receive or IIO implementation. Introduce KUnit configuration and build wiring when the core test source is present. IIO-specific tests are added separately in the next patch. Assisted-by: LLM Signed-off-by: Jinseob Kim --- drivers/iio/opensensorfusion/Kconfig | 12 + drivers/iio/opensensorfusion/Makefile | 1 + drivers/iio/opensensorfusion/osf_core_test.c | 1046 ++++++++++++++++++ 3 files changed, 1059 insertions(+) create mode 100644 drivers/iio/opensensorfusion/osf_core_test.c diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorf= usion/Kconfig index f955a1f2993d..316f33be1327 100644 --- a/drivers/iio/opensensorfusion/Kconfig +++ b/drivers/iio/opensensorfusion/Kconfig @@ -13,3 +13,15 @@ config OPEN_SENSOR_FUSION registers IIO devices for supported capability entries. It exposes accelerometer, gyroscope, magnetometer, and temperature samples through IIO direct reads and software buffers. + +config OPEN_SENSOR_FUSION_KUNIT_TEST + bool "KUnit tests for Open Sensor Fusion" if !KUNIT_ALL_TESTS + depends on OPEN_SENSOR_FUSION=3Dy && KUNIT=3Dy + default KUNIT_ALL_TESTS + help + Build focused unit tests for the Open Sensor Fusion core sample + acceptance, direct-read cache, and capability/session handling. The tes= ts + exercise accepted, ignored, and rejected sample handling without + exposing additional production interfaces. + + If unsure, say N. diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensor= fusion/Makefile index b4e03b80cfa4..c2f5ba065a62 100644 --- a/drivers/iio/opensensorfusion/Makefile +++ b/drivers/iio/opensensorfusion/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_OPEN_SENSOR_FUSION) +=3D open-sensor-fusion.o =20 open-sensor-fusion-y :=3D osf_core.o osf_iio.o osf_protocol.o osf_serdev.o= \ osf_stream.o +open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) +=3D osf_core_t= est.o diff --git a/drivers/iio/opensensorfusion/osf_core_test.c b/drivers/iio/ope= nsensorfusion/osf_core_test.c new file mode 100644 index 000000000000..d96753ab2a45 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_core_test.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_protocol.h" +#include "osf_stream.h" + +#define OSF_TEST_SENSOR_INDEX 0 +#define OSF_TEST_UNSUPPORTED_INDEX 7 +#define OSF_TEST_SCALE_NANO 1000000 +#define OSF_TEST_FLOOD_COUNT (OSF_MAX_CAPABILITIES + 4) +#define OSF_TEST_MAX_PAYLOAD_LEN \ + (OSF_SENSOR_SAMPLE_BASE_LEN + OSF_MAX_SAMPLE_CHANNELS * sizeof(__le32)) +#define OSF_TEST_MAX_FRAME_LEN \ + (OSF_FRAME_HEADER_LEN + OSF_CAP_REPORT_BASE_LEN + \ + 3 * OSF_CAP_SENSOR_ENTRY_LEN + OSF_FRAME_CRC_LEN) + +struct osf_test_context { + struct osf_device osf; + struct device *dev; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + bool buffer_enabled; +}; + +struct osf_test_scan_3axis { + s32 values[3]; +}; + +static size_t osf_test_build_frame(u8 *buf, size_t buf_size, + u16 message_type, const u8 *payload, + u32 payload_len, u64 sequence) +{ + size_t frame_len =3D OSF_FRAME_HEADER_LEN + payload_len + + OSF_FRAME_CRC_LEN; + u32 crc; + + if (frame_len > buf_size) + return 0; + + memset(buf, 0, frame_len); + put_unaligned_le32(OSF_FRAME_MAGIC, buf); + buf[4] =3D OSF_PROTOCOL_MAJOR; + buf[5] =3D OSF_PROTOCOL_MINOR; + put_unaligned_le16(OSF_FRAME_HEADER_LEN, buf + 6); + put_unaligned_le16(message_type, buf + 8); + put_unaligned_le32(payload_len, buf + 10); + put_unaligned_le64(sequence, buf + 14); + put_unaligned_le64(sequence * 1000, buf + 22); + memcpy(buf + OSF_FRAME_HEADER_LEN, payload, payload_len); + + crc =3D crc32_le(~0U, buf, OSF_FRAME_HEADER_LEN + payload_len) ^ ~0U; + put_unaligned_le32(crc, buf + OSF_FRAME_HEADER_LEN + payload_len); + + return frame_len; +} + +static size_t osf_test_build_capability_frame(u8 *buf, size_t buf_size, + u64 sequence) +{ + u8 payload[OSF_CAP_REPORT_BASE_LEN + + 2 * OSF_CAP_SENSOR_ENTRY_LEN] =3D { }; + u8 *supported =3D payload + OSF_CAP_REPORT_BASE_LEN; + u8 *unsupported =3D supported + OSF_CAP_SENSOR_ENTRY_LEN; + + put_unaligned_le16(2, payload); + put_unaligned_le16(OSF_SENSOR_ACCELEROMETER, supported); + put_unaligned_le16(OSF_TEST_SENSOR_INDEX, supported + 2); + put_unaligned_le16(3, supported + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, supported + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, supported + 8); + + put_unaligned_le16(OSF_SENSOR_BAROMETER, unsupported); + put_unaligned_le16(OSF_TEST_UNSUPPORTED_INDEX, unsupported + 2); + put_unaligned_le16(1, unsupported + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, unsupported + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, unsupported + 8); + + return osf_test_build_frame(buf, buf_size, OSF_MSG_CAPABILITY_REPORT, + payload, sizeof(payload), sequence); +} + +static size_t osf_test_build_sample_frame(u8 *buf, size_t buf_size, + u16 sensor_type, u16 sensor_index, + u16 channel_count, u16 sample_format, + const s32 *values, u64 sequence) +{ + u8 payload[OSF_TEST_MAX_PAYLOAD_LEN] =3D { }; + u32 payload_len; + + if (!channel_count || channel_count > OSF_MAX_SAMPLE_CHANNELS) + return 0; + + put_unaligned_le16(sensor_type, payload); + put_unaligned_le16(sensor_index, payload + 2); + put_unaligned_le16(channel_count, payload + 4); + put_unaligned_le16(sample_format, payload + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, payload + 8); + + for (u16 i =3D 0; i < channel_count; i++) + put_unaligned_le32(values[i], + payload + OSF_SENSOR_SAMPLE_BASE_LEN + + i * sizeof(__le32)); + + payload_len =3D OSF_SENSOR_SAMPLE_BASE_LEN + + channel_count * sizeof(__le32); + + return osf_test_build_frame(buf, buf_size, OSF_MSG_SENSOR_SAMPLE, + payload, payload_len, sequence); +} + +static size_t osf_test_build_truncated_sample_frame(u8 *buf, size_t buf_si= ze, + u64 sequence) +{ + u8 payload[OSF_SENSOR_SAMPLE_BASE_LEN + sizeof(__le32)] =3D { }; + + put_unaligned_le16(OSF_SENSOR_ACCELEROMETER, payload); + put_unaligned_le16(OSF_TEST_SENSOR_INDEX, payload + 2); + put_unaligned_le16(3, payload + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, payload + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, payload + 8); + put_unaligned_le32(42, payload + OSF_SENSOR_SAMPLE_BASE_LEN); + + return osf_test_build_frame(buf, buf_size, OSF_MSG_SENSOR_SAMPLE, + payload, sizeof(payload), sequence); +} + +/* Keep frame-construction scratch out of the caller's KASAN stack frame. = */ +static noinline int osf_test_submit_sample(struct osf_test_context *ctx, + u16 sensor_type, u16 sensor_index, + u16 channel_count, u16 sample_format, + const s32 *values, u64 sequence) +{ + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t frame_len; + + frame_len =3D osf_test_build_sample_frame(frame, sizeof(frame), + sensor_type, sensor_index, + channel_count, sample_format, + values, sequence); + if (!frame_len) + return -EINVAL; + + return osf_core_receive_frame(&ctx->osf, frame, frame_len); +} + +static int osf_test_read_raw(struct osf_test_context *ctx, + unsigned int channel, int *value) +{ + int value2 =3D 0; + + return ctx->indio_dev->info->read_raw(ctx->indio_dev, + &ctx->indio_dev->channels[channel], + value, &value2, + IIO_CHAN_INFO_RAW); +} + +static int osf_test_remove_scan(struct osf_test_context *ctx, + struct osf_test_scan_3axis *scan) +{ + return iio_pop_from_buffer(ctx->buffer, scan); +} + +static int osf_test_enable_buffer(struct osf_test_context *ctx) +{ + unsigned long *scan_mask; + int ret; + + scan_mask =3D (unsigned long *)ctx->buffer->scan_mask; + for (unsigned int i =3D 0; i < 3; i++) + bitmap_set(scan_mask, ctx->indio_dev->channels[i].scan_index, 1); + ret =3D iio_update_buffers(ctx->indio_dev, ctx->buffer, NULL); + if (!ret) + ctx->buffer_enabled =3D true; + + return ret; +} + +static void osf_test_cleanup(void *data) +{ + struct osf_test_context *ctx =3D data; + + if (ctx->buffer_enabled) { + iio_update_buffers(ctx->indio_dev, NULL, ctx->buffer); + ctx->buffer_enabled =3D false; + } + + osf_core_unregister_iio(&ctx->osf); +} + +static int osf_test_init(struct kunit *test) +{ + struct osf_test_context *ctx; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t frame_len; + int ret; + + ctx =3D kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + test->priv =3D ctx; + ctx->dev =3D kunit_device_register(test, "osf-core"); + if (IS_ERR(ctx->dev)) + return PTR_ERR(ctx->dev); + if (!ctx->dev) + return -ENOMEM; + + osf_core_init(&ctx->osf, ctx->dev); + osf_core_start(&ctx->osf); + ret =3D kunit_add_action_or_reset(test, osf_test_cleanup, ctx); + if (ret) + return ret; + + frame_len =3D osf_test_build_capability_frame(frame, sizeof(frame), 1); + if (!frame_len) + return -EINVAL; + + ret =3D osf_core_receive_frame(&ctx->osf, frame, frame_len); + if (ret !=3D OSF_STREAM_FRAME_HANDLED) + return ret < 0 ? ret : -EINVAL; + if (ctx->osf.iio_dev_count !=3D 1 || + ctx->osf.capability_cache.capability_count !=3D 1) + return -EINVAL; + + ctx->indio_dev =3D ctx->osf.iio_devs[0].indio_dev; + ctx->buffer =3D ctx->indio_dev->buffer; + if (!ctx->buffer) + return -EINVAL; + + return 0; +} + +static void osf_test_expect_direct_sample(struct kunit *test, + struct osf_test_context *ctx, + const s32 *expected) +{ + int value; + int ret; + + for (unsigned int i =3D 0; i < 3; i++) { + value =3D 0; + ret =3D osf_test_read_raw(ctx, i, &value); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, value, expected[i]); + } +} + +static void +osf_rejected_channel_count_preserves_latest_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 baseline_values[] =3D { 11, 22, 33 }; + const s32 rejected_values[] =3D { -999 }; + struct osf_sensor_sample decoded_sample; + struct osf_latest_sample baseline; + struct osf_test_scan_3axis scan; + struct osf_frame decoded_frame; + u8 rejected_frame[OSF_TEST_MAX_FRAME_LEN]; + size_t decoded_len; + size_t frame_len; + u64 baseline_sequence; + int ret; + + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + KUNIT_ASSERT_TRUE(test, iio_buffer_enabled(ctx->indio_dev)); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + baseline_values, 2); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 1U); + osf_test_expect_direct_sample(test, ctx, baseline_values); + + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + + baseline =3D ctx->osf.latest_samples[0]; + baseline_sequence =3D ctx->osf.last_sequence; + frame_len =3D osf_test_build_sample_frame(rejected_frame, + sizeof(rejected_frame), + OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + rejected_values, 3); + KUNIT_ASSERT_NE(test, frame_len, (size_t)0); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_frame(rejected_frame, frame_len, + &decoded_frame, &decoded_len), 0); + KUNIT_ASSERT_EQ(test, decoded_len, frame_len); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_sensor_sample(&decoded_frame, + &decoded_sample), 0); + KUNIT_ASSERT_EQ(test, decoded_sample.channel_count, (u16)1); + ret =3D osf_core_receive_frame(&ctx->osf, rejected_frame, frame_len); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_EQ(test, ctx->osf.latest_sample_count, 1U); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, + sizeof(baseline)); + KUNIT_EXPECT_EQ(test, ctx->osf.last_sequence, baseline_sequence); + osf_test_expect_direct_sample(test, ctx, baseline_values); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); +} + +static void +osf_valid_sample_updates_direct_and_buffer_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 baseline_values[] =3D { 1, 2, 3 }; + const s32 rejected_values[] =3D { -1 }; + const s32 valid_values[] =3D { 101, -202, 303 }; + struct osf_test_scan_3axis scan =3D { }; + int ret; + + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + baseline_values, 2); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + rejected_values, 3); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + valid_values, 4); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + osf_test_expect_direct_sample(test, ctx, valid_values); + KUNIT_ASSERT_EQ(test, ctx->indio_dev->scan_bytes, (int)sizeof(scan)); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + KUNIT_EXPECT_EQ(test, scan.values[0], valid_values[0]); + KUNIT_EXPECT_EQ(test, scan.values[1], valid_values[1]); + KUNIT_EXPECT_EQ(test, scan.values[2], valid_values[2]); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); +} + +static void osf_unregistered_sample_is_ignored_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 baseline_values[] =3D { 10, 20, 30 }; + const s32 ignored_values[] =3D { -10, -20, -30 }; + struct osf_latest_sample baseline; + struct osf_test_scan_3axis scan; + s32 value; + int ret; + + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + baseline_values, 2); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + baseline =3D ctx->osf.latest_samples[0]; + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX + 1, 3, + OSF_SAMPLE_FORMAT_S32, + ignored_values, 3); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_IGNORED); + KUNIT_EXPECT_EQ(test, ctx->osf.iio_dev_count, 1U); + KUNIT_EXPECT_EQ(test, ctx->osf.latest_sample_count, 1U); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, + sizeof(baseline)); + osf_test_expect_direct_sample(test, ctx, baseline_values); + ret =3D osf_core_read_latest_sample(&ctx->osf, + OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX + 1, + 0, &value); + KUNIT_EXPECT_EQ(test, ret, -ENODATA); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); +} + +static void +osf_unaccepted_samples_do_not_exhaust_cache_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 three_values[] =3D { 7, 8, 9 }; + const s32 one_value[] =3D { 42 }; + struct osf_latest_sample *empty_cache; + struct osf_sensor_sample decoded_sample; + struct osf_frame decoded_frame; + u8 malformed_frame[OSF_TEST_MAX_FRAME_LEN]; + size_t decoded_len; + size_t frame_len; + int value; + int ret; + + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + empty_cache =3D kunit_kzalloc(test, sizeof(ctx->osf.latest_samples), GFP_= KERNEL); + KUNIT_ASSERT_NOT_NULL(test, empty_cache); + memcpy(empty_cache, ctx->osf.latest_samples, sizeof(ctx->osf.latest_sampl= es)); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX + i + 1, + 3, OSF_SAMPLE_FORMAT_S32, + three_values, 100 + i); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_IGNORED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_BAROMETER, + OSF_TEST_UNSUPPORTED_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + one_value, 200 + i); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_IGNORED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 1, + OSF_SAMPLE_FORMAT_S32, + one_value, 300 + i); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + frame_len =3D osf_test_build_truncated_sample_frame(malformed_frame, + sizeof(malformed_frame), 400); + KUNIT_ASSERT_NE(test, frame_len, (size_t)0); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_frame(malformed_frame, frame_len, + &decoded_frame, &decoded_len), 0); + KUNIT_ASSERT_EQ(test, decoded_len, frame_len); + KUNIT_ASSERT_EQ(test, + osf_protocol_decode_sensor_sample(&decoded_frame, + &decoded_sample), + -EMSGSIZE); + + for (unsigned int i =3D 0; i < OSF_TEST_FLOOD_COUNT; i++) { + ret =3D osf_core_receive_frame(&ctx->osf, malformed_frame, + frame_len); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + } + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 0U); + + KUNIT_EXPECT_MEMEQ(test, ctx->osf.latest_samples, empty_cache, + sizeof(ctx->osf.latest_samples)); + KUNIT_EXPECT_EQ(test, ctx->osf.last_sequence, (u64)1); + + ret =3D osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER, + OSF_TEST_SENSOR_INDEX, 3, + OSF_SAMPLE_FORMAT_S32, + three_values, 500); + KUNIT_ASSERT_EQ(test, ret, OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, ctx->osf.latest_sample_count, 1U); + + for (unsigned int i =3D 0; i < 3; i++) { + value =3D 0; + ret =3D osf_test_read_raw(ctx, i, &value); + KUNIT_ASSERT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, value, three_values[i]); + } +} + +static void osf_test_unregister_pending(void *data) +{ + osf_core_unregister_iio(data); +} + +static void osf_early_capability_is_owned_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_device *pending; + struct iio_dev *indio_dev; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t len; + int scale, nano, ret; + + pending =3D kunit_kzalloc(test, sizeof(*pending), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pending); + osf_core_init(pending, ctx->dev); + ret =3D kunit_add_action_or_reset(test, osf_test_unregister_pending, pend= ing); + KUNIT_ASSERT_EQ(test, ret, 0); + len =3D osf_test_build_capability_frame(frame, sizeof(frame), 10); + KUNIT_ASSERT_NE(test, len, (size_t)0); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_HANDLED); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 0U); + KUNIT_ASSERT_TRUE(test, pending->capability_cache.valid); + KUNIT_EXPECT_EQ(test, pending->capability_cache.capability_count, (u16)1); + + /* The stream may reuse its storage before probe opens the registration g= ate. */ + memset(frame, 0xa5, sizeof(frame)); + osf_core_start(pending); + KUNIT_ASSERT_EQ(test, pending->iio_dev_count, 1U); + indio_dev =3D pending->iio_devs[0].indio_dev; + ret =3D indio_dev->info->read_raw(indio_dev, &indio_dev->channels[0], + &scale, &nano, IIO_CHAN_INFO_SCALE); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT_PLUS_NANO); + KUNIT_EXPECT_EQ(test, scale, 0); + KUNIT_EXPECT_EQ(test, nano, OSF_TEST_SCALE_NANO); + osf_core_start(pending); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 1U); + len =3D osf_test_build_capability_frame(frame, sizeof(frame), 11); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_IGNORED); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 1U); + KUNIT_EXPECT_EQ(test, pending->latest_sample_count, 0U); +} + +static void osf_early_duplicate_capability_is_rejected_test(struct kunit *= test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_device *pending; + u8 payload[OSF_CAP_REPORT_BASE_LEN + 2 * OSF_CAP_SENSOR_ENTRY_LEN] =3D { = }; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + u8 *entry =3D payload + OSF_CAP_REPORT_BASE_LEN; + size_t len; + int ret; + + pending =3D kunit_kzalloc(test, sizeof(*pending), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pending); + osf_core_init(pending, ctx->dev); + ret =3D kunit_add_action_or_reset(test, osf_test_unregister_pending, pend= ing); + KUNIT_ASSERT_EQ(test, ret, 0); + put_unaligned_le16(2, payload); + put_unaligned_le16(OSF_SENSOR_ACCELEROMETER, entry); + put_unaligned_le16(3, entry + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, entry + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, entry + 8); + memcpy(entry + OSF_CAP_SENSOR_ENTRY_LEN, entry, OSF_CAP_SENSOR_ENTRY_LEN); + len =3D osf_test_build_frame(frame, sizeof(frame), OSF_MSG_CAPABILITY_REP= ORT, + payload, sizeof(payload), 20); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_FALSE(test, pending->capability_cache.valid); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 0U); + + /* A failed early report must not prevent a subsequent valid report. */ + len =3D osf_test_build_capability_frame(frame, sizeof(frame), 21); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(pending, frame, len), + OSF_STREAM_FRAME_HANDLED); + osf_core_start(pending); + KUNIT_EXPECT_EQ(test, pending->iio_dev_count, 1U); +} + +static const struct osf_capability_entry osf_test_accel =3D { + .sensor_type =3D OSF_SENSOR_ACCELEROMETER, + .channel_count =3D 3, + .sample_format =3D OSF_SAMPLE_FORMAT_S32, + .scale_nano =3D OSF_TEST_SCALE_NANO, +}; + +static void osf_test_crc(u8 *frame, size_t len) +{ + put_unaligned_le32(crc32_le(~0U, frame, len - OSF_FRAME_CRC_LEN) ^ ~0U, + frame + len - OSF_FRAME_CRC_LEN); +} + +static size_t osf_test_report(u8 *frame, + const struct osf_capability_entry *entries, u16 count) +{ + u8 payload[OSF_CAP_REPORT_BASE_LEN + 3 * OSF_CAP_SENSOR_ENTRY_LEN] =3D { = }; + + put_unaligned_le16(count, payload); + for (u16 i =3D 0; i < count; i++) { + u8 *p =3D payload + OSF_CAP_REPORT_BASE_LEN + i * OSF_CAP_SENSOR_ENTRY_L= EN; + + put_unaligned_le16(entries[i].sensor_type, p); + put_unaligned_le16(entries[i].sensor_index, p + 2); + put_unaligned_le16(entries[i].channel_count, p + 4); + put_unaligned_le16(entries[i].sample_format, p + 6); + put_unaligned_le32(entries[i].scale_nano, p + 8); + put_unaligned_le32(entries[i].flags, p + 12); + put_unaligned_le32(entries[i].reserved, p + 16); + } + return osf_test_build_frame(frame, OSF_TEST_MAX_FRAME_LEN, + OSF_MSG_CAPABILITY_REPORT, payload, + OSF_CAP_REPORT_BASE_LEN + count * OSF_CAP_SENSOR_ENTRY_LEN, + 10); +} + +static int osf_test_submit_report(struct osf_test_context *ctx, + const struct osf_capability_entry *entries, u16 count) +{ + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t len =3D osf_test_report(frame, entries, count); + + return osf_core_receive_frame(&ctx->osf, frame, len); +} + +static void osf_test_new_session(struct osf_test_context *ctx) +{ + osf_test_cleanup(ctx); + osf_core_init(&ctx->osf, ctx->dev); + osf_core_start(&ctx->osf); + ctx->indio_dev =3D NULL; + ctx->buffer =3D NULL; +} + +static void osf_test_bind_accel(struct kunit *test, struct osf_test_contex= t *ctx) +{ + KUNIT_ASSERT_EQ(test, osf_test_submit_report(ctx, &osf_test_accel, 1), + OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, ctx->osf.iio_dev_count, 1U); + ctx->indio_dev =3D ctx->osf.iio_devs[0].indio_dev; + ctx->buffer =3D ctx->indio_dev->buffer; +} + +static void osf_zero_scale_is_atomic_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_capability_entry entries[] =3D { osf_test_accel, osf_test_acce= l }; + + entries[1].sensor_index =3D 1; + entries[1].scale_nano =3D 0; + /* Even a repeat with a valid prefix must be rejected without a fault. */ + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 2), + OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); + KUNIT_EXPECT_EQ(test, ctx->osf.iio_dev_count, 1U); + osf_test_new_session(ctx); + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 2), + OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_EQ(test, ctx->osf.iio_dev_count, 0U); + KUNIT_EXPECT_FALSE(test, ctx->osf.capability_cache.valid); + osf_test_bind_accel(test, ctx); +} + +static int osf_test_stream_frame(void *context, const u8 *buf, size_t len) +{ + return osf_core_receive_frame(context, buf, len); +} + +static void osf_scale_mismatch_preserves_and_recovers_test(struct kunit *t= est) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 values[] =3D { OSF_FRAME_MAGIC, -22, 33 }; + const s32 next[] =3D { 101, 202, 303 }; + struct osf_latest_sample baseline; + struct osf_test_scan_3axis scan; + struct osf_stream *stream; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t len; + + stream =3D kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + osf_stream_init(stream, osf_test_stream_frame, &ctx->osf); + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + KUNIT_ASSERT_EQ(test, osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETE= R, + 0, 3, OSF_SAMPLE_FORMAT_S32, values, 2), + OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + baseline =3D ctx->osf.latest_samples[0]; + len =3D osf_test_build_sample_frame(frame, sizeof(frame), OSF_SENSOR_ACCE= LEROMETER, + 0, 3, OSF_SAMPLE_FORMAT_S32, values, 3); + put_unaligned_le32(OSF_TEST_SCALE_NANO + 1, frame + OSF_FRAME_HEADER_LEN = + 8); + osf_test_crc(frame, len); + KUNIT_EXPECT_EQ(test, osf_stream_receive_bytes(stream, frame, len), 0); + KUNIT_EXPECT_EQ(test, stream->stats.rejected_frames, 1ULL); + KUNIT_EXPECT_EQ(test, stream->stats.validated_frames, 1ULL); + KUNIT_EXPECT_EQ(test, stream->stats.bad_magic_resyncs, 0ULL); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, sizeof(b= aseline)); + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); + osf_test_expect_direct_sample(test, ctx, values); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); + KUNIT_EXPECT_EQ(test, osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETE= R, + 0, 3, OSF_SAMPLE_FORMAT_S32, next, 4), + OSF_STREAM_FRAME_HANDLED); + osf_test_expect_direct_sample(test, ctx, next); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + KUNIT_EXPECT_MEMEQ(test, scan.values, next, sizeof(next)); +} + +static void osf_test_initial_recovery(struct kunit *test, bool unsupported) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_capability_entry entry =3D osf_test_accel; + + osf_test_new_session(ctx); + entry.sensor_type =3D 0xffff; + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, &entry, unsupported ? 1= : 0), + OSF_STREAM_FRAME_IGNORED); + KUNIT_EXPECT_FALSE(test, ctx->osf.capability_cache.valid); + KUNIT_EXPECT_EQ(test, ctx->osf.iio_dev_count, 0U); + osf_test_bind_accel(test, ctx); + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); +} + +static void osf_initial_empty_recovers_test(struct kunit *test) +{ + osf_test_initial_recovery(test, false); +} + +static void osf_initial_unsupported_recovers_test(struct kunit *test) +{ + osf_test_initial_recovery(test, true); +} + +static void osf_equivalent_reports_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_capability_entry entries[] =3D { osf_test_accel, osf_test_acce= l, + osf_test_accel }; + struct osf_capability_entry swap; + struct iio_dev *first; + + osf_test_new_session(ctx); + entries[1].sensor_type =3D OSF_SENSOR_GYROSCOPE; + KUNIT_ASSERT_EQ(test, osf_test_submit_report(ctx, entries, 2), OSF_STREAM= _FRAME_HANDLED); + first =3D ctx->osf.iio_devs[0].indio_dev; + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 2), OSF_STREAM= _FRAME_IGNORED); + swap =3D entries[0]; + entries[0] =3D entries[1]; + entries[1] =3D swap; + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 2), OSF_STREAM= _FRAME_IGNORED); + entries[0].flags =3D OSF_CAPABILITY_FLAGS_MASK; + entries[0].reserved =3D 0xa5a5; + entries[2].sensor_type =3D 0xffff; + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 3), OSF_STREAM= _FRAME_IGNORED); + /* Unknown flags make the added key unsupported; padding does not. */ + entries[2] =3D osf_test_accel; + entries[2].sensor_index =3D 1; + entries[2].flags =3D BIT(7); + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 3), OSF_STREAM= _FRAME_IGNORED); + KUNIT_EXPECT_EQ(test, ctx->osf.iio_dev_count, 2U); + KUNIT_EXPECT_PTR_EQ(test, ctx->osf.iio_devs[0].indio_dev, first); + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); +} + +static void osf_changed_reports_fault_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 v[] =3D { 11, 22, 33 }; + struct osf_test_scan_3axis scan; + + for (unsigned int change =3D 0; change < 8; change++) { + struct osf_capability_entry entries[] =3D { osf_test_accel, osf_test_acc= el }; + struct osf_latest_sample baseline; + struct iio_dev *child; + u16 count =3D 1; + int value =3D 0; + + osf_test_new_session(ctx); + osf_test_bind_accel(test, ctx); + child =3D ctx->indio_dev; + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + KUNIT_ASSERT_EQ(test, osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMET= ER, + 0, 3, OSF_SAMPLE_FORMAT_S32, v, 2), + OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0); + baseline =3D ctx->osf.latest_samples[0]; + switch (change) { + case 0: + entries[0].scale_nano++; + break; + case 1: + entries[0].channel_count =3D 1; + break; + case 2: + entries[0].sample_format =3D 2; + break; + case 3: + entries[0].sensor_index =3D 1; + break; + case 4: + count =3D 0; + break; + case 5: + entries[0].sensor_type =3D 0xffff; + break; + case 6: + entries[1].sensor_index =3D 1; + count =3D 2; + break; + case 7: + entries[0].flags =3D BIT(7); + break; + } + kunit_info(test, "meaning change=3D%u\n", change); + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, count), + OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_TRUE(test, ctx->osf.session_fault); + KUNIT_EXPECT_EQ(test, ctx->osf.iio_dev_count, 1U); + KUNIT_EXPECT_PTR_EQ(test, ctx->osf.iio_devs[0].indio_dev, child); + KUNIT_EXPECT_EQ(test, osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMET= ER, + 0, 3, OSF_SAMPLE_FORMAT_S32, v, 3), + OSF_STREAM_FRAME_REJECTED); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, sizeof(= baseline)); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); + KUNIT_EXPECT_EQ(test, osf_test_read_raw(ctx, 0, &value), -EPROTO); + /* Equivalent reports do not repair a faulted host session. */ + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, &osf_test_accel, 1), + OSF_STREAM_FRAME_IGNORED); + KUNIT_EXPECT_EQ(test, osf_test_read_raw(ctx, 0, &value), -EPROTO); + } +} + +static void osf_rebind_and_timestamp_restart_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_capability_entry entry =3D osf_test_accel; + const s32 values[] =3D { 101, -202, 303 }; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t len; + + entry.scale_nano++; + KUNIT_ASSERT_EQ(test, osf_test_submit_report(ctx, &entry, 1), OSF_STREAM_= FRAME_REJECTED); + KUNIT_ASSERT_TRUE(test, ctx->osf.session_fault); + osf_test_new_session(ctx); + KUNIT_ASSERT_EQ(test, osf_test_submit_report(ctx, &entry, 1), OSF_STREAM_= FRAME_HANDLED); + ctx->indio_dev =3D ctx->osf.iio_devs[0].indio_dev; + ctx->buffer =3D ctx->indio_dev->buffer; + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); + KUNIT_EXPECT_EQ(test, ctx->osf.latest_sample_count, 0U); + for (unsigned int i =3D 0; i < 2; i++) { + len =3D osf_test_build_sample_frame(frame, sizeof(frame), OSF_SENSOR_ACC= ELEROMETER, + 0, 3, OSF_SAMPLE_FORMAT_S32, values, i ? 0 : 100); + put_unaligned_le32(entry.scale_nano, frame + OSF_FRAME_HEADER_LEN + 8); + osf_test_crc(frame, len); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), + OSF_STREAM_FRAME_HANDLED); + KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, &entry, 1), + OSF_STREAM_FRAME_IGNORED); + } + osf_test_expect_direct_sample(test, ctx, values); + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); +} + +static void osf_reserved_padding_is_ignored_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_capability_entry entry =3D osf_test_accel; + const s32 values[] =3D { 11, 22, 33 }; + u8 status[OSF_DEVICE_STATUS_LEN] =3D { }; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + size_t len; + + osf_test_new_session(ctx); + entry.reserved =3D 0xa5a5a5a5; + len =3D osf_test_report(frame, &entry, 1); + put_unaligned_le32(0xdeadbeef, frame + 34); + put_unaligned_le16(0x1234, frame + OSF_FRAME_HEADER_LEN + 2); + osf_test_crc(frame, len); + KUNIT_ASSERT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), + OSF_STREAM_FRAME_HANDLED); + KUNIT_ASSERT_EQ(test, ctx->osf.iio_dev_count, 1U); + ctx->indio_dev =3D ctx->osf.iio_devs[0].indio_dev; + ctx->buffer =3D ctx->indio_dev->buffer; + len =3D osf_test_build_sample_frame(frame, sizeof(frame), OSF_SENSOR_ACCE= LEROMETER, + 0, 3, OSF_SAMPLE_FORMAT_S32, values, 2); + put_unaligned_le32(0xaabbccdd, frame + OSF_FRAME_HEADER_LEN + 12); + osf_test_crc(frame, len); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), + OSF_STREAM_FRAME_HANDLED); + osf_test_expect_direct_sample(test, ctx, values); + put_unaligned_le32(0x12345678, status + 16); + len =3D osf_test_build_frame(frame, sizeof(frame), OSF_MSG_DEVICE_STATUS, + status, sizeof(status), 3); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), + OSF_STREAM_FRAME_HANDLED); + KUNIT_EXPECT_TRUE(test, ctx->osf.status_cache.valid); +} + +static void osf_bad_crc_bounds_and_extensions_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + const s32 values[] =3D { 11, 22, 33 }; + struct osf_latest_sample baseline; + u8 frame[OSF_TEST_MAX_FRAME_LEN]; + u8 payload[OSF_TEST_MAX_PAYLOAD_LEN + 1] =3D { }; + size_t len; + + KUNIT_ASSERT_EQ(test, osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETE= R, + 0, 3, OSF_SAMPLE_FORMAT_S32, values, 2), + OSF_STREAM_FRAME_HANDLED); + baseline =3D ctx->osf.latest_samples[0]; + len =3D osf_test_report(frame, &osf_test_accel, 1); + frame[34] =3D 1; + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), -EBA= DMSG); + osf_test_crc(frame, len); + put_unaligned_le32(U32_MAX, frame + 10); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), -EMS= GSIZE); + len =3D osf_test_report(frame, &osf_test_accel, 1); + put_unaligned_le16(2, frame + OSF_FRAME_HEADER_LEN); + osf_test_crc(frame, len); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), + OSF_STREAM_FRAME_REJECTED); + /* A CRC-valid trailing byte is not an extension of any fixed baseline ID= . */ + for (u16 type =3D OSF_MSG_SENSOR_SAMPLE; type <=3D OSF_MSG_CAPABILITY_REP= ORT; type++) { + u32 size; + + memset(payload, 0, sizeof(payload)); + if (type =3D=3D OSF_MSG_SENSOR_SAMPLE) { + put_unaligned_le16(OSF_SENSOR_ACCELEROMETER, payload); + put_unaligned_le16(3, payload + 4); + put_unaligned_le16(OSF_SAMPLE_FORMAT_S32, payload + 6); + put_unaligned_le32(OSF_TEST_SCALE_NANO, payload + 8); + size =3D OSF_TEST_MAX_PAYLOAD_LEN; + } else if (type =3D=3D OSF_MSG_DEVICE_STATUS) { + size =3D OSF_DEVICE_STATUS_LEN; + } else { + size =3D OSF_CAP_REPORT_BASE_LEN; + } + len =3D osf_test_build_frame(frame, sizeof(frame), type, payload, size += 1, 3); + KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), + OSF_STREAM_FRAME_REJECTED); + } + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, sizeof(b= aseline)); +} + +struct osf_session_race { + struct osf_test_context *ctx; + /* Model the transport serialization separately from IIO configuration. */ + struct mutex rx_lock; + struct completion started; + atomic_t faulted; + atomic_t rejected; + atomic_t errors; +}; + +static int osf_session_producer(void *data) +{ + struct osf_session_race *race =3D data; + const s32 values[] =3D { 101, 202, 303 }; + u64 sequence =3D 100; + int ret, raw; + + complete(&race->started); + while (!kthread_should_stop()) { + mutex_lock(&race->rx_lock); + ret =3D osf_test_submit_sample(race->ctx, OSF_SENSOR_ACCELEROMETER, + 0, 3, OSF_SAMPLE_FORMAT_S32, values, sequence++); + if (atomic_read(&race->faulted)) { + if (ret =3D=3D OSF_STREAM_FRAME_REJECTED) + atomic_inc(&race->rejected); + else + atomic_inc(&race->errors); + } + mutex_unlock(&race->rx_lock); + ret =3D osf_test_read_raw(race->ctx, 0, &raw); + if (ret !=3D IIO_VAL_INT && ret !=3D -EPROTO && ret !=3D -ENODATA) + atomic_inc(&race->errors); + cond_resched(); + } + return 0; +} + +static void osf_session_fault_buffer_race_test(struct kunit *test) +{ + struct osf_test_context *ctx =3D test->priv; + struct osf_capability_entry entry =3D osf_test_accel; + struct osf_latest_sample baseline; + struct osf_test_scan_3axis scan; + struct osf_session_race *race; + struct task_struct *producer; + int ret, raw; + + race =3D kunit_kzalloc(test, sizeof(*race), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, race); + race->ctx =3D ctx; + mutex_init(&race->rx_lock); + init_completion(&race->started); + KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0); + producer =3D kthread_run(osf_session_producer, race, "osf-session-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, producer); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->started, HZ), 0U= L); + /* Exercise real IIO mlock/buffer_lock while core takes latest_lock/buffe= r_lock. */ + for (unsigned int i =3D 0; i < 100; i++) { + KUNIT_EXPECT_EQ(test, iio_update_buffers(ctx->indio_dev, NULL, ctx->buff= er), 0); + KUNIT_EXPECT_EQ(test, iio_update_buffers(ctx->indio_dev, ctx->buffer, NU= LL), 0); + cond_resched(); + } + mutex_lock(&race->rx_lock); + entry.scale_nano++; + ret =3D osf_test_submit_report(ctx, &entry, 1); + KUNIT_EXPECT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED); + atomic_set(&race->faulted, 1); + baseline =3D ctx->osf.latest_samples[0]; + while (!osf_test_remove_scan(ctx, &scan)) + ; + mutex_unlock(&race->rx_lock); + for (unsigned int i =3D 0; i < 100 && !atomic_read(&race->rejected); i++) + usleep_range(1000, 2000); + kthread_stop(producer); + KUNIT_EXPECT_GT(test, atomic_read(&race->rejected), 0); + KUNIT_EXPECT_EQ(test, atomic_read(&race->errors), 0); + KUNIT_EXPECT_MEMEQ(test, &ctx->osf.latest_samples[0], &baseline, sizeof(b= aseline)); + KUNIT_EXPECT_EQ(test, osf_test_remove_scan(ctx, &scan), -EBUSY); + KUNIT_EXPECT_EQ(test, osf_test_read_raw(ctx, 0, &raw), -EPROTO); + /* Cleanup follows the real producer-stop -> child-unregister ordering. */ + osf_test_new_session(ctx); + osf_test_bind_accel(test, ctx); + KUNIT_EXPECT_FALSE(test, ctx->osf.session_fault); +} + +static struct kunit_case osf_core_test_cases[] =3D { + KUNIT_CASE(osf_zero_scale_is_atomic_test), + KUNIT_CASE(osf_scale_mismatch_preserves_and_recovers_test), + KUNIT_CASE(osf_initial_empty_recovers_test), + KUNIT_CASE(osf_initial_unsupported_recovers_test), + KUNIT_CASE(osf_equivalent_reports_test), + KUNIT_CASE(osf_changed_reports_fault_test), + KUNIT_CASE(osf_rebind_and_timestamp_restart_test), + KUNIT_CASE(osf_reserved_padding_is_ignored_test), + KUNIT_CASE(osf_bad_crc_bounds_and_extensions_test), + KUNIT_CASE(osf_session_fault_buffer_race_test), + + KUNIT_CASE(osf_early_capability_is_owned_test), + KUNIT_CASE(osf_early_duplicate_capability_is_rejected_test), + KUNIT_CASE(osf_rejected_channel_count_preserves_latest_test), + KUNIT_CASE(osf_valid_sample_updates_direct_and_buffer_test), + KUNIT_CASE(osf_unregistered_sample_is_ignored_test), + KUNIT_CASE(osf_unaccepted_samples_do_not_exhaust_cache_test), + { } +}; + +static struct kunit_suite osf_core_test_suite =3D { + .name =3D "osf-core", + .init =3D osf_test_init, + .test_cases =3D osf_core_test_cases, +}; + +kunit_test_suite(osf_core_test_suite); + +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Thu Sep 24 22:56:14 2026 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8F1F352843A for ; Fri, 18 Sep 2026 18:26:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755965; cv=none; b=NRENLYLHIcL5zPxtxazbBU7ERXMBajdjdt+Y2XG1tBbO/XdeSd9ZHmUe0FA7EAxqp3L26Bwj5J/Gg9739oBxeK5acmW/C1DQYodeCBmf62dbpiAHWwg6bEPlVnQOjG3JZvyLmRg7CtpH700ROdbUjWpoP/HWHOro2ew097knMEg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789755965; c=relaxed/simple; bh=uP2zYyrVNhxR0F5wRNx8VTP3WfC+4MKI0Hisfj+rKY4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fE1mFCIjIgcPx7TZjTV5NQkWe0V2d7nltU6tk67fqhAAyrZeGEi7f4p265BRba4yc5LS6sfWAwLCIpwTA1PO6uSthBPSiatiA+I5Ks9rijbM2d5jPb6X3LH3mYZRi7dqHqxx+uBVUz5wHV7JsKPexr2RvrvvVx44NE8kI1uxPxA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=rfw6TOSM; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rfw6TOSM" Received: by mail-pj2-f13.google.com with SMTP id d9443c01a7336-2d747f066d8so3875215ad.1 for ; Fri, 18 Sep 2026 11:26:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789755962; x=1790360762; 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=ezND/MR9Dspsyc6ymR2jshkitQWvrthe2sHCr5Ma1SU=; b=rfw6TOSMZuuXMW2e7aVbmmH85ZFE5l77LhTMd7wt9kxS9aZyId9DjLgDnBhID45giq u6qgWmuGm38PEfTdGqrbEzYn5V4R3BiNUe7UvjfoaJN2wEO6lwEBYacMvLFcnIE8y7hl AiPZbe9U/gJVvjCcxW7a5Jghz+A/5AKzsfy2hyg4IjhPjRRXIIEawODmlQw8kpEsJk1K h1KYwkJdynHYOxq72IKNnD6x8VnfJCdunAQRv62yk/4glpPpQrXFyLBHFpoRLFzSMLJq wLYricIJe2tHIrRJz4Z2UzxizRVpsj5NNlGztPxODOiSyhB5Qcs3TpBGqAtAKEcDdBin O3Rw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789755962; x=1790360762; 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=ezND/MR9Dspsyc6ymR2jshkitQWvrthe2sHCr5Ma1SU=; b=zYhk6DqR8DQIgzthTf5A4dEP61sws+sDuuxRheLw86nHJCAeDa9BF0HSGP4S+e2Bu5 8K3PauGXaQdS7JHs1BoR2XXVyyi2wW/vcGHzLUQMRa7/nUdjBANw+dTBD2Fi6wXByrM/ HSxCZmzo3DsWjPHwueum3MHZe6sWZJpblXM3MQCFXxq2XQJFxXy3YuiNI3yInzrzJwh+ TJ1pYj76SJybDz8vULfuAB2PfymQo718enyA240RnmqmtHfEMXJ5Em4eDoInI/D8fn4c JVu8lg2pYKkrvrBEyCSDovWe23PGymTcNhXekd67fADkGMFEJ7nU535uB/RgvvUG9xgw +OWQ== X-Forwarded-Encrypted: i=1; AKwUvBw6joVuNdj/n+sEBPJpkmN96PpPAXCPf1btStddO4oKqK7yvNT7PKaDMskO7jbCECUMPtOLjvB89CGHhTQ=@vger.kernel.org X-Gm-Message-State: AFuF++n5dMMI0Y/GavfwkhKdTGblmJEp83xK0WWu5b/LQrPDj4Uu04M2 hYO2bGC8/g6JxAMk/4YcZAX/V6u8/8rFUEpWOipTqespv/d5n949DEkUs+yftcfu X-Gm-Gg: AYBFou0BSgF3NDp7Pl7cLkvfM9K0S0DiLsVPROUsItPvbHZC1zQJOme47cn4JKkoQRV MhRDHxeQRPxY6Dgh7q6hoNmsCkzvV/3592/W5rTcvBh7w/FBpgS4nRF6dVzBB5MfVbJlb/5p8t+ PHBMUyP+zanohKETBzCxLDycs9v83s5ycs2QOv3XMl1wgECdhuRlJkgfJlZHtrP2VWsLsJdFvkA yowZ7Xyv/f+74kGw5h1Dy8uv+Lqqi9B2ihBSoRoepuG+zdZcCYhECBBSt63yRez3xwbTeX/gyVB w8njkccfTFDw5PNXMK7RhFuqh7x/gaxPtQNE3aCZWP9WCU4s2c97HvBx0aI6BmJBIvOJ6oBqDRP zNuipJ7GB+HYNTt5n3UtuKLefeJv9a5U6CZ01DmPJ5UeKQNtJGx8rBRK0G8w5jOhQolbOgYxAjp 8xKNL7Rm8I94BahCPROvNoJ4ElXz0kYra133Be0sz44p/gke2YTBGvbI++FpHsN9aHp9g68/xAQ eiQMNx3D3UYer5+E++NngSer0W8wC/j6tFJ8/wGMXtKUZe2rLEFmxOAbJtZDJxqjipz5ByAXHBH Tss+KCf74Fvyfn0QIOg= X-Received: by 2002:a17:902:d487:b0:2dd:c053:b9c9 with SMTP id d9443c01a7336-2ddc053bab9mr8915495ad.26.1789755961647; Fri, 18 Sep 2026 11:26:01 -0700 (PDT) Received: from DESKTOP-G3E0OSP.localdomain ([58.224.60.243]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc16b49casm871895ad.7.2026.09.18.11.25.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:26:00 -0700 (PDT) From: Jinseob Kim To: jic23@kernel.org, linux-iio@vger.kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andriy.shevchenko@intel.com, linux-kernel@vger.kernel.org, rdunlap@infradead.org, joshua.crofts1@gmail.com, u.kleine-koenig@baylibre.com, julianbraha@gmail.com, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, linux-doc@vger.kernel.org Subject: [PATCH v10 8/8] iio: osf: add IIO KUnit tests Date: Sat, 19 Sep 2026 03:24:46 +0900 Message-ID: <59251baa5b1f53a72ac7a09afc334398e2e84db7.1789753020.git.kimjinseob88@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: 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" Add the existing IIO active-scan packing and buffer producer lifetime tests. Extend the test build wiring and help text to cover both suites. This changes no production source. The complete series retains the production behavior of the combined-driver revision, with CRC terminology clarified. Assisted-by: LLM Signed-off-by: Jinseob Kim --- drivers/iio/opensensorfusion/Kconfig | 2 +- drivers/iio/opensensorfusion/Makefile | 2 +- drivers/iio/opensensorfusion/osf_iio_test.c | 325 ++++++++++++++++++++ 3 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 drivers/iio/opensensorfusion/osf_iio_test.c diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorf= usion/Kconfig index 316f33be1327..b4a2cde9645e 100644 --- a/drivers/iio/opensensorfusion/Kconfig +++ b/drivers/iio/opensensorfusion/Kconfig @@ -20,7 +20,7 @@ config OPEN_SENSOR_FUSION_KUNIT_TEST default KUNIT_ALL_TESTS help Build focused unit tests for the Open Sensor Fusion core sample - acceptance, direct-read cache, and capability/session handling. The tes= ts + acceptance, direct-read cache, and IIO buffer paths. The tests exercise accepted, ignored, and rejected sample handling without exposing additional production interfaces. =20 diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensor= fusion/Makefile index c2f5ba065a62..6a28b14cae1f 100644 --- a/drivers/iio/opensensorfusion/Makefile +++ b/drivers/iio/opensensorfusion/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_OPEN_SENSOR_FUSION) +=3D open-sensor-fusion.o =20 open-sensor-fusion-y :=3D osf_core.o osf_iio.o osf_protocol.o osf_serdev.o= \ osf_stream.o -open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) +=3D osf_core_t= est.o +open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) +=3D osf_core_t= est.o osf_iio_test.o diff --git a/drivers/iio/opensensorfusion/osf_iio_test.c b/drivers/iio/open= sensorfusion/osf_iio_test.c new file mode 100644 index 000000000000..63d3ca1af329 --- /dev/null +++ b/drivers/iio/opensensorfusion/osf_iio_test.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "osf_core.h" +#include "osf_iio.h" + +static noinline void osf_test_poison_stack(void) +{ + u8 bytes[1024]; + + memset(bytes, 0xa5, sizeof(bytes)); + barrier_data(bytes); +} + +static void osf_iio_check_scan(struct kunit *test, struct iio_dev *indio_d= ev, + const s32 *values, unsigned int channels, + unsigned int mask, bool ts) +{ + struct iio_buffer *buffer =3D indio_dev->buffer; + unsigned int data_bytes =3D hweight32(mask) * sizeof(s32); + unsigned int ts_offset =3D ALIGN(data_bytes, 8); + unsigned int scan_bytes =3D ts ? ts_offset + 8 : data_bytes; + unsigned int offset =3D 0; + u8 scan[24] __aligned(8); + s64 before, after, timestamp; + int ret; + + bitmap_zero((unsigned long *)buffer->scan_mask, iio_get_masklength(indio_= dev)); + for (unsigned int i =3D 0; i < channels; i++) + if (mask & BIT(i)) + set_bit(i, (unsigned long *)buffer->scan_mask); + buffer->scan_timestamp =3D ts; + ret =3D iio_update_buffers(indio_dev, buffer, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + kunit_info(test, "device=3D%s mask=3D%u ts=3D%u bytes=3D%u\n", + indio_dev->name, mask, ts, scan_bytes); + memset(scan, 0xa5, sizeof(scan)); + before =3D iio_get_time_ns(indio_dev); + osf_test_poison_stack(); + ret =3D osf_iio_push_sample(indio_dev, values, channels); + after =3D iio_get_time_ns(indio_dev); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, buffer->bytes_per_datum, scan_bytes); + ret =3D iio_pop_from_buffer(buffer, scan); + KUNIT_EXPECT_EQ(test, ret, 0); + if (ret) + goto disable; + + for (unsigned int i =3D 0; i < channels; i++) { + if (!(mask & BIT(i))) + continue; + KUNIT_EXPECT_MEMEQ(test, scan + offset, &values[i], sizeof(s32)); + offset +=3D sizeof(s32); + } + if (ts) { + for (unsigned int i =3D data_bytes; i < ts_offset; i++) + KUNIT_EXPECT_EQ(test, scan[i], (u8)0); + memcpy(×tamp, scan + ts_offset, sizeof(timestamp)); + KUNIT_EXPECT_GE(test, timestamp, before); + KUNIT_EXPECT_LE(test, timestamp, after); + } + /* Neither kfifo nor the producer may write outside the consumer stride. = */ + for (unsigned int i =3D scan_bytes; i < sizeof(scan); i++) + KUNIT_EXPECT_EQ(test, scan[i], (u8)0xa5); + +disable: + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), 0); +} + +/* Protect timestamp alignment holes and every supported scan layout. */ +static void osf_iio_scan_bytes_test(struct kunit *test) +{ + static const u16 types[] =3D { + OSF_SENSOR_ACCELEROMETER, OSF_SENSOR_GYROSCOPE, + OSF_SENSOR_MAGNETOMETER, OSF_SENSOR_TEMPERATURE, + }; + const s32 values[] =3D { 101, -202, 303 }; + struct osf_capability_entry entry =3D { + .sample_format =3D OSF_SAMPLE_FORMAT_S32, + .scale_nano =3D 1000000, + }; + struct osf_device *osf; + struct device *dev; + + dev =3D kunit_device_register(test, "osf-iio"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + osf =3D kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, osf); + osf_core_init(osf, dev); + + for (unsigned int t =3D 0; t < ARRAY_SIZE(types); t++) { + unsigned int channels =3D t =3D=3D 3 ? 1 : 3; + struct iio_dev *indio_dev; + int ret; + + entry.sensor_type =3D types[t]; + entry.channel_count =3D channels; + ret =3D osf_iio_register_sensor(dev, &entry, osf, &indio_dev); + KUNIT_ASSERT_EQ(test, ret, 0); + for (unsigned int mask =3D 1; mask < BIT(channels); mask++) + for (unsigned int ts =3D 0; ts < 2; ts++) + osf_iio_check_scan(test, indio_dev, values, channels, mask, ts); + osf_iio_unregister_sensor(indio_dev); + } +} + +struct osf_iio_race { + struct iio_buffer_access_funcs access; + const struct iio_buffer_access_funcs *original; + struct iio_dev *indio_dev; + struct completion entered; + struct completion release; + struct completion config_started; + struct completion config_done; + atomic_t block_store; + atomic_t stores; + atomic_t disabled; + atomic_t bad_store; + int enable_error; + int disable_error; + int config_result; + bool unregister; +}; + +static struct osf_iio_race *osf_iio_race_from_buffer(struct iio_buffer *bu= ffer) +{ + return container_of(buffer->access, struct osf_iio_race, access); +} + +static int osf_iio_test_store(struct iio_buffer *buffer, const void *data) +{ + struct osf_iio_race *race =3D osf_iio_race_from_buffer(buffer); + + if (atomic_xchg(&race->block_store, 0)) { + complete(&race->entered); + if (!wait_for_completion_timeout(&race->release, 5 * HZ)) + return -ETIMEDOUT; + } + if (atomic_read(&race->disabled)) + atomic_inc(&race->bad_store); + atomic_inc(&race->stores); + return race->original->store_to(buffer, data); +} + +static int osf_iio_test_enable(struct iio_buffer *buffer, struct iio_dev *= indio_dev) +{ + struct osf_iio_race *race =3D osf_iio_race_from_buffer(buffer); + + if (race->enable_error) + return race->enable_error; + atomic_set(&race->disabled, 0); + return 0; +} + +static int osf_iio_test_disable(struct iio_buffer *buffer, struct iio_dev = *indio_dev) +{ + struct osf_iio_race *race =3D osf_iio_race_from_buffer(buffer); + + atomic_set(&race->disabled, 1); + return race->disable_error; +} + +static int osf_iio_test_producer(void *data) +{ + struct osf_iio_race *race =3D data; + const s32 values[] =3D { 101, -202, 303 }; + + while (!kthread_should_stop()) { + osf_iio_push_sample(race->indio_dev, values, ARRAY_SIZE(values)); + cond_resched(); + } + return 0; +} + +static int osf_iio_test_configure(void *data) +{ + struct osf_iio_race *race =3D data; + + complete(&race->config_started); + if (race->unregister) { + osf_iio_unregister_sensor(race->indio_dev); + race->config_result =3D 0; + } else { + race->config_result =3D iio_update_buffers(race->indio_dev, NULL, + race->indio_dev->buffer); + } + complete(&race->config_done); + while (!kthread_should_stop()) + msleep(20); + return 0; +} + +static void osf_iio_expect_quiesce(struct kunit *test, struct osf_iio_race= *race) +{ + struct task_struct *config; + unsigned long waited; + + config =3D kthread_run(osf_iio_test_configure, race, "osf-cfg-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, config); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->config_started, = HZ), 0UL); + waited =3D wait_for_completion_timeout(&race->config_done, msecs_to_jiffi= es(20)); + KUNIT_EXPECT_EQ(test, waited, 0UL); + complete(&race->release); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->config_done, HZ)= , 0UL); + kthread_stop(config); + KUNIT_EXPECT_EQ(test, race->config_result, 0); +} + +/* Run real IIO configuration and kfifo code against an independent produc= er. */ +static void osf_iio_buffer_lifetime_test(struct kunit *test) +{ + struct osf_capability_entry entry =3D { + .sensor_type =3D OSF_SENSOR_ACCELEROMETER, + .channel_count =3D 3, + .sample_format =3D OSF_SAMPLE_FORMAT_S32, + .scale_nano =3D 1000000, + }; + const s32 values[] =3D { 101, -202, 303 }; + struct task_struct *producer; + struct osf_iio_race *race; + struct osf_device *osf; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + struct device *dev; + int stores, ret; + + dev =3D kunit_device_register(test, "osf-race"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + osf =3D kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL); + race =3D kunit_kzalloc(test, sizeof(*race), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, osf); + KUNIT_ASSERT_NOT_NULL(test, race); + osf_core_init(osf, dev); + ret =3D osf_iio_register_sensor(dev, &entry, osf, &indio_dev); + KUNIT_ASSERT_EQ(test, ret, 0); + buffer =3D indio_dev->buffer; + race->indio_dev =3D indio_dev; + race->original =3D buffer->access; + race->access =3D *buffer->access; + race->access.store_to =3D osf_iio_test_store; + race->access.enable =3D osf_iio_test_enable; + race->access.disable =3D osf_iio_test_disable; + buffer->access =3D &race->access; + init_completion(&race->entered); + init_completion(&race->release); + init_completion(&race->config_started); + init_completion(&race->config_done); + atomic_set(&race->disabled, 1); + set_bit(0, (unsigned long *)buffer->scan_mask); + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + + atomic_set(&race->block_store, 1); + producer =3D kthread_run(osf_iio_test_producer, race, "osf-rx-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, producer); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->entered, HZ), 0U= L); + osf_iio_expect_quiesce(test, race); + + for (unsigned int i =3D 0; i < 100; i++) { + bitmap_zero((unsigned long *)buffer->scan_mask, iio_get_masklength(indio= _dev)); + *(unsigned long *)buffer->scan_mask =3D (i % 7) + 1; + buffer->scan_timestamp =3D i & 1; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + cond_resched(); + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), 0); + } + + race->enable_error =3D -EIO; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), -EIO); + stores =3D atomic_read(&race->stores); + KUNIT_EXPECT_EQ(test, osf_iio_push_sample(indio_dev, values, 3), 0); + KUNIT_EXPECT_EQ(test, atomic_read(&race->stores), stores); + race->enable_error =3D 0; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + race->disable_error =3D -EIO; + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), -EIO); + stores =3D atomic_read(&race->stores); + KUNIT_EXPECT_EQ(test, osf_iio_push_sample(indio_dev, values, 3), 0); + KUNIT_EXPECT_EQ(test, atomic_read(&race->stores), stores); + race->disable_error =3D 0; + + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0); + /* Retain ownership while testing unregister against an admitted push. */ + get_device(&indio_dev->dev); + reinit_completion(&race->entered); + reinit_completion(&race->release); + reinit_completion(&race->config_started); + reinit_completion(&race->config_done); + atomic_set(&race->block_store, 1); + KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->entered, HZ), 0U= L); + race->unregister =3D true; + osf_iio_expect_quiesce(test, race); + kthread_stop(producer); + KUNIT_EXPECT_EQ(test, atomic_read(&race->bad_store), 0); + KUNIT_EXPECT_GT(test, atomic_read(&race->stores), 0); + buffer->access =3D race->original; + put_device(&indio_dev->dev); +} + +static struct kunit_case osf_iio_test_cases[] =3D { + KUNIT_CASE(osf_iio_scan_bytes_test), + KUNIT_CASE(osf_iio_buffer_lifetime_test), + { } +}; + +static struct kunit_suite osf_iio_test_suite =3D { + .name =3D "osf-iio", + .test_cases =3D osf_iio_test_cases, +}; + +kunit_test_suite(osf_iio_test_suite); + +MODULE_LICENSE("GPL"); --=20 2.43.0