diff options
Diffstat (limited to 'drivers/spi/tests')
| -rw-r--r-- | drivers/spi/tests/Makefile | 3 | ||||
| -rw-r--r-- | drivers/spi/tests/spi-dma-kunit.c | 259 |
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"); |
