mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jinseob Kim <kimjinseob88@gmail.com>
To: jic23@kernel.org, linux-iio@vger.kernel.org
Cc: kimjinseob88@gmail.com, dlechner@baylibre.com,
	nuno.sa@analog.com, andy@kernel.org, robh@kernel.org,
	krzk+dt@kernel.org, conor+dt@kernel.org,
	neil.armstrong@linaro.org, andersson@kernel.org,
	prabhakar.mahadev-lad.rj@bp.renesas.com,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	corbet@lwn.net, skhan@linuxfoundation.org, rdunlap@infradead.org,
	linux-doc@vger.kernel.org
Subject: [PATCH v11 7/8] iio: osf: add core KUnit tests
Date: Mon, 12 Oct 2026 08:13:39 +0900	[thread overview]
Message-ID: <20261011231340.42885-8-kimjinseob88@gmail.com> (raw)
In-Reply-To: <20261011231340.42885-1-kimjinseob88@gmail.com>

Exercise capability discovery, complete repeated inventories, sample-cache
admission, exact lengths, reserved wire bytes and session faults. Protect
producer lifetime with deferred cleanup and close the session mutex
lifecycle before reinitialization.

Assisted-by: LLM
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
 drivers/iio/sensorhubs/osf/Kconfig         |   12 +
 drivers/iio/sensorhubs/osf/Makefile        |    1 +
 drivers/iio/sensorhubs/osf/osf_core_test.c | 1056 ++++++++++++++++++++
 3 files changed, 1069 insertions(+)
 create mode 100644 drivers/iio/sensorhubs/osf/osf_core_test.c

diff --git a/drivers/iio/sensorhubs/osf/Kconfig b/drivers/iio/sensorhubs/osf/Kconfig
index f955a1f2993d244369943efbd853b9d2c6d47139..316f33be1327ca85eb18920b8d610a3e983439d2 100644
--- a/drivers/iio/sensorhubs/osf/Kconfig
+++ b/drivers/iio/sensorhubs/osf/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/sensorhubs/osf/Makefile b/drivers/iio/sensorhubs/osf/Makefile
index b4e03b80cfa4cca6a67e6cd8228a4776a38dcb72..c2f5ba065a62fef2a4897034239b405ccdd69250 100644
--- a/drivers/iio/sensorhubs/osf/Makefile
+++ b/drivers/iio/sensorhubs/osf/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/sensorhubs/osf/osf_core_test.c b/drivers/iio/sensorhubs/osf/osf_core_test.c
new file mode 100644
index 0000000000000000000000000000000000000000..fabf19018b200359bcfe5a910e6190daae3d22f2
--- /dev/null
+++ b/drivers/iio/sensorhubs/osf/osf_core_test.c
@@ -0,0 +1,1056 @@
+// 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);
+	mutex_destroy(&ctx->osf.latest_lock);
+}
+
+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);
+	}
+	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[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. */
+	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);
+	len = osf_test_report(frame, &entry, 1);
+	put_unaligned_le32(0xdeadbeef, frame + 34);
+	put_unaligned_le16(0x1234, frame + OSF_FRAME_HEADER_LEN + 2);
+	put_unaligned_le32(0xa5a5a5a5,
+			   frame + OSF_FRAME_HEADER_LEN + OSF_CAP_REPORT_BASE_LEN + 16);
+	osf_test_crc(frame, len);
+	KUNIT_ASSERT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len),
+			OSF_STREAM_FRAME_HANDLED);
+	KUNIT_EXPECT_EQ(test, osf_core_receive_frame(&ctx->osf, frame, len),
+			OSF_STREAM_FRAME_IGNORED);
+	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;
+	struct completion fault_observed;
+	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);
+			complete(&race->fault_observed);
+		}
+		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;
+}
+
+KUNIT_DEFINE_ACTION_WRAPPER(osf_test_stop_kthread, kthread_stop,
+			    struct task_struct *);
+
+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);
+	init_completion(&race->fault_observed);
+	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);
+	ret = kunit_add_action_or_reset(test, osf_test_stop_kthread, producer);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->started, HZ), 0UL);
+	/* Exercise buffer reconfiguration against latest_lock -> IIO mlock. */
+	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);
+	KUNIT_EXPECT_NE(test, wait_for_completion_timeout(&race->fault_observed,
+							  5 * HZ), 0UL);
+	kunit_release_action(test, osf_test_stop_kthread, 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");

  parent reply	other threads:[~2026-10-11 23:15 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-11 23:13 [PATCH v11 0/8] iio: add Open Sensor Fusion UART support Jinseob Kim
2026-10-11 23:13 ` [PATCH v11 1/8] dt-bindings: iio: add Open Sensor Fusion device Jinseob Kim
2026-10-11 23:13 ` [PATCH v11 2/8] Documentation: iio: add Open Sensor Fusion driver overview Jinseob Kim
2026-10-11 23:13 ` [PATCH v11 3/8] iio: osf: add protocol decoding Jinseob Kim
2026-10-11 23:13 ` [PATCH v11 4/8] iio: osf: add validated stream parser Jinseob Kim
2026-10-11 23:13 ` [PATCH v11 5/8] iio: osf: add UART transport and core receive path Jinseob Kim
2026-10-11 23:13 ` [PATCH v11 6/8] iio: osf: add IIO devices from capability reports Jinseob Kim
2026-10-11 23:13 ` Jinseob Kim [this message]
2026-10-11 23:13 ` [PATCH v11 8/8] iio: osf: add IIO KUnit tests Jinseob Kim

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261011231340.42885-8-kimjinseob88@gmail.com \
    --to=kimjinseob88@gmail.com \
    --cc=andersson@kernel.org \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=corbet@lwn.net \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=neil.armstrong@linaro.org \
    --cc=nuno.sa@analog.com \
    --cc=prabhakar.mahadev-lad.rj@bp.renesas.com \
    --cc=rdunlap@infradead.org \
    --cc=robh@kernel.org \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®