* [PATCH v10 0/8] iio: add Open Sensor Fusion UART support
@ 2026-09-18 18:24 Jinseob Kim
2026-09-18 18:24 ` [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device Jinseob Kim
` (8 more replies)
0 siblings, 9 replies; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
Specification status: OSF-D2H 0.0 spec-1 has completed project technical
stability review, has been adopted by the project owner, and is published
as a fixed specification.
Canonical specification:
https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/spec/osf-d2h-0.0.md
Errata process:
https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/errata/README.md
Current adoption/publication record:
https://github.com/opensensorfusion/opensensorfusion-protocol/blob/0cabccf63ac01d0eb82dd9882739ff58eed4a76c/reviews/publication-record-20260918.md
The specification was frozen before adoption/publication, so its embedded
status snapshot is intentionally historical. The dated publication record
above establishes the current adopted/public state. This is project review
and adoption, not an external maintainer approval.
This series adds the Open Sensor Fusion UART receive path and IIO devices
discovered from capability reports. It exposes accelerometer, gyroscope,
magnetometer and temperature data through RAW/SCALE and buffered scans.
Device Tree describes the sensor hub; its individual streams are discovered
at runtime.
The receiver validates supported descriptors and sample scales, keeps
discovery open after empty or unsupported initial inventories, and checks
repeated inventories before allowing new data to use registered metadata.
A supported descriptor changing meaning faults the session until an
explicit teardown and rebind. Existing v9 cache, scan-layout and buffer
lifetime fixes are retained.
Based on Jonathan Cameron's IIO testing branch:
69fa76f0af3414cc189c3b0b807cb59e327ecc00
Changes since v9:
- Publish and reference the reviewed/adopted OSF-D2H 0.0 specification,
errata process and dated publication evidence.
- Tolerate reserved padding, validate descriptor/sample scales, keep
discovery open after empty/unsupported reports, and compare repeated
capability reports.
- Fail closed on changed descriptor meaning instead of publishing data
with stale metadata.
- Add focused KUnit coverage for these lifecycle and validation cases.
- Use managed UART/IIO/power teardown and dev_warn_probe() for the
controller baud-rate warning, addressing Andy's probe-path feedback.
- Split the former combined UART/core/IIO driver patch into transport/core,
IIO registration, core KUnit, and IIO KUnit patches following review.
- Use validated/unvalidated terminology for framing/CRC results throughout
the stream/core/transport code, tests and diagnostics. CRC detects
accidental corruption; it does not provide cryptographic authentication.
Prior validation on the identical source tree:
identical source tree evidence reused; no builds rerun for DCO packaging.
- All eight intermediate apply/config/relevant builds.
- Independent transport/core and IIO module link/MODPOST.
- GCC/Clang W=1 vmlinux and modules.
- GCC and Clang KUnit: core 16 + IIO 2, all 18 pass in each run.
- Core-only intermediate KUnit: all 16 pass.
- ARM64 Image, selected OSF module and Pi4 DTB.
- Targeted DT binding/style and IIO documentation.
The terminology-only diff was verified mechanically. Wire semantics and
data-path behavior are unchanged; the receive diagnostic key is validated=.
Hardware testing was not repeated for this terminology revision or this
post-DCO packaging audit; previous hardware evidence remains historical.
Human DCO is complete. No email has been sent.
Jinseob Kim (8):
dt-bindings: iio: add Open Sensor Fusion device
Documentation: iio: add Open Sensor Fusion driver overview
iio: osf: add protocol decoding
iio: osf: add validated stream parser
iio: osf: add UART transport and core receive path
iio: osf: add IIO devices from capability reports
iio: osf: add core KUnit tests
iio: osf: add IIO KUnit tests
.../bindings/iio/opensensorfusion,osf.yaml | 52 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
Documentation/iio/index.rst | 1 +
Documentation/iio/open-sensor-fusion.rst | 77 ++
MAINTAINERS | 8 +
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/opensensorfusion/Kconfig | 27 +
drivers/iio/opensensorfusion/Makefile | 7 +
drivers/iio/opensensorfusion/osf_core.c | 465 ++++++++
drivers/iio/opensensorfusion/osf_core.h | 75 ++
drivers/iio/opensensorfusion/osf_core_test.c | 1046 +++++++++++++++++
drivers/iio/opensensorfusion/osf_iio.c | 338 ++++++
drivers/iio/opensensorfusion/osf_iio.h | 22 +
drivers/iio/opensensorfusion/osf_iio_test.c | 325 +++++
drivers/iio/opensensorfusion/osf_protocol.c | 215 ++++
drivers/iio/opensensorfusion/osf_protocol.h | 101 ++
drivers/iio/opensensorfusion/osf_serdev.c | 148 +++
drivers/iio/opensensorfusion/osf_stream.c | 231 ++++
drivers/iio/opensensorfusion/osf_stream.h | 53 +
20 files changed, 3195 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
create mode 100644 Documentation/iio/open-sensor-fusion.rst
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_core_test.c
create mode 100644 drivers/iio/opensensorfusion/osf_iio.c
create mode 100644 drivers/iio/opensensorfusion/osf_iio.h
create mode 100644 drivers/iio/opensensorfusion/osf_iio_test.c
create mode 100644 drivers/iio/opensensorfusion/osf_protocol.c
create mode 100644 drivers/iio/opensensorfusion/osf_protocol.h
create mode 100644 drivers/iio/opensensorfusion/osf_serdev.c
create mode 100644 drivers/iio/opensensorfusion/osf_stream.c
create mode 100644 drivers/iio/opensensorfusion/osf_stream.h
base-commit: 69fa76f0af3414cc189c3b0b807cb59e327ecc00
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 1:14 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview Jinseob Kim
` (7 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <conor.dooley@microchip.com>
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
.../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.yaml 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 <kimjinseob88@gmail.com>
+
+description: |
+ This binding documents the generic Open Sensor Fusion host interface. Open
+ Sensor Fusion is a sensor aggregation hub. The hub exposes an OSF protocol
+ data stream over its host interface and reports capabilities and samples for
+ multiple sensor classes. The actual sensor channels are discovered at runtime
+ from OSF capability reports instead of describing them in Device Tree. The
+ 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 = "opensensorfusion,osf";
+ vcc-supply = <&vcc_sensor>;
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/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/
+OPEN SENSOR FUSION
+M: Jinseob Kim <kimjinseob88@gmail.com>
+S: Maintained
+F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
+K: opensensorfusion
+
OPENCOMPUTE PTP CLOCK DRIVER
M: Vadim Fedorenko <vadim.fedorenko@linux.dev>
L: netdev@vger.kernel.org
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
2026-09-18 18:24 ` [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 1:12 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 3/8] iio: osf: add protocol decoding Jinseob Kim
` (6 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <kimjinseob88@gmail.com>
---
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/open-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
+==================
+
+Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO driver
+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, compatibility
+rules, reserved fields and physical units. This document describes the Linux
+receiver profile and its mapping to IIO.
+
+.. _specification: https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/spec/osf-d2h-0.0.md
+
+Supported receive profile
+-------------------------
+
+The driver supports the fixed OSF0 frame layout with protocol major version 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 skipped
+individually. A CRC-valid application rejection consumes the complete frame;
+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 unit.
+Buffered samples use signed 32-bit values with native CPU endianness. Active
+channels are packed into the scan, with initialized padding before an optional
+64-bit timestamp.
+
+Discovery and session lifetime
+------------------------------
+
+The driver validates all supported descriptors before registering IIO devices.
+An empty or entirely unsupported initial report leaves discovery open for a
+later supported report. Supported descriptors must advertise a nonzero scale.
+A sample's channel count, format and scale must match its registered descriptor
+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 entry
+order. Reserved padding, advisory flags and unsupported additions do not change
+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 starts
+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 discontinuities
+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 <kimjinseob88@gmail.com>
S: Maintained
F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
+F: Documentation/iio/open-sensor-fusion.rst
K: opensensorfusion
OPENCOMPUTE PTP CLOCK DRIVER
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 3/8] iio: osf: add protocol decoding
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
2026-09-18 18:24 ` [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device Jinseob Kim
2026-09-18 18:24 ` [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 1:27 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 4/8] iio: osf: add validated stream parser Jinseob Kim
` (5 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <kimjinseob88@gmail.com>
---
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 <kimjinseob88@gmail.com>
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
OPENCOMPUTE PTP CLOCK DRIVER
diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/opensensorfusion/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 <linux/bits.h>
+#include <linux/crc32.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#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 >= OSF_SENSOR_ACCELEROMETER &&
+ sensor_type <= 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) != OSF_FRAME_MAGIC)
+ return -EPROTO;
+
+ if (get_unaligned_le16(buf + 6) != OSF_FRAME_HEADER_LEN)
+ return -EPROTO;
+
+ payload_len = get_unaligned_le32(buf + 10);
+ if (payload_len > len - OSF_FRAME_MIN_LEN)
+ return -EMSGSIZE;
+
+ total_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
+ expected_crc = osf_crc32_ieee(buf, OSF_FRAME_HEADER_LEN + payload_len);
+ actual_crc = get_unaligned_le32(buf + OSF_FRAME_HEADER_LEN + payload_len);
+
+ if (actual_crc != expected_crc)
+ return -EBADMSG;
+
+ frame->protocol_major = buf[4];
+ frame->protocol_minor = buf[5];
+ frame->message_type = get_unaligned_le16(buf + 8);
+ frame->payload_len = payload_len;
+ frame->sequence = get_unaligned_le64(buf + 14);
+ frame->timestamp_us = get_unaligned_le64(buf + 22);
+ frame->flags = get_unaligned_le32(buf + 30);
+ frame->reserved = get_unaligned_le32(buf + 34);
+ frame->payload = buf + OSF_FRAME_HEADER_LEN;
+ frame->crc = actual_crc;
+ *frame_len = 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 != OSF_MSG_SENSOR_SAMPLE)
+ return -EPROTO;
+
+ if (frame->payload_len < OSF_SENSOR_SAMPLE_BASE_LEN)
+ return -EMSGSIZE;
+
+ payload = frame->payload;
+ sensor_type = get_unaligned_le16(payload);
+ channel_count = get_unaligned_le16(payload + 4);
+ sample_format = get_unaligned_le16(payload + 6);
+
+ if (!osf_sensor_type_valid(sensor_type))
+ return -EPROTO;
+
+ if (!channel_count)
+ return -EPROTO;
+
+ if (sample_format != OSF_SAMPLE_FORMAT_S32)
+ return -EPROTO;
+
+ expected_len = OSF_SENSOR_SAMPLE_BASE_LEN + channel_count * sizeof(__le32);
+ if (frame->payload_len != expected_len)
+ return -EMSGSIZE;
+
+ *sample = (struct osf_sensor_sample) {
+ .sensor_type = sensor_type,
+ .sensor_index = get_unaligned_le16(payload + 2),
+ .channel_count = channel_count,
+ .sample_format = sample_format,
+ .scale_nano = get_unaligned_le32(payload + 8),
+ .samples = payload + OSF_SENSOR_SAMPLE_BASE_LEN,
+ };
+
+ return 0;
+}
+
+int osf_protocol_sensor_sample_value(const struct osf_sensor_sample *sample,
+ u16 index, s32 *value)
+{
+ if (!sample || !sample->samples || !value)
+ return -EINVAL;
+
+ if (index >= sample->channel_count)
+ return -ERANGE;
+
+ /* Samples are little-endian two's-complement signed values. */
+ *value = 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 != OSF_MSG_DEVICE_STATUS)
+ return -EPROTO;
+
+ if (frame->payload_len != OSF_DEVICE_STATUS_LEN)
+ return -EMSGSIZE;
+
+ payload = frame->payload;
+ *status = (struct osf_device_status) {
+ .uptime_s = get_unaligned_le32(payload),
+ .status_flags = get_unaligned_le32(payload + 4),
+ .error_flags = get_unaligned_le32(payload + 8),
+ .dropped_frames = 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 != OSF_MSG_CAPABILITY_REPORT)
+ return -EPROTO;
+
+ if (frame->payload_len < OSF_CAP_REPORT_BASE_LEN)
+ return -EMSGSIZE;
+
+ payload = frame->payload;
+ capability_count = get_unaligned_le16(payload);
+
+ expected_len = OSF_CAP_REPORT_BASE_LEN +
+ capability_count * OSF_CAP_SENSOR_ENTRY_LEN;
+ if (frame->payload_len != expected_len)
+ return -EMSGSIZE;
+
+ *report = (struct osf_capability_report) {
+ .capability_count = capability_count,
+ .entries = 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 >= report->capability_count)
+ return -ERANGE;
+
+ payload = report->entries + index * OSF_CAP_SENSOR_ENTRY_LEN;
+ *entry = (struct osf_capability_entry) {
+ .sensor_type = get_unaligned_le16(payload),
+ .sensor_index = get_unaligned_le16(payload + 2),
+ .channel_count = get_unaligned_le16(payload + 4),
+ .sample_format = get_unaligned_le16(payload + 6),
+ .scale_nano = get_unaligned_le32(payload + 8),
+ .flags = get_unaligned_le32(payload + 12),
+ .reserved = get_unaligned_le32(payload + 16),
+ };
+
+ return 0;
+}
diff --git a/drivers/iio/opensensorfusion/osf_protocol.h b/drivers/iio/opensensorfusion/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 <linux/bits.h>
+#include <linux/types.h>
+
+#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 = 0x0001,
+ OSF_MSG_DEVICE_STATUS = 0x0002,
+ OSF_MSG_CAPABILITY_REPORT = 0x0003,
+};
+
+enum osf_sensor_type {
+ OSF_SENSOR_ACCELEROMETER = 0x0001,
+ OSF_SENSOR_GYROSCOPE = 0x0002,
+ OSF_SENSOR_MAGNETOMETER = 0x0003,
+ OSF_SENSOR_BAROMETER = 0x0004,
+ OSF_SENSOR_TEMPERATURE = 0x0005,
+ OSF_SENSOR_HUMIDITY = 0x0006,
+ OSF_SENSOR_AMBIENT_LIGHT = 0x0007,
+ OSF_SENSOR_PROXIMITY = 0x0008,
+};
+
+enum osf_sample_format {
+ OSF_SAMPLE_FORMAT_S32 = 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 *sample,
+ u16 index, s32 *value);
+
+#endif
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 4/8] iio: osf: add validated stream parser
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
` (2 preceding siblings ...)
2026-09-18 18:24 ` [PATCH v10 3/8] iio: osf: add protocol decoding Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 1:30 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 5/8] iio: osf: add UART transport and core receive path Jinseob Kim
` (4 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <kimjinseob88@gmail.com>
---
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
OPENCOMPUTE PTP CLOCK DRIVER
diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opensensorfusion/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 <linux/errno.h>
+#include <linux/minmax.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#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 >= stream->len) {
+ stream->len = 0;
+ return;
+ }
+
+ memmove(stream->buf, stream->buf + count, stream->len - count);
+ stream->len -= 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 = 0; i < len; i++) {
+ if (buf[i] != (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 = stream->len;
+ size_t keep_len;
+
+ for (size_t i = 0; i + OSF_STREAM_MAGIC_LEN <= stream->len; i++) {
+ if (get_unaligned_le32(stream->buf + i) == 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 = min(stream->len, OSF_STREAM_MAGIC_LEN - 1);
+ while (keep_len) {
+ size_t offset = 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 = 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 = 0;
+
+ while (stream->len) {
+ discarded = osf_stream_discard_to_magic(stream);
+ if (discarded) {
+ stream->stats.bad_magic_resyncs++;
+ stream->stats.dropped_bytes += discarded;
+ if (!first_err)
+ first_err = -EPROTO;
+ }
+
+ if (!stream->len)
+ break;
+
+ if (stream->len < OSF_FRAME_HEADER_LEN)
+ break;
+
+ if (get_unaligned_le16(stream->buf + 6) != OSF_FRAME_HEADER_LEN) {
+ stream->stats.dropped_bytes++;
+ osf_stream_drop_invalid_head(stream);
+ if (!first_err)
+ first_err = -EPROTO;
+ continue;
+ }
+
+ payload_len = 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 = -EMSGSIZE;
+ continue;
+ }
+
+ frame_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
+ if (stream->len < frame_len)
+ break;
+
+ frame_result = stream->receive_frame(stream->frame_context,
+ stream->buf, frame_len);
+ if (frame_result < 0) {
+ if (frame_result == -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 = 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 = -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 = receive_frame;
+ stream->frame_context = frame_context;
+ stream->len = 0;
+ memset(&stream->stats, 0, sizeof(stream->stats));
+}
+
+void osf_stream_reset(struct osf_stream *stream)
+{
+ if (!stream)
+ return;
+
+ stream->len = 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 = 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 = OSF_STREAM_MAX_FRAME_LEN - stream->len;
+ if (!space) {
+ stream->stats.dropped_bytes++;
+ osf_stream_discard(stream, 1);
+ if (!first_err)
+ first_err = -EMSGSIZE;
+ continue;
+ }
+
+ copy_len = min(len, space);
+ memcpy(stream->buf + stream->len, buf, copy_len);
+ stream->len += copy_len;
+ buf += copy_len;
+ len -= copy_len;
+
+ ret = osf_stream_process(stream);
+ if (ret && !first_err)
+ first_err = ret;
+ }
+
+ return first_err;
+}
diff --git a/drivers/iio/opensensorfusion/osf_stream.h b/drivers/iio/opensensorfusion/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 <linux/types.h>
+
+#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 not
+ * be validated and the parser may perform one-byte resynchronization.
+ * Every nonnegative result must be one of these values. Such a result means
+ * 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
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 5/8] iio: osf: add UART transport and core receive path
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
` (3 preceding siblings ...)
2026-09-18 18:24 ` [PATCH v10 4/8] iio: osf: add validated stream parser Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 1:40 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 6/8] iio: osf: add IIO devices from capability reports Jinseob Kim
` (3 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <kimjinseob88@gmail.com>
---
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 <kimjinseob88@gmail.com>
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
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 += light/
obj-y += magnetometer/
obj-y += multiplexer/
obj-y += orientation/
+obj-y += opensensorfusion/
obj-y += position/
obj-y += potentiometer/
obj-y += potentiostat/
diff --git a/drivers/iio/opensensorfusion/Kconfig b/drivers/iio/opensensorfusion/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/opensensorfusion/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) += open-sensor-fusion.o
+
+open-sensor-fusion-y := osf_core.o osf_protocol.o osf_serdev.o osf_stream.o
diff --git a/drivers/iio/opensensorfusion/osf_core.c b/drivers/iio/opensensorfusion/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 <linux/device.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#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 = (struct osf_device) {
+ .dev = 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 = 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 = (struct osf_status_cache) {
+ .uptime_s = status.uptime_s,
+ .status_flags = status.status_flags,
+ .error_flags = status.error_flags,
+ .dropped_frames = status.dropped_frames,
+ .sequence = frame->sequence,
+ .valid = true,
+ };
+ osf->last_sequence = frame->sequence;
+
+ return OSF_STREAM_FRAME_HANDLED;
+}
+
+int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len)
+{
+ struct osf_frame frame;
+ size_t frame_len;
+ int ret;
+
+ ret = osf_protocol_decode_frame(buf, len, &frame, &frame_len);
+ if (ret)
+ return ret;
+
+ if (frame_len != len)
+ return -EMSGSIZE;
+
+ if (frame.protocol_major != 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 = osf_core_handle_device_status(osf, &frame);
+ break;
+ default:
+ if (frame.message_type >= OSF_RESERVED_MSG_FIRST &&
+ frame.message_type <= 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 >= 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/opensensorfusion/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 <linux/types.h>
+
+#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 len);
+
+#endif
diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opensensorfusion/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 <linux/cleanup.h>
+#include <linux/device-id/of.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/serdev.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#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 len)
+{
+ struct osf_device *osf = 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 = serdev_device_get_drvdata(serdev);
+ const struct osf_stream_stats *stats;
+ u64 validated_before;
+ int ret;
+
+ guard(mutex)(&osf_uart->rx_lock);
+ validated_before = osf_uart->stream.stats.validated_frames;
+ ret = osf_stream_receive_bytes(&osf_uart->stream, buf, count);
+ stats = &osf_uart->stream.stats;
+
+ if (ret || stats->validated_frames != validated_before)
+ dev_dbg_ratelimited(&serdev->dev,
+ "rx count=%zu validated=%llu handled=%llu ignored=%llu rejected=%llu bad_magic=%llu bad_crc=%llu dropped=%llu ret=%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 = {
+ .receive_buf = osf_serdev_receive_buf,
+};
+
+static void osf_serdev_release(void *data)
+{
+ struct osf_serdev *osf_uart = 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 = &serdev->dev;
+ struct osf_serdev *osf_uart;
+ unsigned int baudrate;
+ int ret;
+
+ osf_uart = devm_kzalloc(dev, sizeof(*osf_uart), GFP_KERNEL);
+ if (!osf_uart)
+ return -ENOMEM;
+
+ osf_uart->vcc = 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 = 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 = serdev_device_open(serdev);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, osf_serdev_release, osf_uart);
+ if (ret)
+ return ret;
+
+ baudrate = serdev_device_set_baudrate(serdev, OSF_SERDEV_BAUD);
+ if (baudrate != 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 = regulator_enable(osf_uart->vcc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable vcc regulator\n");
+ osf_uart->vcc_enabled = true;
+
+ return 0;
+}
+
+static const struct of_device_id osf_serdev_of_match[] = {
+ { .compatible = "opensensorfusion,osf" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, osf_serdev_of_match);
+
+static struct serdev_device_driver osf_serdev_driver = {
+ .probe = osf_serdev_probe,
+ .driver = {
+ .name = "open-sensor-fusion-uart",
+ .of_match_table = osf_serdev_of_match,
+ },
+};
+module_serdev_device_driver(osf_serdev_driver);
+
+MODULE_DESCRIPTION("Open Sensor Fusion UART receiver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 6/8] iio: osf: add IIO devices from capability reports
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
` (4 preceding siblings ...)
2026-09-18 18:24 ` [PATCH v10 5/8] iio: osf: add UART transport and core receive path Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 2:03 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 7/8] iio: osf: add core KUnit tests Jinseob Kim
` (2 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <kimjinseob88@gmail.com>
---
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/opensensorfusion/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
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.
- 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/opensensorfusion/Makefile
index 940c82eddc2e..b4e03b80cfa4 100644
--- a/drivers/iio/opensensorfusion/Makefile
+++ b/drivers/iio/opensensorfusion/Makefile
@@ -2,4 +2,5 @@
obj-$(CONFIG_OPEN_SENSOR_FUSION) += open-sensor-fusion.o
-open-sensor-fusion-y := osf_core.o osf_protocol.o osf_serdev.o osf_stream.o
+open-sensor-fusion-y := 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/opensensorfusion/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
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/errno.h>
+#include <linux/string.h>
#include <linux/types.h>
#include "osf_core.h"
+#include "osf_iio.h"
#include "osf_stream.h"
#define OSF_RESERVED_MSG_FIRST 0x7f00
@@ -16,6 +19,247 @@ void osf_core_init(struct osf_device *osf, struct device *dev)
*osf = (struct osf_device) {
.dev = dev,
};
+ mutex_init(&osf->latest_lock);
+}
+
+void osf_core_unregister_iio(struct osf_device *osf)
+{
+ for (unsigned int i = 0; i < osf->iio_dev_count; i++)
+ osf_iio_unregister_sensor(osf->iio_devs[i].indio_dev);
+
+ osf->iio_dev_count = 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 = 0; i < osf->iio_dev_count; i++) {
+ binding = &osf->iio_devs[i];
+ if (binding->sensor_type == sensor_type &&
+ binding->sensor_index == 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 = 0; i < osf->latest_sample_count; i++) {
+ latest = &osf->latest_samples[i];
+ if (latest->sensor_type == sensor_type &&
+ latest->sensor_index == sensor_index)
+ return latest;
+ }
+
+ if (osf->latest_sample_count >= 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 == 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 = 0; i < cache->capability_count; i++) {
+ const struct osf_capability_entry *entry = &cache->entries[i];
+
+ if (entry->sensor_type == sensor_type &&
+ entry->sensor_index == 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 != new->capability_count)
+ return false;
+
+ for (u16 i = 0; i < old->capability_count; i++) {
+ const struct osf_capability_entry *entry = &old->entries[i];
+ const struct osf_capability_entry *other;
+
+ other = 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 != entry->channel_count ||
+ other->sample_format != entry->sample_format ||
+ other->scale_nano != entry->scale_nano)
+ return false;
+ }
+
+ return true;
+}
+
+static bool osf_core_capability_is_duplicate(const struct osf_capability_cache *cache,
+ u16 index)
+{
+ const struct osf_capability_entry *entry = &cache->entries[index];
+
+ for (u16 i = 0; i < index; i++) {
+ if (cache->entries[i].sensor_type == entry->sensor_type &&
+ cache->entries[i].sensor_index == 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 = 0; i < cache->capability_count; i++) {
+ ret = osf_iio_register_sensor(osf->dev, &cache->entries[i],
+ osf, &indio_dev);
+ if (ret)
+ goto err_unregister;
+
+ osf->iio_devs[osf->iio_dev_count++] = (struct osf_iio_binding) {
+ .sensor_type = cache->entries[i].sensor_type,
+ .sensor_index = cache->entries[i].sensor_index,
+ .indio_dev = 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 = true;
+ if (!osf->capability_cache.valid ||
+ !osf->capability_cache.capability_count)
+ return;
+
+ ret = osf_core_register_capabilities(osf, &osf->capability_cache);
+ if (ret) {
+ /* As with RX discovery failure, a later report may retry. */
+ osf->capability_cache.valid = 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] = { };
+ int ret;
+
+ ret = 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 = 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 = osf_core_find_capability(&osf->capability_cache,
+ sample.sensor_type, sample.sensor_index);
+ if (!entry || sample.channel_count != entry->channel_count ||
+ sample.sample_format != entry->sample_format ||
+ sample.scale_nano != 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 = 0; i < sample.channel_count; i++) {
+ ret = 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 = 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 = 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 = sample.sensor_type;
+ latest->sensor_index = sample.sensor_index;
+ latest->channel_count = sample.channel_count;
+ latest->sample_format = sample.sample_format;
+ latest->scale_nano = sample.scale_nano;
+ latest->sequence = frame->sequence;
+ latest->timestamp_us = frame->timestamp_us;
+ latest->valid = true;
+ osf->last_sequence = frame->sequence;
+
+ return OSF_STREAM_FRAME_HANDLED;
}
static int osf_core_handle_device_status(struct osf_device *osf,
@@ -45,6 +289,91 @@ static int osf_core_handle_device_status(struct osf_device *osf,
return OSF_STREAM_FRAME_HANDLED;
}
+static int osf_core_handle_capability_report(struct osf_device *osf,
+ const struct osf_frame *frame)
+{
+ struct osf_capability_cache cache = { };
+ struct osf_capability_report report;
+ int frame_result;
+ int ret;
+
+ ret = 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 = 0; i < report.capability_count; i++) {
+ struct osf_capability_entry entry;
+
+ ret = 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 >= OSF_MAX_CAPABILITIES) {
+ dev_warn_ratelimited(osf->dev,
+ "too many supported capabilities\n");
+ return -E2BIG;
+ }
+
+ cache.entries[cache.capability_count] = 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 = frame->sequence;
+ cache.valid = cache.capability_count != 0;
+
+ if (osf->iio_dev_count) {
+ if (!osf_core_capabilities_equal(&osf->capability_cache, &cache)) {
+ guard(mutex)(&osf->latest_lock);
+ osf->session_fault = true;
+ return -EPROTO;
+ }
+
+ osf->last_sequence = frame->sequence;
+ return OSF_STREAM_FRAME_IGNORED;
+ }
+
+ frame_result = OSF_STREAM_FRAME_IGNORED;
+ if (cache.capability_count) {
+ if (osf->iio_ready) {
+ ret = osf_core_register_capabilities(osf, &cache);
+ if (ret) {
+ dev_err_ratelimited(osf->dev,
+ "failed to register capabilities: %d\n",
+ ret);
+ return ret;
+ }
+ }
+ frame_result = OSF_STREAM_FRAME_HANDLED;
+ } else {
+ dev_dbg_ratelimited(osf->dev,
+ "ignoring report without supported capabilities\n");
+ }
+
+ osf->capability_cache = cache;
+ osf->last_sequence = frame->sequence;
+
+ return frame_result;
+}
+
int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len)
{
struct osf_frame frame;
@@ -66,9 +395,15 @@ int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len)
}
switch (frame.message_type) {
+ case OSF_MSG_SENSOR_SAMPLE:
+ ret = osf_core_handle_sensor_sample(osf, &frame);
+ break;
case OSF_MSG_DEVICE_STATUS:
ret = osf_core_handle_device_status(osf, &frame);
break;
+ case OSF_MSG_CAPABILITY_REPORT:
+ ret = osf_core_handle_capability_report(osf, &frame);
+ break;
default:
if (frame.message_type >= OSF_RESERVED_MSG_FIRST &&
frame.message_type <= OSF_RESERVED_MSG_LAST) {
@@ -99,3 +434,32 @@ int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len)
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 = 0; i < osf->latest_sample_count; i++) {
+ latest = &osf->latest_samples[i];
+ if (latest->sensor_type != sensor_type ||
+ latest->sensor_index != sensor_index)
+ continue;
+
+ if (!latest->valid || channel >= latest->channel_count)
+ break;
+
+ *value = latest->values[channel];
+ return 0;
+ }
+
+ return -ENODATA;
+}
diff --git a/drivers/iio/opensensorfusion/osf_core.h b/drivers/iio/opensensorfusion/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
+#include <linux/mutex.h>
#include <linux/types.h>
#include "osf_protocol.h"
+#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;
+};
struct osf_status_cache {
u32 uptime_s;
@@ -17,13 +41,35 @@ struct osf_status_cache {
bool valid;
};
+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;
};
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 len);
+int osf_core_read_latest_sample(struct osf_device *osf, u16 sensor_type,
+ u16 sensor_index, u16 channel,
+ s32 *value);
#endif
diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/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 <linux/array_size.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#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 = (_type), \
+ .modified = 1, \
+ .channel2 = (_mod), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = (_idx), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+ }
+
+#define OSF_CHAN(_type, _idx) \
+ { \
+ .type = (_type), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = (_idx), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 32, \
+ .storagebits = 32, \
+ .endianness = IIO_CPU, \
+ }, \
+ }
+
+static const struct iio_chan_spec osf_accel_channels[] = {
+ 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[] = {
+ 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[] = {
+ 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[] = {
+ OSF_CHAN(IIO_TEMP, 0),
+ IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static const struct osf_iio_sensor_spec osf_iio_sensor_specs[] = {
+ {
+ .sensor_type = OSF_SENSOR_ACCELEROMETER,
+ .channel_count = 3,
+ .name = "osf-accel",
+ .channels = osf_accel_channels,
+ .num_channels = ARRAY_SIZE(osf_accel_channels),
+ },
+ {
+ .sensor_type = OSF_SENSOR_GYROSCOPE,
+ .channel_count = 3,
+ .name = "osf-gyro",
+ .channels = osf_gyro_channels,
+ .num_channels = ARRAY_SIZE(osf_gyro_channels),
+ },
+ {
+ .sensor_type = OSF_SENSOR_MAGNETOMETER,
+ .channel_count = 3,
+ .name = "osf-magn",
+ .channels = osf_mag_channels,
+ .num_channels = ARRAY_SIZE(osf_mag_channels),
+ },
+ {
+ .sensor_type = OSF_SENSOR_TEMPERATURE,
+ .channel_count = 1,
+ .name = "osf-temp",
+ .channels = osf_temp_channels,
+ .num_channels = 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 = 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) {
+ if (osf_iio_sensor_specs[i].sensor_type == sensor_type &&
+ osf_iio_sensor_specs[i].channel_count == 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 = 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) {
+ if (osf_iio_sensor_specs[i].sensor_type == 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 = iio_priv(indio_dev);
+ s32 raw;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = osf_core_read_latest_sample(state->osf,
+ state->spec->sensor_type,
+ state->sensor_index,
+ chan->scan_index, &raw);
+ if (ret)
+ return ret;
+
+ *val = raw;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = state->scale_nano / NANO;
+ *val2 = state->scale_nano % NANO;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info osf_iio_info = {
+ .read_raw = osf_iio_read_raw,
+};
+
+static int osf_iio_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct osf_iio_state *state = iio_priv(indio_dev);
+
+ guard(mutex)(&state->buffer_lock);
+ state->buffer_active = true;
+
+ return 0;
+}
+
+static int osf_iio_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct osf_iio_state *state = iio_priv(indio_dev);
+
+ /* Wait for the current push before the IIO core changes its buffers. */
+ guard(mutex)(&state->buffer_lock);
+ state->buffer_active = false;
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops osf_iio_buffer_ops = {
+ .postenable = osf_iio_buffer_postenable,
+ .predisable = 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 = osf_iio_find_sensor_spec(entry->sensor_type,
+ entry->channel_count);
+ if (!spec)
+ return -EOPNOTSUPP;
+
+ if (entry->sample_format != OSF_SAMPLE_FORMAT_S32 ||
+ (entry->flags & ~OSF_CAPABILITY_FLAGS_MASK))
+ return -EOPNOTSUPP;
+
+ if (!entry->scale_nano)
+ return -EINVAL;
+
+ iio_dev = iio_device_alloc(dev, sizeof(*state));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ state = iio_priv(iio_dev);
+ state->spec = spec;
+ state->scale_nano = entry->scale_nano;
+ state->sensor_index = entry->sensor_index;
+ state->osf = osf;
+ mutex_init(&state->buffer_lock);
+
+ iio_dev->name = spec->name;
+ iio_dev->info = &osf_iio_info;
+ iio_dev->setup_ops = &osf_iio_buffer_ops;
+ iio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
+ iio_dev->channels = spec->channels;
+ iio_dev->num_channels = spec->num_channels;
+
+ state->buffer = iio_kfifo_allocate();
+ if (!state->buffer) {
+ ret = -ENOMEM;
+ goto err_free_iio;
+ }
+
+ ret = iio_device_attach_buffer(iio_dev, state->buffer);
+ if (ret)
+ goto err_free_buffer;
+
+ ret = iio_device_register(iio_dev);
+ if (ret)
+ goto err_free_buffer;
+
+ *indio_dev = 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 = 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 = iio_priv(indio_dev);
+ s64 timestamp;
+
+ if (channel_count != state->spec->channel_count)
+ return -EPROTO;
+
+ guard(mutex)(&state->buffer_lock);
+ if (!state->buffer_active || !iio_buffer_enabled(indio_dev))
+ return 0;
+
+ timestamp = iio_get_time_ns(indio_dev);
+
+ switch (channel_count) {
+ case 1: {
+ struct osf_iio_scan_1axis scan = {
+ .value = values[0],
+ };
+
+ return iio_push_to_buffers_with_ts(indio_dev, &scan,
+ sizeof(scan), timestamp);
+ }
+ case 3: {
+ struct osf_iio_scan_3axis scan = {
+ .values = { },
+ };
+ unsigned int channel, index = 0;
+
+ /* Pack the active channels; unused storage remains initialized. */
+ for_each_set_bit(channel, indio_dev->active_scan_mask, channel_count)
+ scan.values[index++] = 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/opensensorfusion/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 <linux/types.h>
+
+#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/opensensorfusion/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;
};
@@ -68,9 +68,10 @@ static void osf_serdev_release(void *data)
{
struct osf_serdev *osf_uart = data;
- /* 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 *serdev)
return dev_err_probe(dev, ret, "failed to enable vcc regulator\n");
osf_uart->vcc_enabled = true;
+ /* 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;
}
@@ -139,5 +144,5 @@ static struct serdev_device_driver osf_serdev_driver = {
};
module_serdev_device_driver(osf_serdev_driver);
-MODULE_DESCRIPTION("Open Sensor Fusion UART receiver");
+MODULE_DESCRIPTION("Open Sensor Fusion IIO driver");
MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 7/8] iio: osf: add core KUnit tests
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
` (5 preceding siblings ...)
2026-09-18 18:24 ` [PATCH v10 6/8] iio: osf: add IIO devices from capability reports Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 2:12 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 8/8] iio: osf: add IIO " Jinseob Kim
2026-09-20 2:05 ` [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jonathan Cameron
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <kimjinseob88@gmail.com>
---
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/opensensorfusion/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=y && KUNIT=y
+ 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 tests
+ 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/opensensorfusion/Makefile
index b4e03b80cfa4..c2f5ba065a62 100644
--- a/drivers/iio/opensensorfusion/Makefile
+++ b/drivers/iio/opensensorfusion/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_OPEN_SENSOR_FUSION) += open-sensor-fusion.o
open-sensor-fusion-y := osf_core.o osf_iio.o osf_protocol.o osf_serdev.o \
osf_stream.o
+open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) += osf_core_test.o
diff --git a/drivers/iio/opensensorfusion/osf_core_test.c b/drivers/iio/opensensorfusion/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 <kunit/device.h>
+#include <kunit/test.h>
+
+#include <linux/bitmap.h>
+#include <linux/crc32.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/buffer_impl.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#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 = 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] = OSF_PROTOCOL_MAJOR;
+ buf[5] = 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 = 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] = { };
+ u8 *supported = payload + OSF_CAP_REPORT_BASE_LEN;
+ u8 *unsupported = 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] = { };
+ 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 = 0; i < channel_count; i++)
+ put_unaligned_le32(values[i],
+ payload + OSF_SENSOR_SAMPLE_BASE_LEN +
+ i * sizeof(__le32));
+
+ payload_len = 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_size,
+ u64 sequence)
+{
+ u8 payload[OSF_SENSOR_SAMPLE_BASE_LEN + sizeof(__le32)] = { };
+
+ 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 = 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 = 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 = (unsigned long *)ctx->buffer->scan_mask;
+ for (unsigned int i = 0; i < 3; i++)
+ bitmap_set(scan_mask, ctx->indio_dev->channels[i].scan_index, 1);
+ ret = iio_update_buffers(ctx->indio_dev, ctx->buffer, NULL);
+ if (!ret)
+ ctx->buffer_enabled = true;
+
+ return ret;
+}
+
+static void osf_test_cleanup(void *data)
+{
+ struct osf_test_context *ctx = data;
+
+ if (ctx->buffer_enabled) {
+ iio_update_buffers(ctx->indio_dev, NULL, ctx->buffer);
+ ctx->buffer_enabled = 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 = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ test->priv = ctx;
+ ctx->dev = 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 = kunit_add_action_or_reset(test, osf_test_cleanup, ctx);
+ if (ret)
+ return ret;
+
+ frame_len = osf_test_build_capability_frame(frame, sizeof(frame), 1);
+ if (!frame_len)
+ return -EINVAL;
+
+ ret = osf_core_receive_frame(&ctx->osf, frame, frame_len);
+ if (ret != OSF_STREAM_FRAME_HANDLED)
+ return ret < 0 ? ret : -EINVAL;
+ if (ctx->osf.iio_dev_count != 1 ||
+ ctx->osf.capability_cache.capability_count != 1)
+ return -EINVAL;
+
+ ctx->indio_dev = ctx->osf.iio_devs[0].indio_dev;
+ ctx->buffer = 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 = 0; i < 3; i++) {
+ value = 0;
+ ret = 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 = test->priv;
+ const s32 baseline_values[] = { 11, 22, 33 };
+ const s32 rejected_values[] = { -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 = 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 = ctx->osf.latest_samples[0];
+ baseline_sequence = ctx->osf.last_sequence;
+ frame_len = 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 = 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 = test->priv;
+ const s32 baseline_values[] = { 1, 2, 3 };
+ const s32 rejected_values[] = { -1 };
+ const s32 valid_values[] = { 101, -202, 303 };
+ struct osf_test_scan_3axis scan = { };
+ int ret;
+
+ KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0);
+
+ ret = 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 = 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 = 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 = test->priv;
+ const s32 baseline_values[] = { 10, 20, 30 };
+ const s32 ignored_values[] = { -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 = 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 = ctx->osf.latest_samples[0];
+
+ ret = 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 = 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 = test->priv;
+ const s32 three_values[] = { 7, 8, 9 };
+ const s32 one_value[] = { 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 = 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_samples));
+
+ for (unsigned int i = 0; i < OSF_TEST_FLOOD_COUNT; i++) {
+ ret = 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 = 0; i < OSF_TEST_FLOOD_COUNT; i++) {
+ ret = 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 = 0; i < OSF_TEST_FLOOD_COUNT; i++) {
+ ret = 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 = 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 = 0; i < OSF_TEST_FLOOD_COUNT; i++) {
+ ret = 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 = 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 = 0; i < 3; i++) {
+ value = 0;
+ ret = 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 = 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 = kunit_kzalloc(test, sizeof(*pending), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pending);
+ osf_core_init(pending, ctx->dev);
+ ret = kunit_add_action_or_reset(test, osf_test_unregister_pending, pending);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ len = 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 gate. */
+ memset(frame, 0xa5, sizeof(frame));
+ osf_core_start(pending);
+ KUNIT_ASSERT_EQ(test, pending->iio_dev_count, 1U);
+ indio_dev = pending->iio_devs[0].indio_dev;
+ ret = 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 = 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 = test->priv;
+ struct osf_device *pending;
+ u8 payload[OSF_CAP_REPORT_BASE_LEN + 2 * OSF_CAP_SENSOR_ENTRY_LEN] = { };
+ u8 frame[OSF_TEST_MAX_FRAME_LEN];
+ u8 *entry = payload + OSF_CAP_REPORT_BASE_LEN;
+ size_t len;
+ int ret;
+
+ pending = kunit_kzalloc(test, sizeof(*pending), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, pending);
+ osf_core_init(pending, ctx->dev);
+ ret = kunit_add_action_or_reset(test, osf_test_unregister_pending, pending);
+ 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 = osf_test_build_frame(frame, sizeof(frame), OSF_MSG_CAPABILITY_REPORT,
+ 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 = 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 = {
+ .sensor_type = OSF_SENSOR_ACCELEROMETER,
+ .channel_count = 3,
+ .sample_format = OSF_SAMPLE_FORMAT_S32,
+ .scale_nano = 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] = { };
+
+ put_unaligned_le16(count, payload);
+ for (u16 i = 0; i < count; i++) {
+ u8 *p = payload + OSF_CAP_REPORT_BASE_LEN + i * OSF_CAP_SENSOR_ENTRY_LEN;
+
+ 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 = 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 = NULL;
+ ctx->buffer = NULL;
+}
+
+static void osf_test_bind_accel(struct kunit *test, struct osf_test_context *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 = ctx->osf.iio_devs[0].indio_dev;
+ ctx->buffer = ctx->indio_dev->buffer;
+}
+
+static void osf_zero_scale_is_atomic_test(struct kunit *test)
+{
+ struct osf_test_context *ctx = test->priv;
+ struct osf_capability_entry entries[] = { osf_test_accel, osf_test_accel };
+
+ entries[1].sensor_index = 1;
+ entries[1].scale_nano = 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 *test)
+{
+ struct osf_test_context *ctx = test->priv;
+ const s32 values[] = { OSF_FRAME_MAGIC, -22, 33 };
+ const s32 next[] = { 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 = 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_ACCELEROMETER,
+ 0, 3, OSF_SAMPLE_FORMAT_S32, values, 2),
+ OSF_STREAM_FRAME_HANDLED);
+ KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0);
+ baseline = ctx->osf.latest_samples[0];
+ len = osf_test_build_sample_frame(frame, sizeof(frame), OSF_SENSOR_ACCELEROMETER,
+ 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(baseline));
+ 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_ACCELEROMETER,
+ 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 = test->priv;
+ struct osf_capability_entry entry = osf_test_accel;
+
+ osf_test_new_session(ctx);
+ entry.sensor_type = 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 = test->priv;
+ struct osf_capability_entry entries[] = { osf_test_accel, osf_test_accel,
+ osf_test_accel };
+ struct osf_capability_entry swap;
+ struct iio_dev *first;
+
+ osf_test_new_session(ctx);
+ entries[1].sensor_type = OSF_SENSOR_GYROSCOPE;
+ KUNIT_ASSERT_EQ(test, osf_test_submit_report(ctx, entries, 2), OSF_STREAM_FRAME_HANDLED);
+ first = ctx->osf.iio_devs[0].indio_dev;
+ KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 2), OSF_STREAM_FRAME_IGNORED);
+ swap = entries[0];
+ entries[0] = entries[1];
+ entries[1] = swap;
+ KUNIT_EXPECT_EQ(test, osf_test_submit_report(ctx, entries, 2), OSF_STREAM_FRAME_IGNORED);
+ entries[0].flags = OSF_CAPABILITY_FLAGS_MASK;
+ entries[0].reserved = 0xa5a5;
+ entries[2].sensor_type = 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] = osf_test_accel;
+ entries[2].sensor_index = 1;
+ entries[2].flags = 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 = test->priv;
+ const s32 v[] = { 11, 22, 33 };
+ struct osf_test_scan_3axis scan;
+
+ for (unsigned int change = 0; change < 8; change++) {
+ struct osf_capability_entry entries[] = { osf_test_accel, osf_test_accel };
+ struct osf_latest_sample baseline;
+ struct iio_dev *child;
+ u16 count = 1;
+ int value = 0;
+
+ osf_test_new_session(ctx);
+ osf_test_bind_accel(test, ctx);
+ child = ctx->indio_dev;
+ KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0);
+ KUNIT_ASSERT_EQ(test, osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER,
+ 0, 3, OSF_SAMPLE_FORMAT_S32, v, 2),
+ OSF_STREAM_FRAME_HANDLED);
+ KUNIT_ASSERT_EQ(test, osf_test_remove_scan(ctx, &scan), 0);
+ baseline = ctx->osf.latest_samples[0];
+ switch (change) {
+ case 0:
+ entries[0].scale_nano++;
+ break;
+ case 1:
+ entries[0].channel_count = 1;
+ break;
+ case 2:
+ entries[0].sample_format = 2;
+ break;
+ case 3:
+ entries[0].sensor_index = 1;
+ break;
+ case 4:
+ count = 0;
+ break;
+ case 5:
+ entries[0].sensor_type = 0xffff;
+ break;
+ case 6:
+ entries[1].sensor_index = 1;
+ count = 2;
+ break;
+ case 7:
+ entries[0].flags = BIT(7);
+ break;
+ }
+ kunit_info(test, "meaning change=%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_ACCELEROMETER,
+ 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 = test->priv;
+ struct osf_capability_entry entry = osf_test_accel;
+ const s32 values[] = { 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 = ctx->osf.iio_devs[0].indio_dev;
+ ctx->buffer = 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 = 0; i < 2; i++) {
+ len = osf_test_build_sample_frame(frame, sizeof(frame), OSF_SENSOR_ACCELEROMETER,
+ 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 = test->priv;
+ struct osf_capability_entry entry = osf_test_accel;
+ const s32 values[] = { 11, 22, 33 };
+ u8 status[OSF_DEVICE_STATUS_LEN] = { };
+ u8 frame[OSF_TEST_MAX_FRAME_LEN];
+ size_t len;
+
+ osf_test_new_session(ctx);
+ entry.reserved = 0xa5a5a5a5;
+ len = 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 = ctx->osf.iio_devs[0].indio_dev;
+ ctx->buffer = ctx->indio_dev->buffer;
+ len = osf_test_build_sample_frame(frame, sizeof(frame), OSF_SENSOR_ACCELEROMETER,
+ 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 = 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 = test->priv;
+ const s32 values[] = { 11, 22, 33 };
+ struct osf_latest_sample baseline;
+ u8 frame[OSF_TEST_MAX_FRAME_LEN];
+ u8 payload[OSF_TEST_MAX_PAYLOAD_LEN + 1] = { };
+ size_t len;
+
+ KUNIT_ASSERT_EQ(test, osf_test_submit_sample(ctx, OSF_SENSOR_ACCELEROMETER,
+ 0, 3, OSF_SAMPLE_FORMAT_S32, values, 2),
+ OSF_STREAM_FRAME_HANDLED);
+ baseline = ctx->osf.latest_samples[0];
+ len = osf_test_report(frame, &osf_test_accel, 1);
+ frame[34] = 1;
+ KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), -EBADMSG);
+ osf_test_crc(frame, len);
+ put_unaligned_le32(U32_MAX, frame + 10);
+ KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len), -EMSGSIZE);
+ len = 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 = OSF_MSG_SENSOR_SAMPLE; type <= OSF_MSG_CAPABILITY_REPORT; type++) {
+ u32 size;
+
+ memset(payload, 0, sizeof(payload));
+ if (type == 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 = OSF_TEST_MAX_PAYLOAD_LEN;
+ } else if (type == OSF_MSG_DEVICE_STATUS) {
+ size = OSF_DEVICE_STATUS_LEN;
+ } else {
+ size = OSF_CAP_REPORT_BASE_LEN;
+ }
+ len = 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(baseline));
+}
+
+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 = data;
+ const s32 values[] = { 101, 202, 303 };
+ u64 sequence = 100;
+ int ret, raw;
+
+ complete(&race->started);
+ while (!kthread_should_stop()) {
+ mutex_lock(&race->rx_lock);
+ ret = osf_test_submit_sample(race->ctx, OSF_SENSOR_ACCELEROMETER,
+ 0, 3, OSF_SAMPLE_FORMAT_S32, values, sequence++);
+ if (atomic_read(&race->faulted)) {
+ if (ret == OSF_STREAM_FRAME_REJECTED)
+ atomic_inc(&race->rejected);
+ else
+ atomic_inc(&race->errors);
+ }
+ mutex_unlock(&race->rx_lock);
+ ret = osf_test_read_raw(race->ctx, 0, &raw);
+ if (ret != IIO_VAL_INT && ret != -EPROTO && ret != -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 = test->priv;
+ struct osf_capability_entry entry = 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 = kunit_kzalloc(test, sizeof(*race), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, race);
+ race->ctx = ctx;
+ mutex_init(&race->rx_lock);
+ init_completion(&race->started);
+ KUNIT_ASSERT_EQ(test, osf_test_enable_buffer(ctx), 0);
+ producer = 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), 0UL);
+ /* Exercise real IIO mlock/buffer_lock while core takes latest_lock/buffer_lock. */
+ for (unsigned int i = 0; i < 100; i++) {
+ KUNIT_EXPECT_EQ(test, iio_update_buffers(ctx->indio_dev, NULL, ctx->buffer), 0);
+ KUNIT_EXPECT_EQ(test, iio_update_buffers(ctx->indio_dev, ctx->buffer, NULL), 0);
+ cond_resched();
+ }
+ mutex_lock(&race->rx_lock);
+ entry.scale_nano++;
+ ret = osf_test_submit_report(ctx, &entry, 1);
+ KUNIT_EXPECT_EQ(test, ret, OSF_STREAM_FRAME_REJECTED);
+ atomic_set(&race->faulted, 1);
+ baseline = ctx->osf.latest_samples[0];
+ while (!osf_test_remove_scan(ctx, &scan))
+ ;
+ mutex_unlock(&race->rx_lock);
+ for (unsigned int i = 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(baseline));
+ 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[] = {
+ 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 = {
+ .name = "osf-core",
+ .init = osf_test_init,
+ .test_cases = osf_core_test_cases,
+};
+
+kunit_test_suite(osf_core_test_suite);
+
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v10 8/8] iio: osf: add IIO KUnit tests
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
` (6 preceding siblings ...)
2026-09-18 18:24 ` [PATCH v10 7/8] iio: osf: add core KUnit tests Jinseob Kim
@ 2026-09-18 18:24 ` Jinseob Kim
2026-09-20 2:18 ` Jonathan Cameron
2026-09-20 2:05 ` [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jonathan Cameron
8 siblings, 1 reply; 25+ messages in thread
From: Jinseob Kim @ 2026-09-18 18:24 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andriy.shevchenko, linux-kernel, rdunlap,
joshua.crofts1, u.kleine-koenig, julianbraha, robh, krzk+dt,
conor+dt, devicetree, corbet, skhan, linux-doc
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 <kimjinseob88@gmail.com>
---
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/opensensorfusion/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 tests
+ acceptance, direct-read cache, and IIO buffer paths. The tests
exercise accepted, ignored, and rejected sample handling without
exposing additional production interfaces.
diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensorfusion/Makefile
index c2f5ba065a62..6a28b14cae1f 100644
--- a/drivers/iio/opensensorfusion/Makefile
+++ b/drivers/iio/opensensorfusion/Makefile
@@ -4,4 +4,4 @@ obj-$(CONFIG_OPEN_SENSOR_FUSION) += open-sensor-fusion.o
open-sensor-fusion-y := osf_core.o osf_iio.o osf_protocol.o osf_serdev.o \
osf_stream.o
-open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) += osf_core_test.o
+open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) += osf_core_test.o osf_iio_test.o
diff --git a/drivers/iio/opensensorfusion/osf_iio_test.c b/drivers/iio/opensensorfusion/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 <kunit/device.h>
+#include <kunit/test.h>
+
+#include <linux/bitmap.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/buffer_impl.h>
+#include <linux/iio/iio.h>
+#include <linux/kthread.h>
+#include <linux/string.h>
+
+#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_dev,
+ const s32 *values, unsigned int channels,
+ unsigned int mask, bool ts)
+{
+ struct iio_buffer *buffer = indio_dev->buffer;
+ unsigned int data_bytes = hweight32(mask) * sizeof(s32);
+ unsigned int ts_offset = ALIGN(data_bytes, 8);
+ unsigned int scan_bytes = ts ? ts_offset + 8 : data_bytes;
+ unsigned int offset = 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 = 0; i < channels; i++)
+ if (mask & BIT(i))
+ set_bit(i, (unsigned long *)buffer->scan_mask);
+ buffer->scan_timestamp = ts;
+ ret = iio_update_buffers(indio_dev, buffer, NULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ kunit_info(test, "device=%s mask=%u ts=%u bytes=%u\n",
+ indio_dev->name, mask, ts, scan_bytes);
+ memset(scan, 0xa5, sizeof(scan));
+ before = iio_get_time_ns(indio_dev);
+ osf_test_poison_stack();
+ ret = osf_iio_push_sample(indio_dev, values, channels);
+ after = iio_get_time_ns(indio_dev);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, buffer->bytes_per_datum, scan_bytes);
+ ret = iio_pop_from_buffer(buffer, scan);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ if (ret)
+ goto disable;
+
+ for (unsigned int i = 0; i < channels; i++) {
+ if (!(mask & BIT(i)))
+ continue;
+ KUNIT_EXPECT_MEMEQ(test, scan + offset, &values[i], sizeof(s32));
+ offset += sizeof(s32);
+ }
+ if (ts) {
+ for (unsigned int i = 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 = 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[] = {
+ OSF_SENSOR_ACCELEROMETER, OSF_SENSOR_GYROSCOPE,
+ OSF_SENSOR_MAGNETOMETER, OSF_SENSOR_TEMPERATURE,
+ };
+ const s32 values[] = { 101, -202, 303 };
+ struct osf_capability_entry entry = {
+ .sample_format = OSF_SAMPLE_FORMAT_S32,
+ .scale_nano = 1000000,
+ };
+ struct osf_device *osf;
+ struct device *dev;
+
+ dev = kunit_device_register(test, "osf-iio");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ osf = kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, osf);
+ osf_core_init(osf, dev);
+
+ for (unsigned int t = 0; t < ARRAY_SIZE(types); t++) {
+ unsigned int channels = t == 3 ? 1 : 3;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ entry.sensor_type = types[t];
+ entry.channel_count = channels;
+ ret = osf_iio_register_sensor(dev, &entry, osf, &indio_dev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ for (unsigned int mask = 1; mask < BIT(channels); mask++)
+ for (unsigned int ts = 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 *buffer)
+{
+ 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 = 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 = 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 = 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 = data;
+ const s32 values[] = { 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 = data;
+
+ complete(&race->config_started);
+ if (race->unregister) {
+ osf_iio_unregister_sensor(race->indio_dev);
+ race->config_result = 0;
+ } else {
+ race->config_result = 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 = 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 = wait_for_completion_timeout(&race->config_done, msecs_to_jiffies(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 producer. */
+static void osf_iio_buffer_lifetime_test(struct kunit *test)
+{
+ struct osf_capability_entry entry = {
+ .sensor_type = OSF_SENSOR_ACCELEROMETER,
+ .channel_count = 3,
+ .sample_format = OSF_SAMPLE_FORMAT_S32,
+ .scale_nano = 1000000,
+ };
+ const s32 values[] = { 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 = kunit_device_register(test, "osf-race");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+ osf = kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL);
+ race = 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 = osf_iio_register_sensor(dev, &entry, osf, &indio_dev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ buffer = indio_dev->buffer;
+ race->indio_dev = indio_dev;
+ race->original = buffer->access;
+ race->access = *buffer->access;
+ race->access.store_to = osf_iio_test_store;
+ race->access.enable = osf_iio_test_enable;
+ race->access.disable = osf_iio_test_disable;
+ buffer->access = &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 = 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), 0UL);
+ osf_iio_expect_quiesce(test, race);
+
+ for (unsigned int i = 0; i < 100; i++) {
+ bitmap_zero((unsigned long *)buffer->scan_mask, iio_get_masklength(indio_dev));
+ *(unsigned long *)buffer->scan_mask = (i % 7) + 1;
+ buffer->scan_timestamp = 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 = -EIO;
+ KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), -EIO);
+ stores = 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 = 0;
+ KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0);
+ race->disable_error = -EIO;
+ KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), -EIO);
+ stores = 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 = 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), 0UL);
+ race->unregister = 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 = race->original;
+ put_device(&indio_dev->dev);
+}
+
+static struct kunit_case osf_iio_test_cases[] = {
+ KUNIT_CASE(osf_iio_scan_bytes_test),
+ KUNIT_CASE(osf_iio_buffer_lifetime_test),
+ { }
+};
+
+static struct kunit_suite osf_iio_test_suite = {
+ .name = "osf-iio",
+ .test_cases = osf_iio_test_cases,
+};
+
+kunit_test_suite(osf_iio_test_suite);
+
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview
2026-09-18 18:24 ` [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview Jinseob Kim
@ 2026-09-20 1:12 ` Jonathan Cameron
2026-09-20 3:57 ` Kim Jinseob
0 siblings, 1 reply; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 1:12 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:40 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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 <kimjinseob88@gmail.com>
Hi Jinseob,
Just a couple of minor things inline.
Thanks,
Jonathan
> ---
> 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/open-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
> +==================
> +
> +Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO driver
> +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, compatibility
> +rules, reserved fields and physical units. This document describes the Linux
> +receiver profile and its mapping to IIO.
> +
> +.. _specification: https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/spec/osf-d2h-0.0.md
> +
> +Supported receive profile
> +-------------------------
> +
> +The driver supports the fixed OSF0 frame layout with protocol major version 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
If you spin again "Unsupported major versions and.."
> +unknown message types are ignored. Unsupported capability entries are skipped
> +individually. A CRC-valid application rejection consumes the complete frame;
> +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 unit.
> +Buffered samples use signed 32-bit values with native CPU endianness. Active
> +channels are packed into the scan, with initialized padding before an optional
> +64-bit timestamp.
> +
> +Discovery and session lifetime
> +------------------------------
> +
> +The driver validates all supported descriptors before registering IIO devices.
> +An empty or entirely unsupported initial report leaves discovery open for a
> +later supported report.
So if the device can send multiple discovery reports, this implies if we
recognise something in the first one we will stop looking at later ones?
That seems odd given if we don't recognise anything we keep looking.
If I'm reading too much into this text, then please tweak it to make
it clear why this doesn't matter.
> Supported descriptors must advertise a nonzero scale.
> +A sample's channel count, format and scale must match its registered descriptor
> +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 entry
> +order. Reserved padding, advisory flags and unsupported additions do not change
> +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 starts
> +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 discontinuities
> +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 <kimjinseob88@gmail.com>
> S: Maintained
> F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
> +F: Documentation/iio/open-sensor-fusion.rst
> K: opensensorfusion
>
> OPENCOMPUTE PTP CLOCK DRIVER
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device
2026-09-18 18:24 ` [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device Jinseob Kim
@ 2026-09-20 1:14 ` Jonathan Cameron
2026-09-20 4:01 ` Kim Jinseob
0 siblings, 1 reply; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 1:14 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:39 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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.
Sashiko raises a reasonable question about what you'd do if it turned
out your current OSF GREEN doesn't quite comply with the specification
or needs some additional quirks. Does it make sense to have a more
specific compatible with a fallback to the generic one?
Or given this is mostly about device firmware maybe this is fine.
Either way - would be good to add a note on why that doesn't make
sense in this patch description.
Thanks,
Jonathan
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 3/8] iio: osf: add protocol decoding
2026-09-18 18:24 ` [PATCH v10 3/8] iio: osf: add protocol decoding Jinseob Kim
@ 2026-09-20 1:27 ` Jonathan Cameron
2026-09-20 4:12 ` Kim Jinseob
0 siblings, 1 reply; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 1:27 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:41 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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 <kimjinseob88@gmail.com>
Hi Jinseob
A few things inline. Some are about what I'd kind of expect from
how specs are often defined to leave a bit of flexibility and reduce
the need to update drivers for new stuff being added.
If you don't want to go that way I don't really mind.
Otherwise main thing here is why carry reserved data to next layer
given it is meant to be ignored. Drop it down here in the protocol decode.
Thanks,
Jonathan
> diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/opensensorfusion/osf_protocol.c
> new file mode 100644
> index 000000000000..e0d7c7a9ebd7
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_protocol.c
> +
> +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) != OSF_FRAME_MAGIC)
> + return -EPROTO;
> +
> + if (get_unaligned_le16(buf + 6) != OSF_FRAME_HEADER_LEN)
> + return -EPROTO;
> +
> + payload_len = get_unaligned_le32(buf + 10);
> + if (payload_len > len - OSF_FRAME_MIN_LEN)
> + return -EMSGSIZE;
> +
> + total_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
> + expected_crc = osf_crc32_ieee(buf, OSF_FRAME_HEADER_LEN + payload_len);
> + actual_crc = get_unaligned_le32(buf + OSF_FRAME_HEADER_LEN + payload_len);
> +
> + if (actual_crc != expected_crc)
> + return -EBADMSG;
> +
> + frame->protocol_major = buf[4];
> + frame->protocol_minor = buf[5];
> + frame->message_type = get_unaligned_le16(buf + 8);
> + frame->payload_len = payload_len;
> + frame->sequence = get_unaligned_le64(buf + 14);
> + frame->timestamp_us = get_unaligned_le64(buf + 22);
> + frame->flags = get_unaligned_le32(buf + 30);
> + frame->reserved = get_unaligned_le32(buf + 34);
As below. I'm not sure what benefit of keeping reserved around is.
> + frame->payload = buf + OSF_FRAME_HEADER_LEN;
> + frame->crc = actual_crc;
The above fully assigns frame so I'd do similar to you have elsewhere
*frame = (struct osf_frame) {
.protocol_major = ...
...
};
Both makes it more readable and makes it clear you aren't leaving
any existing data in place.
> + *frame_len = total_len;
> +
> + 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 != OSF_MSG_CAPABILITY_REPORT)
> + return -EPROTO;
> +
> + if (frame->payload_len < OSF_CAP_REPORT_BASE_LEN)
> + return -EMSGSIZE;
> +
> + payload = frame->payload;
> + capability_count = get_unaligned_le16(payload);
> +
> + expected_len = OSF_CAP_REPORT_BASE_LEN +
> + capability_count * OSF_CAP_SENSOR_ENTRY_LEN;
> + if (frame->payload_len != expected_len)
I don't hugely mind as it's your code + spec to maintain but generally
for a spec with records like this I'd expect it to be possible to extend
the structure without it being a breaking spec change. As such
I'd kind of expect the check to be that the payload_len was at least
as big as expected len. Larger would be fine but we'd ignore anything
there.
> + return -EMSGSIZE;
> +
> + *report = (struct osf_capability_report) {
> + .capability_count = capability_count,
> + .entries = 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 >= report->capability_count)
> + return -ERANGE;
> +
> + payload = report->entries + index * OSF_CAP_SENSOR_ENTRY_LEN;
> + *entry = (struct osf_capability_entry) {
> + .sensor_type = get_unaligned_le16(payload),
> + .sensor_index = get_unaligned_le16(payload + 2),
> + .channel_count = get_unaligned_le16(payload + 4),
> + .sample_format = get_unaligned_le16(payload + 6),
> + .scale_nano = get_unaligned_le32(payload + 8),
> + .flags = get_unaligned_le32(payload + 12),
> + .reserved = get_unaligned_le32(payload + 16),
Entirely correctly I think you don't do any checks or reads of reserved values.
As such why copy them around? I'd just drop that field from your decoded
structures. Not a huge saving but why carry irrelevant data around!
Jonathan
> + };
> +
> + return 0;
> +}
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 4/8] iio: osf: add validated stream parser
2026-09-18 18:24 ` [PATCH v10 4/8] iio: osf: add validated stream parser Jinseob Kim
@ 2026-09-20 1:30 ` Jonathan Cameron
2026-09-20 4:12 ` Kim Jinseob
0 siblings, 1 reply; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 1:30 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:42 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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 <kimjinseob88@gmail.com>
Just one trivial thing.
Thanks,
Jonathan
> diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opensensorfusion/osf_stream.c
> new file mode 100644
> index 000000000000..e262415e69b7
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_stream.c
> +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 = receive_frame;
> + stream->frame_context = frame_context;
> + stream->len = 0;
> + memset(&stream->stats, 0, sizeof(stream->stats));
> +}
> +
> +void osf_stream_reset(struct osf_stream *stream)
> +{
> + if (!stream)
> + return;
> +
> + stream->len = 0;
> + memset(&stream->stats, 0, sizeof(stream->stats));
> +}
You could reorder these two functions and call stream_reset from
stream_init. Would make it clear what getting to a 'clean' state
means.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 5/8] iio: osf: add UART transport and core receive path
2026-09-18 18:24 ` [PATCH v10 5/8] iio: osf: add UART transport and core receive path Jinseob Kim
@ 2026-09-20 1:40 ` Jonathan Cameron
0 siblings, 0 replies; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 1:40 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:43 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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 <kimjinseob88@gmail.com>
One question for Uwe if they see it about whether device-id/of.h should
be included or we can assume it for serial drivers.
Otherwise, just a couple of minor things inline.
Thanks,
Jonathan
> ---
> 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 <kimjinseob88@gmail.com>
> 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/
Push that back to earlier patches.
> K: opensensorfusion
> diff --git a/drivers/iio/opensensorfusion/osf_core.c b/drivers/iio/opensensorfusion/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 <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/types.h>
> +
> +#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 = (struct osf_device) {
> + .dev = dev,
> + };
> +}
Unless this gets a lot more complex in later patches, I'd drop
the helper.
> +int osf_core_receive_frame(struct osf_device *osf, const u8 *buf, size_t len)
> +{
> + struct osf_frame frame;
> + size_t frame_len;
> + int ret;
> +
> + ret = osf_protocol_decode_frame(buf, len, &frame, &frame_len);
> + if (ret)
> + return ret;
> +
> + if (frame_len != len)
> + return -EMSGSIZE;
> +
> + if (frame.protocol_major != 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 = osf_core_handle_device_status(osf, &frame);
> + break;
> + default:
> + if (frame.message_type >= OSF_RESERVED_MSG_FIRST &&
> + frame.message_type <= 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 >= 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;
I think there is only one path to this. Move the comment and check up there
+ the return.
Then we never leave the switch statement without returning so this last
chunk isn't needed.
> +
> + return ret;
> +}
> diff --git a/drivers/iio/opensensorfusion/osf_serdev.c b/drivers/iio/opensensorfusion/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 <linux/cleanup.h>
> +#include <linux/device-id/of.h>
Curious that in all the recent tidying up of this stuff serdev.h
didn't include this one. Uwe, if you see this, was that intentional?
Doesn't matter though as if it is something that gets tidied up later
then this driver will get covered with any others.
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/serdev.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +static int osf_serdev_probe(struct serdev_device *serdev)
> +{
> + struct device *dev = &serdev->dev;
> + struct osf_serdev *osf_uart;
> + unsigned int baudrate;
> + int ret;
> +
> + osf_uart = devm_kzalloc(dev, sizeof(*osf_uart), GFP_KERNEL);
> + if (!osf_uart)
> + return -ENOMEM;
> +
> + osf_uart->vcc = 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);
Can we use devm_mutex_init() here. It doesn't bring huge advantage
but it is a cheap win for some debugging cases.
> + osf_uart->serdev = 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 = serdev_device_open(serdev);
> + if (ret)
> + return ret;
> +
> + ret = devm_add_action_or_reset(dev, osf_serdev_release, osf_uart);
> + if (ret)
> + return ret;
> +
> + baudrate = serdev_device_set_baudrate(serdev, OSF_SERDEV_BAUD);
> + if (baudrate != 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 = regulator_enable(osf_uart->vcc);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to enable vcc regulator\n");
> + osf_uart->vcc_enabled = true;
> +
> + return 0;
> +}
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 6/8] iio: osf: add IIO devices from capability reports
2026-09-18 18:24 ` [PATCH v10 6/8] iio: osf: add IIO devices from capability reports Jinseob Kim
@ 2026-09-20 2:03 ` Jonathan Cameron
2026-09-20 4:20 ` Kim Jinseob
0 siblings, 1 reply; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 2:03 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:44 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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.
No need to talk about what other patches do and that a series is functionally
complete in a patch. If you want to do it, under the --- so it doesn't end
up in the git log.
>
> Assisted-by: LLM
> Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
A few things inline. Biggest one is a question (I'm not sure) on
whether you can use a claim on buffered mode (which will hold it in that
if it succeeds until released) is sufficient to replace the extra lock you
have to block that transition.
Thanks,
Jonathan
> diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/osf_iio.c
> new file mode 100644
> index 000000000000..f4011b8fae14
> --- /dev/null
> +++ b/drivers/iio/opensensorfusion/osf_iio.c
...
> +
> +struct osf_iio_scan_3axis {
> + s32 values[3];
> + u32 padding;
No needed unless you explicitly write into padding for some reason.
C rules add it anyway and then we don't need to make sure it remains
correct if this structure changes in future.
> + aligned_s64 timestamp;
> +};
> +
> +struct osf_iio_scan_1axis {
> + s32 value;
> + u32 padding;
As above, unneeded.
> + aligned_s64 timestamp;
> +};
Given I think these are only used in one place, moving them to there would
avoid need for named structure types.
> +
> +#define OSF_MOD_CHAN(_type, _mod, _idx) \
> + { \
> + .type = (_type), \
> + .modified = 1, \
> + .channel2 = (_mod), \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> + .scan_index = (_idx), \
> + .scan_type = { \
> + .sign = 's', \
> + .realbits = 32, \
> + .storagebits = 32, \
> + .endianness = IIO_CPU, \
> + }, \
> + }
> +
> +#define OSF_CHAN(_type, _idx) \
Given this is not much used, you could use the IIO_NO_MOD modifier as a gate
on setting .modified = (_mod != IIO_NO_MOD) ? 1 : 0,
and have just one macro.
> + { \
> + .type = (_type), \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> + .scan_index = (_idx), \
> + .scan_type = { \
> + .sign = 's', \
> + .realbits = 32, \
> + .storagebits = 32, \
> + .endianness = IIO_CPU, \
> + }, \
> + }
> +
> +static const struct iio_chan_spec osf_accel_channels[] = {
> + 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[] = {
> + 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[] = {
> + 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[] = {
> + OSF_CHAN(IIO_TEMP, 0),
> + IIO_CHAN_SOFT_TIMESTAMP(1),
> +};
> +
> +static const struct osf_iio_sensor_spec osf_iio_sensor_specs[] = {
> + {
> + .sensor_type = OSF_SENSOR_ACCELEROMETER,
> + .channel_count = 3,
> + .name = "osf-accel",
> + .channels = osf_accel_channels,
> + .num_channels = ARRAY_SIZE(osf_accel_channels),
> + },
> + {
> + .sensor_type = OSF_SENSOR_GYROSCOPE,
> + .channel_count = 3,
> + .name = "osf-gyro",
> + .channels = osf_gyro_channels,
> + .num_channels = ARRAY_SIZE(osf_gyro_channels),
> + },
> + {
> + .sensor_type = OSF_SENSOR_MAGNETOMETER,
> + .channel_count = 3,
> + .name = "osf-magn",
> + .channels = osf_mag_channels,
> + .num_channels = ARRAY_SIZE(osf_mag_channels),
> + },
> + {
> + .sensor_type = OSF_SENSOR_TEMPERATURE,
> + .channel_count = 1,
> + .name = "osf-temp",
> + .channels = osf_temp_channels,
> + .num_channels = 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 = 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) {
> + if (osf_iio_sensor_specs[i].sensor_type == sensor_type &&
> + osf_iio_sensor_specs[i].channel_count == 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
return osf_iio_find_sensor_spec(sensor_type, channel_count) != NULL;
> +}
> +
> +static int osf_iio_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct osf_iio_state *state = iio_priv(indio_dev);
> +
> + guard(mutex)(&state->buffer_lock);
These transistions are already guarded by the core, so see the question later
on whether you can just claim buffered mode in the data handler
to avoid the need for this. That might not work but the way you did
this got me thinking about a more general solution to the problem.
> + state->buffer_active = true;
> +
> + return 0;
> +}
> +
> +static int osf_iio_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct osf_iio_state *state = iio_priv(indio_dev);
> +
> + /* Wait for the current push before the IIO core changes its buffers. */
> + guard(mutex)(&state->buffer_lock);
> + state->buffer_active = false;
> +
> + return 0;
> +}
> +
> +int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values,
> + u16 channel_count)
> +{
> + struct osf_iio_state *state = iio_priv(indio_dev);
> + s64 timestamp;
> +
> + if (channel_count != state->spec->channel_count)
> + return -EPROTO;
> +
> + guard(mutex)(&state->buffer_lock);
Using an extra lock is one solution I suppose to the risk
of this racing with the buffer tear down and the problems
around active_scan_mask being freed. Could you use
if (iio_device_try_claim_buffer_mode(indio_dev))
and later a release on that to achieve the same result?
Only the ade9000 is doing similar at the moment so would need
some testing to make sure no lock inversions occur in general
but given you don't take any other locks in here it should be fine.
> + if (!state->buffer_active || !iio_buffer_enabled(indio_dev))
> + return 0;
> +
> + timestamp = iio_get_time_ns(indio_dev);
> +
> + switch (channel_count) {
> + case 1: {
> + struct osf_iio_scan_1axis scan = {
> + .value = values[0],
> + };
As suggested above, this structure type is only used locally here. So
make the definition local as well.
struct {
s32 value;
aligned_s64 timestamp;
} scan = { };
> +
> + return iio_push_to_buffers_with_ts(indio_dev, &scan,
> + sizeof(scan), timestamp);
> + }
> + case 3: {
> + struct osf_iio_scan_3axis scan = {
> + .values = { },
Setting values doesn't add anything useful as = { }
zeros the whole structure (including any holes - they kernel is
built with specific compiler options to ensure that last bit
as it wasn't required in older c specs).
> + };
> + unsigned int channel, index = 0;
Please split declarations that assign and ones that don't on to separate lines.
Ends up a little more readable.
unsigned int index = 0;
unsigned int channel;
> +
> + /* Pack the active channels; unused storage remains initialized. */
> + for_each_set_bit(channel, indio_dev->active_scan_mask, channel_count)
> + scan.values[index++] = 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_serdev.c b/drivers/iio/opensensorfusion/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;
Just name it 'lock' given it has scope beyond rx. Do that in earlier patch
though rather than changing it here.
> };
>
> @@ -139,5 +144,5 @@ static struct serdev_device_driver osf_serdev_driver = {
> };
> module_serdev_device_driver(osf_serdev_driver);
>
> -MODULE_DESCRIPTION("Open Sensor Fusion UART receiver");
> +MODULE_DESCRIPTION("Open Sensor Fusion IIO driver");
Just have that being slightly missleading in earlier patch to reduce churn here.
> MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 0/8] iio: add Open Sensor Fusion UART support
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
` (7 preceding siblings ...)
2026-09-18 18:24 ` [PATCH v10 8/8] iio: osf: add IIO " Jinseob Kim
@ 2026-09-20 2:05 ` Jonathan Cameron
2026-09-20 4:22 ` Kim Jinseob
8 siblings, 1 reply; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 2:05 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:38 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> Specification status: OSF-D2H 0.0 spec-1 has completed project technical
> stability review, has been adopted by the project owner, and is published
> as a fixed specification.
>
> Canonical specification:
> https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/spec/osf-d2h-0.0.md
>
> Errata process:
> https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/errata/README.md
>
> Current adoption/publication record:
> https://github.com/opensensorfusion/opensensorfusion-protocol/blob/0cabccf63ac01d0eb82dd9882739ff58eed4a76c/reviews/publication-record-20260918.md
>
> The specification was frozen before adoption/publication, so its embedded
> status snapshot is intentionally historical. The dated publication record
> above establishes the current adopted/public state. This is project review
> and adoption, not an external maintainer approval.
>
> This series adds the Open Sensor Fusion UART receive path and IIO devices
> discovered from capability reports. It exposes accelerometer, gyroscope,
> magnetometer and temperature data through RAW/SCALE and buffered scans.
> Device Tree describes the sensor hub; its individual streams are discovered
> at runtime.
>
> The receiver validates supported descriptors and sample scales, keeps
> discovery open after empty or unsupported initial inventories, and checks
> repeated inventories before allowing new data to use registered metadata.
> A supported descriptor changing meaning faults the session until an
> explicit teardown and rebind. Existing v9 cache, scan-layout and buffer
> lifetime fixes are retained.
>
> Based on Jonathan Cameron's IIO testing branch:
> 69fa76f0af3414cc189c3b0b807cb59e327ecc00
>
> Changes since v9:
> - Publish and reference the reviewed/adopted OSF-D2H 0.0 specification,
> errata process and dated publication evidence.
> - Tolerate reserved padding, validate descriptor/sample scales, keep
> discovery open after empty/unsupported reports, and compare repeated
> capability reports.
> - Fail closed on changed descriptor meaning instead of publishing data
> with stale metadata.
> - Add focused KUnit coverage for these lifecycle and validation cases.
> - Use managed UART/IIO/power teardown and dev_warn_probe() for the
> controller baud-rate warning, addressing Andy's probe-path feedback.
> - Split the former combined UART/core/IIO driver patch into transport/core,
> IIO registration, core KUnit, and IIO KUnit patches following review.
> - Use validated/unvalidated terminology for framing/CRC results throughout
> the stream/core/transport code, tests and diagnostics. CRC detects
> accidental corruption; it does not provide cryptographic authentication.
>
> Prior validation on the identical source tree:
> identical source tree evidence reused; no builds rerun for DCO packaging.
> - All eight intermediate apply/config/relevant builds.
> - Independent transport/core and IIO module link/MODPOST.
> - GCC/Clang W=1 vmlinux and modules.
> - GCC and Clang KUnit: core 16 + IIO 2, all 18 pass in each run.
> - Core-only intermediate KUnit: all 16 pass.
> - ARM64 Image, selected OSF module and Pi4 DTB.
> - Targeted DT binding/style and IIO documentation.
>
> The terminology-only diff was verified mechanically. Wire semantics and
> data-path behavior are unchanged; the receive diagnostic key is validated=.
> Hardware testing was not repeated for this terminology revision or this
> post-DCO packaging audit; previous hardware evidence remains historical.
>
> Human DCO is complete. No email has been sent.
>
> Jinseob Kim (8):
> dt-bindings: iio: add Open Sensor Fusion device
> Documentation: iio: add Open Sensor Fusion driver overview
> iio: osf: add protocol decoding
> iio: osf: add validated stream parser
> iio: osf: add UART transport and core receive path
> iio: osf: add IIO devices from capability reports
> iio: osf: add core KUnit tests
> iio: osf: add IIO KUnit tests
>
> .../bindings/iio/opensensorfusion,osf.yaml | 52 +
> .../devicetree/bindings/vendor-prefixes.yaml | 2 +
> Documentation/iio/index.rst | 1 +
> Documentation/iio/open-sensor-fusion.rst | 77 ++
> MAINTAINERS | 8 +
> drivers/iio/Kconfig | 1 +
> drivers/iio/Makefile | 1 +
> drivers/iio/opensensorfusion/Kconfig | 27 +
Any reason not to push it one level deeper and put it under IMUs?
I'm not that keen to grow the top level menu for this as all the
other entries are about type of sensor, not one specific sensor.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 7/8] iio: osf: add core KUnit tests
2026-09-18 18:24 ` [PATCH v10 7/8] iio: osf: add core KUnit tests Jinseob Kim
@ 2026-09-20 2:12 ` Jonathan Cameron
0 siblings, 0 replies; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 2:12 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:45 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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 <kimjinseob88@gmail.com>
https://sashiko.dev/#/patchset/cover.1789753020.git.kimjinseob88%40gmail.com
The lock one looks right and needs to be fixed. Not sure on the others
(I didn't really look as running out of time today!)
There is a lot here and that is likely to mean it doesn't all get detailed
review. That isn't necessarily a problem though and they are generally good to
have. Just something to be aware of.
Jonathan
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 8/8] iio: osf: add IIO KUnit tests
2026-09-18 18:24 ` [PATCH v10 8/8] iio: osf: add IIO " Jinseob Kim
@ 2026-09-20 2:18 ` Jonathan Cameron
0 siblings, 0 replies; 25+ messages in thread
From: Jonathan Cameron @ 2026-09-20 2:18 UTC (permalink / raw)
To: Jinseob Kim
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
On Sat, 19 Sep 2026 03:24:46 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:
> 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 <kimjinseob88@gmail.com>
So there are a number of generic tests in here (using osf as a device)
that we should probably be looking to pull out as independent self tests
if we can. Good to have them as something to build on though and
there are of course some driver interactions that matter.
Anyhow, I'd like input from others on how they think we should ensure
the coverage you have here is present but avoid duplication on a per
driver basis (assuming others decide to also start putting together
comprehensive self tests like you have done!).
I'd like to find time to take a close look at these tests but
it won't happen for a while as there is simply to much activity on
the list + I'm travelling in the next few weeks (including Linux
Plumbers in Prague in early October). So it may be that we end
up merging the driver ahead of these tests.
Jonathan
> ---
> 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/opensensorfusion/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 tests
> + acceptance, direct-read cache, and IIO buffer paths. The tests
> exercise accepted, ignored, and rejected sample handling without
> exposing additional production interfaces.
>
> diff --git a/drivers/iio/opensensorfusion/Makefile b/drivers/iio/opensensorfusion/Makefile
> index c2f5ba065a62..6a28b14cae1f 100644
> --- a/drivers/iio/opensensorfusion/Makefile
> +++ b/drivers/iio/opensensorfusion/Makefile
> @@ -4,4 +4,4 @@ obj-$(CONFIG_OPEN_SENSOR_FUSION) += open-sensor-fusion.o
>
> open-sensor-fusion-y := osf_core.o osf_iio.o osf_protocol.o osf_serdev.o \
> osf_stream.o
> -open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) += osf_core_test.o
> +open-sensor-fusion-$(CONFIG_OPEN_SENSOR_FUSION_KUNIT_TEST) += osf_core_test.o osf_iio_test.o
> diff --git a/drivers/iio/opensensorfusion/osf_iio_test.c b/drivers/iio/opensensorfusion/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 <kunit/device.h>
> +#include <kunit/test.h>
> +
> +#include <linux/bitmap.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/buffer_impl.h>
> +#include <linux/iio/iio.h>
> +#include <linux/kthread.h>
> +#include <linux/string.h>
> +
> +#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_dev,
> + const s32 *values, unsigned int channels,
> + unsigned int mask, bool ts)
> +{
> + struct iio_buffer *buffer = indio_dev->buffer;
> + unsigned int data_bytes = hweight32(mask) * sizeof(s32);
> + unsigned int ts_offset = ALIGN(data_bytes, 8);
> + unsigned int scan_bytes = ts ? ts_offset + 8 : data_bytes;
> + unsigned int offset = 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 = 0; i < channels; i++)
> + if (mask & BIT(i))
> + set_bit(i, (unsigned long *)buffer->scan_mask);
> + buffer->scan_timestamp = ts;
> + ret = iio_update_buffers(indio_dev, buffer, NULL);
> + KUNIT_ASSERT_EQ(test, ret, 0);
> +
> + kunit_info(test, "device=%s mask=%u ts=%u bytes=%u\n",
> + indio_dev->name, mask, ts, scan_bytes);
> + memset(scan, 0xa5, sizeof(scan));
> + before = iio_get_time_ns(indio_dev);
> + osf_test_poison_stack();
> + ret = osf_iio_push_sample(indio_dev, values, channels);
> + after = iio_get_time_ns(indio_dev);
> + KUNIT_EXPECT_EQ(test, ret, 0);
> + KUNIT_EXPECT_EQ(test, buffer->bytes_per_datum, scan_bytes);
> + ret = iio_pop_from_buffer(buffer, scan);
> + KUNIT_EXPECT_EQ(test, ret, 0);
> + if (ret)
> + goto disable;
> +
> + for (unsigned int i = 0; i < channels; i++) {
> + if (!(mask & BIT(i)))
> + continue;
> + KUNIT_EXPECT_MEMEQ(test, scan + offset, &values[i], sizeof(s32));
> + offset += sizeof(s32);
> + }
> + if (ts) {
> + for (unsigned int i = 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 = 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[] = {
> + OSF_SENSOR_ACCELEROMETER, OSF_SENSOR_GYROSCOPE,
> + OSF_SENSOR_MAGNETOMETER, OSF_SENSOR_TEMPERATURE,
> + };
> + const s32 values[] = { 101, -202, 303 };
> + struct osf_capability_entry entry = {
> + .sample_format = OSF_SAMPLE_FORMAT_S32,
> + .scale_nano = 1000000,
> + };
> + struct osf_device *osf;
> + struct device *dev;
> +
> + dev = kunit_device_register(test, "osf-iio");
> + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
> + osf = kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, osf);
> + osf_core_init(osf, dev);
> +
> + for (unsigned int t = 0; t < ARRAY_SIZE(types); t++) {
> + unsigned int channels = t == 3 ? 1 : 3;
> + struct iio_dev *indio_dev;
> + int ret;
> +
> + entry.sensor_type = types[t];
> + entry.channel_count = channels;
> + ret = osf_iio_register_sensor(dev, &entry, osf, &indio_dev);
> + KUNIT_ASSERT_EQ(test, ret, 0);
> + for (unsigned int mask = 1; mask < BIT(channels); mask++)
> + for (unsigned int ts = 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 *buffer)
> +{
> + 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 = 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 = 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 = 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 = data;
> + const s32 values[] = { 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 = data;
> +
> + complete(&race->config_started);
> + if (race->unregister) {
> + osf_iio_unregister_sensor(race->indio_dev);
> + race->config_result = 0;
> + } else {
> + race->config_result = 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 = 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 = wait_for_completion_timeout(&race->config_done, msecs_to_jiffies(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 producer. */
> +static void osf_iio_buffer_lifetime_test(struct kunit *test)
> +{
> + struct osf_capability_entry entry = {
> + .sensor_type = OSF_SENSOR_ACCELEROMETER,
> + .channel_count = 3,
> + .sample_format = OSF_SAMPLE_FORMAT_S32,
> + .scale_nano = 1000000,
> + };
> + const s32 values[] = { 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 = kunit_device_register(test, "osf-race");
> + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
> + osf = kunit_kzalloc(test, sizeof(*osf), GFP_KERNEL);
> + race = 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 = osf_iio_register_sensor(dev, &entry, osf, &indio_dev);
> + KUNIT_ASSERT_EQ(test, ret, 0);
> + buffer = indio_dev->buffer;
> + race->indio_dev = indio_dev;
> + race->original = buffer->access;
> + race->access = *buffer->access;
> + race->access.store_to = osf_iio_test_store;
> + race->access.enable = osf_iio_test_enable;
> + race->access.disable = osf_iio_test_disable;
> + buffer->access = &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 = 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), 0UL);
> + osf_iio_expect_quiesce(test, race);
> +
> + for (unsigned int i = 0; i < 100; i++) {
> + bitmap_zero((unsigned long *)buffer->scan_mask, iio_get_masklength(indio_dev));
> + *(unsigned long *)buffer->scan_mask = (i % 7) + 1;
> + buffer->scan_timestamp = 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 = -EIO;
> + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), -EIO);
> + stores = 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 = 0;
> + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, buffer, NULL), 0);
> + race->disable_error = -EIO;
> + KUNIT_EXPECT_EQ(test, iio_update_buffers(indio_dev, NULL, buffer), -EIO);
> + stores = 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 = 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), 0UL);
> + race->unregister = 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 = race->original;
> + put_device(&indio_dev->dev);
> +}
> +
> +static struct kunit_case osf_iio_test_cases[] = {
> + KUNIT_CASE(osf_iio_scan_bytes_test),
> + KUNIT_CASE(osf_iio_buffer_lifetime_test),
> + { }
> +};
> +
> +static struct kunit_suite osf_iio_test_suite = {
> + .name = "osf-iio",
> + .test_cases = osf_iio_test_cases,
> +};
> +
> +kunit_test_suite(osf_iio_test_suite);
> +
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview
2026-09-20 1:12 ` Jonathan Cameron
@ 2026-09-20 3:57 ` Kim Jinseob
0 siblings, 0 replies; 25+ messages in thread
From: Kim Jinseob @ 2026-09-20 3:57 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
Hi Jonathan,
both points make sense.
The capability reports are complete inventory snapshots rather than
incremental fragments. An empty or unsupported-only initial report
does not establish a supported inventory, so discovery remains open.
Once a complete report with supported descriptors establishes the
inventory for the host session, later reports are still processed:
equivalent inventories are idempotent, while a supported key
appearing/disappearing or changing its shape, format, or scale is
treated as an inventory change and faults the session.
I'll clarify that distinction in the next revision, and also change
“Unsupported majors” to “Unsupported major versions”.
Thanks,
Jinseob
2026년 9월 20일 (일) 오전 10:12, Jonathan Cameron <jic23@kernel.org>님이 작성:
>
> On Sat, 19 Sep 2026 03:24:40 +0900
> Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> > 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 <kimjinseob88@gmail.com>
>
> Hi Jinseob,
>
> Just a couple of minor things inline.
>
> Thanks,
>
> Jonathan
>
> > ---
> > 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/open-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
> > +==================
> > +
> > +Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO driver
> > +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, compatibility
> > +rules, reserved fields and physical units. This document describes the Linux
> > +receiver profile and its mapping to IIO.
> > +
> > +.. _specification: https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/spec/osf-d2h-0.0.md
> > +
> > +Supported receive profile
> > +-------------------------
> > +
> > +The driver supports the fixed OSF0 frame layout with protocol major version 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
>
> If you spin again "Unsupported major versions and.."
>
> > +unknown message types are ignored. Unsupported capability entries are skipped
> > +individually. A CRC-valid application rejection consumes the complete frame;
> > +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 unit.
> > +Buffered samples use signed 32-bit values with native CPU endianness. Active
> > +channels are packed into the scan, with initialized padding before an optional
> > +64-bit timestamp.
> > +
> > +Discovery and session lifetime
> > +------------------------------
> > +
> > +The driver validates all supported descriptors before registering IIO devices.
> > +An empty or entirely unsupported initial report leaves discovery open for a
> > +later supported report.
>
> So if the device can send multiple discovery reports, this implies if we
> recognise something in the first one we will stop looking at later ones?
> That seems odd given if we don't recognise anything we keep looking.
> If I'm reading too much into this text, then please tweak it to make
> it clear why this doesn't matter.
>
> > Supported descriptors must advertise a nonzero scale.
> > +A sample's channel count, format and scale must match its registered descriptor
> > +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 entry
> > +order. Reserved padding, advisory flags and unsupported additions do not change
> > +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 starts
> > +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 discontinuities
> > +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 <kimjinseob88@gmail.com>
> > S: Maintained
> > F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
> > +F: Documentation/iio/open-sensor-fusion.rst
> > K: opensensorfusion
> >
> > OPENCOMPUTE PTP CLOCK DRIVER
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device
2026-09-20 1:14 ` Jonathan Cameron
@ 2026-09-20 4:01 ` Kim Jinseob
0 siblings, 0 replies; 25+ messages in thread
From: Kim Jinseob @ 2026-09-20 4:01 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
> Sashiko raises a reasonable question about what you'd do if it turned
> out your current OSF GREEN doesn't quite comply with the specification
> or needs some additional quirks. Does it make sense to have a more
> specific compatible with a fallback to the generic one?
Hi Jonathan
With that in mind, I agree that a specific compatible with the generic
OSF interface as a fallback is the better model.
OSF GREEN is one hardware variant in a family of boards with different
hardware capabilities and configurations. For example, GREEN does not
include RTK-GPS hardware, while another variant does. The firmware
still exposes the common OSF host interface and runtime capability
discovery, but the underlying boards are distinct hardware
implementations.
For the next revision I'll describe the current board as:
compatible = "opensensorfusion,osf-green",
"opensensorfusion,osf";
The first string identifies the specific hardware, while the second
remains the common host-interface fallback. Future hardware variants
can get their own specific compatibles as they are supported, without
encoding their sensor inventory or protocol version into Device Tree.
OSF0 and the protocol major/minor will remain runtime wire-protocol details.
thanks,
Jinseob
2026년 9월 20일 (일) 오전 10:14, Jonathan Cameron <jic23@kernel.org>님이 작성:
>
> On Sat, 19 Sep 2026 03:24:39 +0900
> Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> > 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.
> Sashiko raises a reasonable question about what you'd do if it turned
> out your current OSF GREEN doesn't quite comply with the specification
> or needs some additional quirks. Does it make sense to have a more
> specific compatible with a fallback to the generic one?
>
> Or given this is mostly about device firmware maybe this is fine.
>
> Either way - would be good to add a note on why that doesn't make
> sense in this patch description.
>
> Thanks,
>
> Jonathan
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 3/8] iio: osf: add protocol decoding
2026-09-20 1:27 ` Jonathan Cameron
@ 2026-09-20 4:12 ` Kim Jinseob
0 siblings, 0 replies; 25+ messages in thread
From: Kim Jinseob @ 2026-09-20 4:12 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
> The above fully assigns frame so I'd do similar to you have elsewhere
> *frame = (struct osf_frame) {
> .protocol_major = ...
> ...
> };
> Both makes it more readable and makes it clear you aren't leaving
> any existing data in place.
Agreed. I'll change this to a designated initializer in the next revision.
> I don't hugely mind as it's your code + spec to maintain but generally
> for a spec with records like this I'd expect it to be possible to extend
> the structure without it being a breaking spec change. As such
> I'd kind of expect the check to be that the payload_len was at least
> as big as expected len. Larger would be fine but we'd ignore anything
> there.
I'd prefer to keep the exact-length check for the existing OSF0
message IDs. The fixed OSF0 specification deliberately keeps the
baseline message layouts exact, with optional extensions using newly
allocated message IDs rather than trailing data on an existing message
ID.
So I don't plan to change this to an at-least-length check in the next revision.
> Entirely correctly I think you don't do any checks or reads of reserved values.
> As such why copy them around? I'd just drop that field from your decoded
> structures. Not a huge saving but why carry irrelevant data around!
Agreed. I'll remove the reserved fields from the decoded structures.
The reserved bytes will still be covered by the CRC and tolerated on
receive, but they don't need to be propagated past the protocol
decoder.
Thanks,
Jinseob
2026년 9월 20일 (일) 오전 10:27, Jonathan Cameron <jic23@kernel.org>님이 작성:
>
> On Sat, 19 Sep 2026 03:24:41 +0900
> Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> > 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 <kimjinseob88@gmail.com>
> Hi Jinseob
>
> A few things inline. Some are about what I'd kind of expect from
> how specs are often defined to leave a bit of flexibility and reduce
> the need to update drivers for new stuff being added.
>
> If you don't want to go that way I don't really mind.
>
> Otherwise main thing here is why carry reserved data to next layer
> given it is meant to be ignored. Drop it down here in the protocol decode.
>
> Thanks,
>
> Jonathan
>
> > diff --git a/drivers/iio/opensensorfusion/osf_protocol.c b/drivers/iio/opensensorfusion/osf_protocol.c
> > new file mode 100644
> > index 000000000000..e0d7c7a9ebd7
> > --- /dev/null
> > +++ b/drivers/iio/opensensorfusion/osf_protocol.c
>
> > +
> > +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) != OSF_FRAME_MAGIC)
> > + return -EPROTO;
> > +
> > + if (get_unaligned_le16(buf + 6) != OSF_FRAME_HEADER_LEN)
> > + return -EPROTO;
> > +
> > + payload_len = get_unaligned_le32(buf + 10);
> > + if (payload_len > len - OSF_FRAME_MIN_LEN)
> > + return -EMSGSIZE;
> > +
> > + total_len = OSF_FRAME_HEADER_LEN + payload_len + OSF_FRAME_CRC_LEN;
> > + expected_crc = osf_crc32_ieee(buf, OSF_FRAME_HEADER_LEN + payload_len);
> > + actual_crc = get_unaligned_le32(buf + OSF_FRAME_HEADER_LEN + payload_len);
> > +
> > + if (actual_crc != expected_crc)
> > + return -EBADMSG;
> > +
> > + frame->protocol_major = buf[4];
> > + frame->protocol_minor = buf[5];
> > + frame->message_type = get_unaligned_le16(buf + 8);
> > + frame->payload_len = payload_len;
> > + frame->sequence = get_unaligned_le64(buf + 14);
> > + frame->timestamp_us = get_unaligned_le64(buf + 22);
> > + frame->flags = get_unaligned_le32(buf + 30);
> > + frame->reserved = get_unaligned_le32(buf + 34);
>
> As below. I'm not sure what benefit of keeping reserved around is.
>
> > + frame->payload = buf + OSF_FRAME_HEADER_LEN;
> > + frame->crc = actual_crc;
>
> The above fully assigns frame so I'd do similar to you have elsewhere
> *frame = (struct osf_frame) {
> .protocol_major = ...
> ...
> };
> Both makes it more readable and makes it clear you aren't leaving
> any existing data in place.
>
>
> > + *frame_len = total_len;
> > +
> > + 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 != OSF_MSG_CAPABILITY_REPORT)
> > + return -EPROTO;
> > +
> > + if (frame->payload_len < OSF_CAP_REPORT_BASE_LEN)
> > + return -EMSGSIZE;
> > +
> > + payload = frame->payload;
> > + capability_count = get_unaligned_le16(payload);
> > +
> > + expected_len = OSF_CAP_REPORT_BASE_LEN +
> > + capability_count * OSF_CAP_SENSOR_ENTRY_LEN;
> > + if (frame->payload_len != expected_len)
>
> I don't hugely mind as it's your code + spec to maintain but generally
> for a spec with records like this I'd expect it to be possible to extend
> the structure without it being a breaking spec change. As such
> I'd kind of expect the check to be that the payload_len was at least
> as big as expected len. Larger would be fine but we'd ignore anything
> there.
>
> > + return -EMSGSIZE;
> > +
> > + *report = (struct osf_capability_report) {
> > + .capability_count = capability_count,
> > + .entries = 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 >= report->capability_count)
> > + return -ERANGE;
> > +
> > + payload = report->entries + index * OSF_CAP_SENSOR_ENTRY_LEN;
> > + *entry = (struct osf_capability_entry) {
> > + .sensor_type = get_unaligned_le16(payload),
> > + .sensor_index = get_unaligned_le16(payload + 2),
> > + .channel_count = get_unaligned_le16(payload + 4),
> > + .sample_format = get_unaligned_le16(payload + 6),
> > + .scale_nano = get_unaligned_le32(payload + 8),
> > + .flags = get_unaligned_le32(payload + 12),
> > + .reserved = get_unaligned_le32(payload + 16),
>
> Entirely correctly I think you don't do any checks or reads of reserved values.
> As such why copy them around? I'd just drop that field from your decoded
> structures. Not a huge saving but why carry irrelevant data around!
>
> Jonathan
>
> > + };
> > +
> > + return 0;
> > +}
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 4/8] iio: osf: add validated stream parser
2026-09-20 1:30 ` Jonathan Cameron
@ 2026-09-20 4:12 ` Kim Jinseob
0 siblings, 0 replies; 25+ messages in thread
From: Kim Jinseob @ 2026-09-20 4:12 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
> You could reorder these two functions and call stream_reset from
> stream_init. Would make it clear what getting to a 'clean' state
> means.
Agreed. I'll move osf_stream_reset() before osf_stream_init() and use
it from the init path in the next revision.
Thanks,
Jinseob
2026년 9월 20일 (일) 오전 10:31, Jonathan Cameron <jic23@kernel.org>님이 작성:
>
> On Sat, 19 Sep 2026 03:24:42 +0900
> Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> > 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 <kimjinseob88@gmail.com>
> Just one trivial thing.
>
> Thanks,
>
> Jonathan
>
>
> > diff --git a/drivers/iio/opensensorfusion/osf_stream.c b/drivers/iio/opensensorfusion/osf_stream.c
> > new file mode 100644
> > index 000000000000..e262415e69b7
> > --- /dev/null
> > +++ b/drivers/iio/opensensorfusion/osf_stream.c
>
>
> > +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 = receive_frame;
> > + stream->frame_context = frame_context;
> > + stream->len = 0;
> > + memset(&stream->stats, 0, sizeof(stream->stats));
> > +}
> > +
> > +void osf_stream_reset(struct osf_stream *stream)
> > +{
> > + if (!stream)
> > + return;
> > +
> > + stream->len = 0;
> > + memset(&stream->stats, 0, sizeof(stream->stats));
> > +}
>
> You could reorder these two functions and call stream_reset from
> stream_init. Would make it clear what getting to a 'clean' state
> means.
>
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 6/8] iio: osf: add IIO devices from capability reports
2026-09-20 2:03 ` Jonathan Cameron
@ 2026-09-20 4:20 ` Kim Jinseob
2026-09-20 5:07 ` Kim Jinseob
0 siblings, 1 reply; 25+ messages in thread
From: Kim Jinseob @ 2026-09-20 4:20 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
> Using an extra lock is one solution I suppose to the risk
> of this racing with the buffer tear down and the problems
> around active_scan_mask being freed. Could you use
>
> if (iio_device_try_claim_buffer_mode(indio_dev))
>
> and later a release on that to achieve the same result?
>
> Only the ade9000 is doing similar at the moment so would need
> some testing to make sure no lock inversions occur in general
> but given you don't take any other locks in here it should be fine.
I think that should be a better fit here. I'll test replacing the
private buffer lock and active flag with
iio_device_try_claim_buffer_mode() , iio_device_release_buffer_mode(),
including the existing buffer disable/unregister race coverage and
lockdep checks.
If that confirms the expected serialization against buffer
reconfiguration, I'll use that approach in the next revision.
Thanks,
Jinseob
2026년 9월 20일 (일) 오전 11:03, Jonathan Cameron <jic23@kernel.org>님이 작성:
>
> On Sat, 19 Sep 2026 03:24:44 +0900
> Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> > 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.
>
> No need to talk about what other patches do and that a series is functionally
> complete in a patch. If you want to do it, under the --- so it doesn't end
> up in the git log.
>
> >
> > Assisted-by: LLM
> > Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
>
> A few things inline. Biggest one is a question (I'm not sure) on
> whether you can use a claim on buffered mode (which will hold it in that
> if it succeeds until released) is sufficient to replace the extra lock you
> have to block that transition.
>
> Thanks,
>
> Jonathan
>
>
>
> > diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/osf_iio.c
> > new file mode 100644
> > index 000000000000..f4011b8fae14
> > --- /dev/null
> > +++ b/drivers/iio/opensensorfusion/osf_iio.c
> ...
>
> > +
> > +struct osf_iio_scan_3axis {
> > + s32 values[3];
> > + u32 padding;
> No needed unless you explicitly write into padding for some reason.
> C rules add it anyway and then we don't need to make sure it remains
> correct if this structure changes in future.
> > + aligned_s64 timestamp;
> > +};
> > +
> > +struct osf_iio_scan_1axis {
> > + s32 value;
> > + u32 padding;
> As above, unneeded.
> > + aligned_s64 timestamp;
> > +};
>
> Given I think these are only used in one place, moving them to there would
> avoid need for named structure types.
>
> > +
> > +#define OSF_MOD_CHAN(_type, _mod, _idx) \
> > + { \
> > + .type = (_type), \
> > + .modified = 1, \
> > + .channel2 = (_mod), \
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> > + .scan_index = (_idx), \
> > + .scan_type = { \
> > + .sign = 's', \
> > + .realbits = 32, \
> > + .storagebits = 32, \
> > + .endianness = IIO_CPU, \
> > + }, \
> > + }
> > +
> > +#define OSF_CHAN(_type, _idx) \
>
> Given this is not much used, you could use the IIO_NO_MOD modifier as a gate
> on setting .modified = (_mod != IIO_NO_MOD) ? 1 : 0,
> and have just one macro.
>
> > + { \
> > + .type = (_type), \
> > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> > + .scan_index = (_idx), \
> > + .scan_type = { \
> > + .sign = 's', \
> > + .realbits = 32, \
> > + .storagebits = 32, \
> > + .endianness = IIO_CPU, \
> > + }, \
> > + }
> > +
> > +static const struct iio_chan_spec osf_accel_channels[] = {
> > + 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[] = {
> > + 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[] = {
> > + 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[] = {
> > + OSF_CHAN(IIO_TEMP, 0),
> > + IIO_CHAN_SOFT_TIMESTAMP(1),
> > +};
> > +
> > +static const struct osf_iio_sensor_spec osf_iio_sensor_specs[] = {
> > + {
> > + .sensor_type = OSF_SENSOR_ACCELEROMETER,
> > + .channel_count = 3,
> > + .name = "osf-accel",
> > + .channels = osf_accel_channels,
> > + .num_channels = ARRAY_SIZE(osf_accel_channels),
> > + },
> > + {
> > + .sensor_type = OSF_SENSOR_GYROSCOPE,
> > + .channel_count = 3,
> > + .name = "osf-gyro",
> > + .channels = osf_gyro_channels,
> > + .num_channels = ARRAY_SIZE(osf_gyro_channels),
> > + },
> > + {
> > + .sensor_type = OSF_SENSOR_MAGNETOMETER,
> > + .channel_count = 3,
> > + .name = "osf-magn",
> > + .channels = osf_mag_channels,
> > + .num_channels = ARRAY_SIZE(osf_mag_channels),
> > + },
> > + {
> > + .sensor_type = OSF_SENSOR_TEMPERATURE,
> > + .channel_count = 1,
> > + .name = "osf-temp",
> > + .channels = osf_temp_channels,
> > + .num_channels = 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 = 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) {
> > + if (osf_iio_sensor_specs[i].sensor_type == sensor_type &&
> > + osf_iio_sensor_specs[i].channel_count == 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
>
> return osf_iio_find_sensor_spec(sensor_type, channel_count) != NULL;
>
> > +}
>
> > +
> > +static int osf_iio_buffer_postenable(struct iio_dev *indio_dev)
> > +{
> > + struct osf_iio_state *state = iio_priv(indio_dev);
> > +
> > + guard(mutex)(&state->buffer_lock);
>
> These transistions are already guarded by the core, so see the question later
> on whether you can just claim buffered mode in the data handler
> to avoid the need for this. That might not work but the way you did
> this got me thinking about a more general solution to the problem.
>
> > + state->buffer_active = true;
> > +
> > + return 0;
> > +}
> > +
> > +static int osf_iio_buffer_predisable(struct iio_dev *indio_dev)
> > +{
> > + struct osf_iio_state *state = iio_priv(indio_dev);
> > +
> > + /* Wait for the current push before the IIO core changes its buffers. */
> > + guard(mutex)(&state->buffer_lock);
> > + state->buffer_active = false;
> > +
> > + return 0;
> > +}
>
>
> > +
> > +int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values,
> > + u16 channel_count)
> > +{
> > + struct osf_iio_state *state = iio_priv(indio_dev);
> > + s64 timestamp;
> > +
> > + if (channel_count != state->spec->channel_count)
> > + return -EPROTO;
> > +
> > + guard(mutex)(&state->buffer_lock);
>
> Using an extra lock is one solution I suppose to the risk
> of this racing with the buffer tear down and the problems
> around active_scan_mask being freed. Could you use
>
> if (iio_device_try_claim_buffer_mode(indio_dev))
> and later a release on that to achieve the same result?
>
> Only the ade9000 is doing similar at the moment so would need
> some testing to make sure no lock inversions occur in general
> but given you don't take any other locks in here it should be fine.
>
>
> > + if (!state->buffer_active || !iio_buffer_enabled(indio_dev))
> > + return 0;
> > +
> > + timestamp = iio_get_time_ns(indio_dev);
> > +
> > + switch (channel_count) {
> > + case 1: {
> > + struct osf_iio_scan_1axis scan = {
> > + .value = values[0],
> > + };
> As suggested above, this structure type is only used locally here. So
> make the definition local as well.
> struct {
> s32 value;
> aligned_s64 timestamp;
> } scan = { };
>
> > +
> > + return iio_push_to_buffers_with_ts(indio_dev, &scan,
> > + sizeof(scan), timestamp);
> > + }
> > + case 3: {
> > + struct osf_iio_scan_3axis scan = {
> > + .values = { },
>
> Setting values doesn't add anything useful as = { }
> zeros the whole structure (including any holes - they kernel is
> built with specific compiler options to ensure that last bit
> as it wasn't required in older c specs).
>
> > + };
> > + unsigned int channel, index = 0;
>
> Please split declarations that assign and ones that don't on to separate lines.
> Ends up a little more readable.
> unsigned int index = 0;
> unsigned int channel;
>
> > +
> > + /* Pack the active channels; unused storage remains initialized. */
> > + for_each_set_bit(channel, indio_dev->active_scan_mask, channel_count)
> > + scan.values[index++] = 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_serdev.c b/drivers/iio/opensensorfusion/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;
>
> Just name it 'lock' given it has scope beyond rx. Do that in earlier patch
> though rather than changing it here.
>
> > };
>
> >
> > @@ -139,5 +144,5 @@ static struct serdev_device_driver osf_serdev_driver = {
> > };
> > module_serdev_device_driver(osf_serdev_driver);
> >
> > -MODULE_DESCRIPTION("Open Sensor Fusion UART receiver");
> > +MODULE_DESCRIPTION("Open Sensor Fusion IIO driver");
>
> Just have that being slightly missleading in earlier patch to reduce churn here.
>
> > MODULE_LICENSE("GPL");
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 0/8] iio: add Open Sensor Fusion UART support
2026-09-20 2:05 ` [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jonathan Cameron
@ 2026-09-20 4:22 ` Kim Jinseob
0 siblings, 0 replies; 25+ messages in thread
From: Kim Jinseob @ 2026-09-20 4:22 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
> Any reason not to push it one level deeper and put it under IMUs?
> I'm not that keen to grow the top level menu for this as all the
> other entries are about type of sensor, not one specific sensor.
Yes, I agree that adding an Open Sensor Fusion entry at the IIO top
level is not ideal.
My hesitation with putting it under imu/ is that Open Sensor Fusion is
not intended to be an IMU. The current Linux profile exposes
accelerometer, gyroscope, magnetometer and temperature data, but the
hardware/firmware architecture is a general sensor hub and fusion
device. Other OSF hardware variants can aggregate sources such as
pressure sensors, GNSS/RTK-GNSS, motor encoders and LiDAR in addition
to inertial sensors.
Would drivers/iio/common/opensensorfusion/ be a better fit?
There are already sensor-hub/common implementations such as the
ChromeOS EC sensors and Samsung SSP sensor hub under
drivers/iio/common/.
That would avoid growing the IIO top-level menu without classifying
the device itself as an IMU.
Thanks,
Jinseob
2026년 9월 20일 (일) 오전 11:05, Jonathan Cameron <jic23@kernel.org>님이 작성:
>
> On Sat, 19 Sep 2026 03:24:38 +0900
> Jinseob Kim <kimjinseob88@gmail.com> wrote:
>
> > Specification status: OSF-D2H 0.0 spec-1 has completed project technical
> > stability review, has been adopted by the project owner, and is published
> > as a fixed specification.
> >
> > Canonical specification:
> > https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/spec/osf-d2h-0.0.md
> >
> > Errata process:
> > https://github.com/opensensorfusion/opensensorfusion-protocol/blob/ca9cdea1ae550c2b4d6f29f87877adae99470744/errata/README.md
> >
> > Current adoption/publication record:
> > https://github.com/opensensorfusion/opensensorfusion-protocol/blob/0cabccf63ac01d0eb82dd9882739ff58eed4a76c/reviews/publication-record-20260918.md
> >
> > The specification was frozen before adoption/publication, so its embedded
> > status snapshot is intentionally historical. The dated publication record
> > above establishes the current adopted/public state. This is project review
> > and adoption, not an external maintainer approval.
> >
> > This series adds the Open Sensor Fusion UART receive path and IIO devices
> > discovered from capability reports. It exposes accelerometer, gyroscope,
> > magnetometer and temperature data through RAW/SCALE and buffered scans.
> > Device Tree describes the sensor hub; its individual streams are discovered
> > at runtime.
> >
> > The receiver validates supported descriptors and sample scales, keeps
> > discovery open after empty or unsupported initial inventories, and checks
> > repeated inventories before allowing new data to use registered metadata.
> > A supported descriptor changing meaning faults the session until an
> > explicit teardown and rebind. Existing v9 cache, scan-layout and buffer
> > lifetime fixes are retained.
> >
> > Based on Jonathan Cameron's IIO testing branch:
> > 69fa76f0af3414cc189c3b0b807cb59e327ecc00
> >
> > Changes since v9:
> > - Publish and reference the reviewed/adopted OSF-D2H 0.0 specification,
> > errata process and dated publication evidence.
> > - Tolerate reserved padding, validate descriptor/sample scales, keep
> > discovery open after empty/unsupported reports, and compare repeated
> > capability reports.
> > - Fail closed on changed descriptor meaning instead of publishing data
> > with stale metadata.
> > - Add focused KUnit coverage for these lifecycle and validation cases.
> > - Use managed UART/IIO/power teardown and dev_warn_probe() for the
> > controller baud-rate warning, addressing Andy's probe-path feedback.
> > - Split the former combined UART/core/IIO driver patch into transport/core,
> > IIO registration, core KUnit, and IIO KUnit patches following review.
> > - Use validated/unvalidated terminology for framing/CRC results throughout
> > the stream/core/transport code, tests and diagnostics. CRC detects
> > accidental corruption; it does not provide cryptographic authentication.
> >
> > Prior validation on the identical source tree:
> > identical source tree evidence reused; no builds rerun for DCO packaging.
> > - All eight intermediate apply/config/relevant builds.
> > - Independent transport/core and IIO module link/MODPOST.
> > - GCC/Clang W=1 vmlinux and modules.
> > - GCC and Clang KUnit: core 16 + IIO 2, all 18 pass in each run.
> > - Core-only intermediate KUnit: all 16 pass.
> > - ARM64 Image, selected OSF module and Pi4 DTB.
> > - Targeted DT binding/style and IIO documentation.
> >
> > The terminology-only diff was verified mechanically. Wire semantics and
> > data-path behavior are unchanged; the receive diagnostic key is validated=.
> > Hardware testing was not repeated for this terminology revision or this
> > post-DCO packaging audit; previous hardware evidence remains historical.
> >
> > Human DCO is complete. No email has been sent.
> >
> > Jinseob Kim (8):
> > dt-bindings: iio: add Open Sensor Fusion device
> > Documentation: iio: add Open Sensor Fusion driver overview
> > iio: osf: add protocol decoding
> > iio: osf: add validated stream parser
> > iio: osf: add UART transport and core receive path
> > iio: osf: add IIO devices from capability reports
> > iio: osf: add core KUnit tests
> > iio: osf: add IIO KUnit tests
> >
> > .../bindings/iio/opensensorfusion,osf.yaml | 52 +
> > .../devicetree/bindings/vendor-prefixes.yaml | 2 +
> > Documentation/iio/index.rst | 1 +
> > Documentation/iio/open-sensor-fusion.rst | 77 ++
> > MAINTAINERS | 8 +
> > drivers/iio/Kconfig | 1 +
> > drivers/iio/Makefile | 1 +
> > drivers/iio/opensensorfusion/Kconfig | 27 +
> Any reason not to push it one level deeper and put it under IMUs?
> I'm not that keen to grow the top level menu for this as all the
> other entries are about type of sensor, not one specific sensor.
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v10 6/8] iio: osf: add IIO devices from capability reports
2026-09-20 4:20 ` Kim Jinseob
@ 2026-09-20 5:07 ` Kim Jinseob
0 siblings, 0 replies; 25+ messages in thread
From: Kim Jinseob @ 2026-09-20 5:07 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, dlechner, nuno.sa, andriy.shevchenko, linux-kernel,
rdunlap, joshua.crofts1, u.kleine-koenig, julianbraha, robh,
krzk+dt, conor+dt, devicetree, corbet, skhan, linux-doc
> Using an extra lock is one solution I suppose to the risk
> of this racing with the buffer tear down and the problems
> around active_scan_mask being freed. Could you use
>
> if (iio_device_try_claim_buffer_mode(indio_dev))
>
> and later a release on that to achieve the same result?
I looked into this against the current IIO core and tested the relevant paths.
iio_device_try_claim_buffer_mode() does hold the IIO mode lock, and
iio_update_buffers() takes the same lock, so it would serialize normal
buffer reconfiguration.
However, the unregister path does not hold that mode lock while
disabling/deactivating the buffers, and that path can free
active_scan_mask. Because the OSF receive path can still have an
admitted buffer push at that point, the mode claim alone does not
appear to provide the same lifetime guarantee as the existing private
lock.
I therefore kept the private lock for now. I also reran the
disable/unregister race coverage with KASAN and lockdep enabled; those
tests pass without diagnostics.
Thanks,
Jinseob
2026년 9월 20일 (일) 오후 1:20, Kim Jinseob <kimjinseob88@gmail.com>님이 작성:
>
> > Using an extra lock is one solution I suppose to the risk
> > of this racing with the buffer tear down and the problems
> > around active_scan_mask being freed. Could you use
> >
> > if (iio_device_try_claim_buffer_mode(indio_dev))
> >
> > and later a release on that to achieve the same result?
> >
> > Only the ade9000 is doing similar at the moment so would need
> > some testing to make sure no lock inversions occur in general
> > but given you don't take any other locks in here it should be fine.
>
> I think that should be a better fit here. I'll test replacing the
> private buffer lock and active flag with
> iio_device_try_claim_buffer_mode() , iio_device_release_buffer_mode(),
> including the existing buffer disable/unregister race coverage and
> lockdep checks.
>
> If that confirms the expected serialization against buffer
> reconfiguration, I'll use that approach in the next revision.
>
> Thanks,
>
> Jinseob
>
> 2026년 9월 20일 (일) 오전 11:03, Jonathan Cameron <jic23@kernel.org>님이 작성:
> >
> > On Sat, 19 Sep 2026 03:24:44 +0900
> > Jinseob Kim <kimjinseob88@gmail.com> wrote:
> >
> > > 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.
> >
> > No need to talk about what other patches do and that a series is functionally
> > complete in a patch. If you want to do it, under the --- so it doesn't end
> > up in the git log.
> >
> > >
> > > Assisted-by: LLM
> > > Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
> >
> > A few things inline. Biggest one is a question (I'm not sure) on
> > whether you can use a claim on buffered mode (which will hold it in that
> > if it succeeds until released) is sufficient to replace the extra lock you
> > have to block that transition.
> >
> > Thanks,
> >
> > Jonathan
> >
> >
> >
> > > diff --git a/drivers/iio/opensensorfusion/osf_iio.c b/drivers/iio/opensensorfusion/osf_iio.c
> > > new file mode 100644
> > > index 000000000000..f4011b8fae14
> > > --- /dev/null
> > > +++ b/drivers/iio/opensensorfusion/osf_iio.c
> > ...
> >
> > > +
> > > +struct osf_iio_scan_3axis {
> > > + s32 values[3];
> > > + u32 padding;
> > No needed unless you explicitly write into padding for some reason.
> > C rules add it anyway and then we don't need to make sure it remains
> > correct if this structure changes in future.
> > > + aligned_s64 timestamp;
> > > +};
> > > +
> > > +struct osf_iio_scan_1axis {
> > > + s32 value;
> > > + u32 padding;
> > As above, unneeded.
> > > + aligned_s64 timestamp;
> > > +};
> >
> > Given I think these are only used in one place, moving them to there would
> > avoid need for named structure types.
> >
> > > +
> > > +#define OSF_MOD_CHAN(_type, _mod, _idx) \
> > > + { \
> > > + .type = (_type), \
> > > + .modified = 1, \
> > > + .channel2 = (_mod), \
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> > > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> > > + .scan_index = (_idx), \
> > > + .scan_type = { \
> > > + .sign = 's', \
> > > + .realbits = 32, \
> > > + .storagebits = 32, \
> > > + .endianness = IIO_CPU, \
> > > + }, \
> > > + }
> > > +
> > > +#define OSF_CHAN(_type, _idx) \
> >
> > Given this is not much used, you could use the IIO_NO_MOD modifier as a gate
> > on setting .modified = (_mod != IIO_NO_MOD) ? 1 : 0,
> > and have just one macro.
> >
> > > + { \
> > > + .type = (_type), \
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> > > + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> > > + .scan_index = (_idx), \
> > > + .scan_type = { \
> > > + .sign = 's', \
> > > + .realbits = 32, \
> > > + .storagebits = 32, \
> > > + .endianness = IIO_CPU, \
> > > + }, \
> > > + }
> > > +
> > > +static const struct iio_chan_spec osf_accel_channels[] = {
> > > + 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[] = {
> > > + 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[] = {
> > > + 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[] = {
> > > + OSF_CHAN(IIO_TEMP, 0),
> > > + IIO_CHAN_SOFT_TIMESTAMP(1),
> > > +};
> > > +
> > > +static const struct osf_iio_sensor_spec osf_iio_sensor_specs[] = {
> > > + {
> > > + .sensor_type = OSF_SENSOR_ACCELEROMETER,
> > > + .channel_count = 3,
> > > + .name = "osf-accel",
> > > + .channels = osf_accel_channels,
> > > + .num_channels = ARRAY_SIZE(osf_accel_channels),
> > > + },
> > > + {
> > > + .sensor_type = OSF_SENSOR_GYROSCOPE,
> > > + .channel_count = 3,
> > > + .name = "osf-gyro",
> > > + .channels = osf_gyro_channels,
> > > + .num_channels = ARRAY_SIZE(osf_gyro_channels),
> > > + },
> > > + {
> > > + .sensor_type = OSF_SENSOR_MAGNETOMETER,
> > > + .channel_count = 3,
> > > + .name = "osf-magn",
> > > + .channels = osf_mag_channels,
> > > + .num_channels = ARRAY_SIZE(osf_mag_channels),
> > > + },
> > > + {
> > > + .sensor_type = OSF_SENSOR_TEMPERATURE,
> > > + .channel_count = 1,
> > > + .name = "osf-temp",
> > > + .channels = osf_temp_channels,
> > > + .num_channels = 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 = 0; i < ARRAY_SIZE(osf_iio_sensor_specs); i++) {
> > > + if (osf_iio_sensor_specs[i].sensor_type == sensor_type &&
> > > + osf_iio_sensor_specs[i].channel_count == 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
> >
> > return osf_iio_find_sensor_spec(sensor_type, channel_count) != NULL;
> >
> > > +}
> >
> > > +
> > > +static int osf_iio_buffer_postenable(struct iio_dev *indio_dev)
> > > +{
> > > + struct osf_iio_state *state = iio_priv(indio_dev);
> > > +
> > > + guard(mutex)(&state->buffer_lock);
> >
> > These transistions are already guarded by the core, so see the question later
> > on whether you can just claim buffered mode in the data handler
> > to avoid the need for this. That might not work but the way you did
> > this got me thinking about a more general solution to the problem.
> >
> > > + state->buffer_active = true;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int osf_iio_buffer_predisable(struct iio_dev *indio_dev)
> > > +{
> > > + struct osf_iio_state *state = iio_priv(indio_dev);
> > > +
> > > + /* Wait for the current push before the IIO core changes its buffers. */
> > > + guard(mutex)(&state->buffer_lock);
> > > + state->buffer_active = false;
> > > +
> > > + return 0;
> > > +}
> >
> >
> > > +
> > > +int osf_iio_push_sample(struct iio_dev *indio_dev, const s32 *values,
> > > + u16 channel_count)
> > > +{
> > > + struct osf_iio_state *state = iio_priv(indio_dev);
> > > + s64 timestamp;
> > > +
> > > + if (channel_count != state->spec->channel_count)
> > > + return -EPROTO;
> > > +
> > > + guard(mutex)(&state->buffer_lock);
> >
> > Using an extra lock is one solution I suppose to the risk
> > of this racing with the buffer tear down and the problems
> > around active_scan_mask being freed. Could you use
> >
> > if (iio_device_try_claim_buffer_mode(indio_dev))
> > and later a release on that to achieve the same result?
> >
> > Only the ade9000 is doing similar at the moment so would need
> > some testing to make sure no lock inversions occur in general
> > but given you don't take any other locks in here it should be fine.
> >
> >
> > > + if (!state->buffer_active || !iio_buffer_enabled(indio_dev))
> > > + return 0;
> > > +
> > > + timestamp = iio_get_time_ns(indio_dev);
> > > +
> > > + switch (channel_count) {
> > > + case 1: {
> > > + struct osf_iio_scan_1axis scan = {
> > > + .value = values[0],
> > > + };
> > As suggested above, this structure type is only used locally here. So
> > make the definition local as well.
> > struct {
> > s32 value;
> > aligned_s64 timestamp;
> > } scan = { };
> >
> > > +
> > > + return iio_push_to_buffers_with_ts(indio_dev, &scan,
> > > + sizeof(scan), timestamp);
> > > + }
> > > + case 3: {
> > > + struct osf_iio_scan_3axis scan = {
> > > + .values = { },
> >
> > Setting values doesn't add anything useful as = { }
> > zeros the whole structure (including any holes - they kernel is
> > built with specific compiler options to ensure that last bit
> > as it wasn't required in older c specs).
> >
> > > + };
> > > + unsigned int channel, index = 0;
> >
> > Please split declarations that assign and ones that don't on to separate lines.
> > Ends up a little more readable.
> > unsigned int index = 0;
> > unsigned int channel;
> >
> > > +
> > > + /* Pack the active channels; unused storage remains initialized. */
> > > + for_each_set_bit(channel, indio_dev->active_scan_mask, channel_count)
> > > + scan.values[index++] = 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_serdev.c b/drivers/iio/opensensorfusion/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;
> >
> > Just name it 'lock' given it has scope beyond rx. Do that in earlier patch
> > though rather than changing it here.
> >
> > > };
> >
> > >
> > > @@ -139,5 +144,5 @@ static struct serdev_device_driver osf_serdev_driver = {
> > > };
> > > module_serdev_device_driver(osf_serdev_driver);
> > >
> > > -MODULE_DESCRIPTION("Open Sensor Fusion UART receiver");
> > > +MODULE_DESCRIPTION("Open Sensor Fusion IIO driver");
> >
> > Just have that being slightly missleading in earlier patch to reduce churn here.
> >
> > > MODULE_LICENSE("GPL");
> >
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-09-20 5:07 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 18:24 [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
2026-09-18 18:24 ` [PATCH v10 1/8] dt-bindings: iio: add Open Sensor Fusion device Jinseob Kim
2026-09-20 1:14 ` Jonathan Cameron
2026-09-20 4:01 ` Kim Jinseob
2026-09-18 18:24 ` [PATCH v10 2/8] Documentation: iio: add Open Sensor Fusion driver overview Jinseob Kim
2026-09-20 1:12 ` Jonathan Cameron
2026-09-20 3:57 ` Kim Jinseob
2026-09-18 18:24 ` [PATCH v10 3/8] iio: osf: add protocol decoding Jinseob Kim
2026-09-20 1:27 ` Jonathan Cameron
2026-09-20 4:12 ` Kim Jinseob
2026-09-18 18:24 ` [PATCH v10 4/8] iio: osf: add validated stream parser Jinseob Kim
2026-09-20 1:30 ` Jonathan Cameron
2026-09-20 4:12 ` Kim Jinseob
2026-09-18 18:24 ` [PATCH v10 5/8] iio: osf: add UART transport and core receive path Jinseob Kim
2026-09-20 1:40 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 6/8] iio: osf: add IIO devices from capability reports Jinseob Kim
2026-09-20 2:03 ` Jonathan Cameron
2026-09-20 4:20 ` Kim Jinseob
2026-09-20 5:07 ` Kim Jinseob
2026-09-18 18:24 ` [PATCH v10 7/8] iio: osf: add core KUnit tests Jinseob Kim
2026-09-20 2:12 ` Jonathan Cameron
2026-09-18 18:24 ` [PATCH v10 8/8] iio: osf: add IIO " Jinseob Kim
2026-09-20 2:18 ` Jonathan Cameron
2026-09-20 2:05 ` [PATCH v10 0/8] iio: add Open Sensor Fusion UART support Jonathan Cameron
2026-09-20 4:22 ` Kim Jinseob
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®