mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@kernel.org>
To: Jinseob Kim <kimjinseob88@gmail.com>
Cc: linux-iio@vger.kernel.org, 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: Re: [PATCH v11 7/8] iio: osf: add core KUnit tests
Date: Sun, 18 Oct 2026 00:58:05 +0100	[thread overview]
Message-ID: <20261018005805.7db7ca9f@jic23-hlaptop> (raw)
In-Reply-To: <20261011231340.42885-8-kimjinseob88@gmail.com>

On Mon, 12 Oct 2026 08:13:39 +0900
Jinseob Kim <kimjinseob88@gmail.com> wrote:

> 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>
https://sashiko.dev/#/patchset/20261011231340.42885-1-kimjinseob88%40gmail.com

The comment about maybe allowing building when osf is a module
looks superficially valid. Some of the others are about
outdated code, but please take a look.

Thanks,

Jonathan

> ---
>  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");
> 


  reply	other threads:[~2026-10-17 23:58 UTC|newest]

Thread overview: 14+ 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-12 19:10   ` Conor Dooley
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 ` [PATCH v11 7/8] iio: osf: add core KUnit tests Jinseob Kim
2026-10-17 23:58   ` Jonathan Cameron [this message]
2026-10-11 23:13 ` [PATCH v11 8/8] iio: osf: add IIO " Jinseob Kim
2026-10-18  0:02 ` [PATCH v11 0/8] iio: add Open Sensor Fusion UART support Jonathan Cameron
2026-10-18  9:46   ` Kim Jinseob
2026-10-18 18:04     ` Jonathan Cameron

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=20261018005805.7db7ca9f@jic23-hlaptop \
    --to=jic23@kernel.org \
    --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=kimjinseob88@gmail.com \
    --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®