summaryrefslogtreecommitdiffstats
path: root/drivers/spi/tests
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/tests')
-rw-r--r--drivers/spi/tests/Makefile3
-rw-r--r--drivers/spi/tests/spi-dma-kunit.c259
2 files changed, 262 insertions, 0 deletions
diff --git a/drivers/spi/tests/Makefile b/drivers/spi/tests/Makefile
new file mode 100644
index 000000000..26689e0cb
--- /dev/null
+++ b/drivers/spi/tests/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_SPI_DMA_KUNIT_TEST) += spi-dma-kunit.o
diff --git a/drivers/spi/tests/spi-dma-kunit.c b/drivers/spi/tests/spi-dma-kunit.c
new file mode 100644
index 000000000..ee5476ff3
--- /dev/null
+++ b/drivers/spi/tests/spi-dma-kunit.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0
+// KUnit tests for the SPI core DMA mapping error paths.
+//
+// A mapping error must clear all SG tables and *_sg_mapped flags while
+// cur_{tx,rx}_dma_dev identify the devices used for the attempted mapping.
+// Zero-length transfers make sg_alloc_table() fail with -EINVAL, providing
+// deterministic failure injection without test hooks.
+
+#include <kunit/device.h>
+#include <kunit/test.h>
+#include <linux/dma-mapping.h>
+#include <linux/limits.h>
+#include <linux/spi/spi.h>
+
+#include "../internals.h"
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+
+#define SPI_DMA_TEST_LEN 256
+#define SPI_DMA_TEST_XFERS 2
+
+struct spi_dma_test_ctx {
+ struct spi_controller *ctlr;
+ struct spi_device *spi;
+ struct device *dma_dev;
+ struct device *stale_dma_dev;
+ struct spi_transfer xfer[SPI_DMA_TEST_XFERS];
+ struct spi_message msg;
+ void *buf[SPI_DMA_TEST_XFERS * 2];
+};
+
+static bool spi_dma_test_can_dma(struct spi_controller *ctlr,
+ struct spi_device *spi,
+ struct spi_transfer *xfer)
+{
+ /* Opt every transfer into the core DMA mapping path. */
+ return true;
+}
+
+/*
+ * A bare controller is sufficient because the mapping helpers do not
+ * dereference ctlr->dev. With dma_tx and dma_rx unset, both directions use
+ * dma_map_dev, so the controller need not be registered.
+ */
+static struct spi_dma_test_ctx *spi_dma_test_ctx_new(struct kunit *test)
+{
+ struct spi_dma_test_ctx *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+ ctx->dma_dev = kunit_device_register(test, "spi-dma-error-path");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->dma_dev);
+ ctx->stale_dma_dev =
+ kunit_device_register(test, "spi-dma-stale-device");
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->stale_dma_dev);
+
+ /* Keep both devices valid if an assertion aborts the test. */
+ KUNIT_ASSERT_EQ(test, 0,
+ dma_coerce_mask_and_coherent(ctx->dma_dev,
+ DMA_BIT_MASK(64)));
+ KUNIT_ASSERT_EQ(test, 0,
+ dma_coerce_mask_and_coherent(ctx->stale_dma_dev,
+ DMA_BIT_MASK(64)));
+
+ ctx->ctlr = kunit_kzalloc(test, sizeof(*ctx->ctlr), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->ctlr);
+
+ ctx->spi = kunit_kzalloc(test, sizeof(*ctx->spi), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->spi);
+
+ ctx->ctlr->can_dma = spi_dma_test_can_dma;
+ ctx->ctlr->dma_map_dev = ctx->dma_dev;
+ /* Normally initialized by spi_register_controller(). */
+ ctx->ctlr->max_dma_len = INT_MAX;
+
+ ctx->spi->controller = ctx->ctlr;
+ spi_message_init(&ctx->msg);
+ ctx->msg.spi = ctx->spi;
+
+ return ctx;
+}
+
+static void *spi_dma_test_buf(struct kunit *test, struct spi_dma_test_ctx *ctx,
+ unsigned int slot)
+{
+ KUNIT_ASSERT_LT(test, slot, ARRAY_SIZE(ctx->buf));
+
+ ctx->buf[slot] = kunit_kzalloc(test, SPI_DMA_TEST_LEN, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->buf[slot]);
+
+ return ctx->buf[slot];
+}
+
+/*
+ * Emulate DMA devices retained from an earlier message. Using valid devices
+ * also lets the unfixed path reach the assertions instead of dereferencing
+ * NULL during cleanup.
+ */
+static void spi_dma_test_pin_stale_dma_devs(struct spi_dma_test_ctx *ctx)
+{
+ ctx->ctlr->cur_tx_dma_dev = ctx->stale_dma_dev;
+ ctx->ctlr->cur_rx_dma_dev = ctx->stale_dma_dev;
+}
+
+static void spi_dma_test_assert_dma_devs_published(struct kunit *test,
+ struct spi_dma_test_ctx *ctx)
+{
+ KUNIT_ASSERT_PTR_EQ(test, ctx->ctlr->cur_tx_dma_dev, ctx->dma_dev);
+ KUNIT_ASSERT_PTR_EQ(test, ctx->ctlr->cur_rx_dma_dev, ctx->dma_dev);
+}
+
+static void spi_dma_test_assert_nothing_mapped(struct kunit *test,
+ struct spi_dma_test_ctx *ctx,
+ unsigned int nr_xfers)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_xfers; i++) {
+ KUNIT_ASSERT_FALSE_MSG(test, ctx->xfer[i].tx_sg_mapped,
+ "xfer[%u] still claims a TX mapping after __spi_map_msg() failed",
+ i);
+ KUNIT_ASSERT_FALSE_MSG(test, ctx->xfer[i].rx_sg_mapped,
+ "xfer[%u] still claims an RX mapping after __spi_map_msg() failed",
+ i);
+ KUNIT_EXPECT_NULL(test, ctx->xfer[i].tx_sg.sgl);
+ KUNIT_EXPECT_EQ(test, ctx->xfer[i].tx_sg.orig_nents, 0U);
+ KUNIT_EXPECT_EQ(test, ctx->xfer[i].tx_sg.nents, 0U);
+ KUNIT_EXPECT_NULL(test, ctx->xfer[i].rx_sg.sgl);
+ KUNIT_EXPECT_EQ(test, ctx->xfer[i].rx_sg.orig_nents, 0U);
+ KUNIT_EXPECT_EQ(test, ctx->xfer[i].rx_sg.nents, 0U);
+ }
+}
+
+/*
+ * xfer0 maps TX and RX; zero-length xfer1 then fails its TX mapping.
+ * The failure must unwind xfer0 and update cur_*_dma_dev.
+ */
+static void spi_dma_later_tx_fail_rolls_back_earlier(struct kunit *test)
+{
+ struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test);
+ int ret;
+
+ ctx->xfer[0].tx_buf = spi_dma_test_buf(test, ctx, 0);
+ ctx->xfer[0].rx_buf = spi_dma_test_buf(test, ctx, 1);
+ ctx->xfer[0].len = SPI_DMA_TEST_LEN;
+
+ ctx->xfer[1].tx_buf = spi_dma_test_buf(test, ctx, 2);
+ ctx->xfer[1].rx_buf = NULL;
+ ctx->xfer[1].len = 0; /* forces -EINVAL */
+
+ spi_message_add_tail(&ctx->xfer[0], &ctx->msg);
+ spi_message_add_tail(&ctx->xfer[1], &ctx->msg);
+
+ spi_dma_test_pin_stale_dma_devs(ctx);
+
+ ret = __spi_map_msg(ctx->ctlr, &ctx->msg);
+ KUNIT_ASSERT_EQ(test, ret, -EINVAL);
+
+ spi_dma_test_assert_dma_devs_published(test, ctx);
+ spi_dma_test_assert_nothing_mapped(test, ctx, SPI_DMA_TEST_XFERS);
+
+ KUNIT_EXPECT_EQ(test, 0, __spi_unmap_msg(ctx->ctlr, &ctx->msg));
+}
+
+/*
+ * xfer0 maps TX and RX; zero-length RX-only xfer1 then fails.
+ * The failure must unwind xfer0 without leaving either mapping flag set.
+ */
+static void spi_dma_later_rx_fail_rolls_back_earlier(struct kunit *test)
+{
+ struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test);
+ int ret;
+
+ ctx->xfer[0].tx_buf = spi_dma_test_buf(test, ctx, 0);
+ ctx->xfer[0].rx_buf = spi_dma_test_buf(test, ctx, 1);
+ ctx->xfer[0].len = SPI_DMA_TEST_LEN;
+
+ ctx->xfer[1].tx_buf = NULL;
+ ctx->xfer[1].rx_buf = spi_dma_test_buf(test, ctx, 2);
+ ctx->xfer[1].len = 0; /* forces -EINVAL */
+
+ spi_message_add_tail(&ctx->xfer[0], &ctx->msg);
+ spi_message_add_tail(&ctx->xfer[1], &ctx->msg);
+
+ spi_dma_test_pin_stale_dma_devs(ctx);
+
+ ret = __spi_map_msg(ctx->ctlr, &ctx->msg);
+ KUNIT_ASSERT_EQ(test, ret, -EINVAL);
+
+ spi_dma_test_assert_dma_devs_published(test, ctx);
+ spi_dma_test_assert_nothing_mapped(test, ctx, SPI_DMA_TEST_XFERS);
+
+ KUNIT_EXPECT_EQ(test, 0, __spi_unmap_msg(ctx->ctlr, &ctx->msg));
+}
+
+/* Ensure the error unwind does not affect successful mappings. */
+static void spi_dma_map_success_publishes_dma_devs(struct kunit *test)
+{
+ struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test);
+ int ret;
+
+ ctx->xfer[0].tx_buf = spi_dma_test_buf(test, ctx, 0);
+ ctx->xfer[0].rx_buf = spi_dma_test_buf(test, ctx, 1);
+ ctx->xfer[0].len = SPI_DMA_TEST_LEN;
+
+ spi_message_add_tail(&ctx->xfer[0], &ctx->msg);
+
+ ret = __spi_map_msg(ctx->ctlr, &ctx->msg);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_TRUE(test, ctx->xfer[0].tx_sg_mapped);
+ KUNIT_EXPECT_TRUE(test, ctx->xfer[0].rx_sg_mapped);
+ KUNIT_EXPECT_PTR_EQ(test, ctx->ctlr->cur_tx_dma_dev, ctx->dma_dev);
+ KUNIT_EXPECT_PTR_EQ(test, ctx->ctlr->cur_rx_dma_dev, ctx->dma_dev);
+
+ KUNIT_EXPECT_EQ(test, 0, __spi_unmap_msg(ctx->ctlr, &ctx->msg));
+
+ KUNIT_EXPECT_FALSE(test, ctx->xfer[0].tx_sg_mapped);
+ KUNIT_EXPECT_FALSE(test, ctx->xfer[0].rx_sg_mapped);
+ KUNIT_EXPECT_NULL(test, ctx->xfer[0].tx_sg.sgl);
+ KUNIT_EXPECT_NULL(test, ctx->xfer[0].rx_sg.sgl);
+}
+
+/* A transfer without buffers requires no DMA mapping. */
+static void spi_dma_map_nothing_is_success(struct kunit *test)
+{
+ struct spi_dma_test_ctx *ctx = spi_dma_test_ctx_new(test);
+ int ret;
+
+ ctx->xfer[0].tx_buf = NULL;
+ ctx->xfer[0].rx_buf = NULL;
+ ctx->xfer[0].len = SPI_DMA_TEST_LEN;
+
+ spi_message_add_tail(&ctx->xfer[0], &ctx->msg);
+
+ ret = __spi_map_msg(ctx->ctlr, &ctx->msg);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ spi_dma_test_assert_nothing_mapped(test, ctx, 1);
+}
+
+static struct kunit_case spi_dma_error_path_cases[] = {
+ KUNIT_CASE(spi_dma_later_tx_fail_rolls_back_earlier),
+ KUNIT_CASE(spi_dma_later_rx_fail_rolls_back_earlier),
+ KUNIT_CASE(spi_dma_map_success_publishes_dma_devs),
+ KUNIT_CASE(spi_dma_map_nothing_is_success),
+ {}
+};
+
+static struct kunit_suite spi_dma_error_path_suite = {
+ .name = "spi_dma",
+ .test_cases = spi_dma_error_path_cases,
+};
+
+kunit_test_suite(spi_dma_error_path_suite);
+
+MODULE_DESCRIPTION("KUnit tests for SPI core DMA mapping");
+MODULE_LICENSE("GPL");