Linux SPI subsystem development
 help / color / mirror / Atom feed
From: Joseph CT Chan <jctchan@gmail.com>
To: Mark Brown <broonie@kernel.org>
Cc: linux-spi@vger.kernel.org, David Lechner <dlechner@baylibre.com>,
	Bart Van Assche <bvanassche@acm.org>,
	Joseph CT Chan <josephctchan@google.com>
Subject: [PATCH v2 2/2] spi: spi-ti-qspi: add KUnit tests for transfer error finalization
Date: Tue,  3 Nov 2026 22:51:16 +0000	[thread overview]
Message-ID: <20261103225116.3624270-3-jctchan@gmail.com> (raw)
In-Reply-To: <20261103225116.3624270-1-jctchan@gmail.com>

From: Joseph CT Chan <josephctchan@google.com>

Add KUnit tests for ti_qspi_start_transfer_one() covering the transfer
error handling path.

On ARCH=um, emulate the TI QSPI controller MMIO via logic_iomem:
- ti_qspi_test: verify that queued and synchronous transfers hitting a
  ti_qspi_poll_wc() write completion timeout complete with -ETIMEDOUT,
  that QSPI_INVAL is written to end the frame, and that subsequent
  transfers succeed cleanly once QSPI_SPI_STATUS_REG reports QSPI_WC.

Developed with help from the Claude and Gemini AI assistants.

Signed-off-by: Joseph CT Chan <josephctchan@google.com>
---
v2: Trimmed the comments.

 drivers/spi/Kconfig                   |  12 ++
 drivers/spi/Makefile                  |   1 +
 drivers/spi/tests/.kunitconfig        |   4 +
 drivers/spi/tests/Makefile            |   1 +
 drivers/spi/tests/spi-ti-qspi-kunit.c | 188 ++++++++++++++++++++++++++
 5 files changed, 206 insertions(+)
 create mode 100644 drivers/spi/tests/spi-ti-qspi-kunit.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index a7b343d124a9..feb9265b7d0f 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1258,6 +1258,18 @@ config SPI_TI_QSPI
 	  This device supports single, dual and quad read support, while
 	  it only supports single write mode.
 
+config SPI_TI_QSPI_KUNIT_TEST
+	bool "KUnit test for TI QSPI controller driver" if !KUNIT_ALL_TESTS
+	depends on KUNIT=y && SPI_TI_QSPI=y && INDIRECT_IOMEM && !HAVE_CLK
+	default KUNIT_ALL_TESTS
+	help
+	  This option builds the KUnit test suite for the TI QSPI controller
+	  driver. It verifies that transfer timeout errors are properly
+	  finalized with -ETIMEDOUT and that QSPI_INVAL is written to the
+	  command register.
+
+	  If unsure, say N.
+
 config SPI_TLE62X0
 	tristate "Infineon TLE62X0 (for power switching)"
 	depends on SYSFS
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index d4a806b46b2c..24497d3936f6 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_SPI_SPIDEV)		+= spidev.o
 obj-$(CONFIG_SPI_LOOPBACK_TEST)		+= spi-loopback-test.o
 obj-$(CONFIG_SPI_DMA_KUNIT_TEST)	+= tests/
 obj-$(CONFIG_SPI_CORE_KUNIT_TEST)	+= tests/
+obj-$(CONFIG_SPI_TI_QSPI_KUNIT_TEST)	+= tests/
 
 # SPI master controller drivers (bus)
 obj-$(CONFIG_SPI_AIROHA_SNFI)		+= spi-airoha-snfi.o
diff --git a/drivers/spi/tests/.kunitconfig b/drivers/spi/tests/.kunitconfig
index f781425d258e..354bafc42c2b 100644
--- a/drivers/spi/tests/.kunitconfig
+++ b/drivers/spi/tests/.kunitconfig
@@ -1,4 +1,8 @@
 CONFIG_KUNIT=y
+CONFIG_KUNIT_UML_PCI=y
 CONFIG_SPI=y
 CONFIG_SPI_MASTER=y
 CONFIG_SPI_CORE_KUNIT_TEST=y
+CONFIG_COMPILE_TEST=y
+CONFIG_SPI_TI_QSPI=y
+CONFIG_SPI_TI_QSPI_KUNIT_TEST=y
diff --git a/drivers/spi/tests/Makefile b/drivers/spi/tests/Makefile
index 6dbf53643619..a5a8fc9435d4 100644
--- a/drivers/spi/tests/Makefile
+++ b/drivers/spi/tests/Makefile
@@ -2,3 +2,4 @@
 
 obj-$(CONFIG_SPI_DMA_KUNIT_TEST) += spi-dma-kunit.o
 obj-$(CONFIG_SPI_CORE_KUNIT_TEST) += spi-core-kunit.o
+obj-$(CONFIG_SPI_TI_QSPI_KUNIT_TEST) += spi-ti-qspi-kunit.o
diff --git a/drivers/spi/tests/spi-ti-qspi-kunit.c b/drivers/spi/tests/spi-ti-qspi-kunit.c
new file mode 100644
index 000000000000..fd75d5d3ab89
--- /dev/null
+++ b/drivers/spi/tests/spi-ti-qspi-kunit.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <kunit/test.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/kthread.h>
+#include <linux/logic_iomem.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+
+#define QSPI_SPI_CMD_REG	0x48
+#define QSPI_SPI_STATUS_REG	0x4c
+#define QSPI_WC			BIT(1)
+#define QSPI_INVAL		(4 << 16)
+
+struct ti_qspi_test_ctx {
+	struct platform_device *pdev;
+	struct spi_device *spi;
+	struct spi_message msg;
+	struct spi_transfer xfer;
+	struct completion done;
+	u8 tx[4];
+};
+
+static bool g_err, g_inval, g_region_registered;
+static struct resource g_iomem_res = DEFINE_RES_MEM(0x30000000, 0x2000);
+
+static unsigned long ti_qspi_io_r(void *p, unsigned int off, int sz)
+{
+	return (off == QSPI_SPI_STATUS_REG && !g_err) ? QSPI_WC : 0;
+}
+
+static void ti_qspi_io_w(void *p, unsigned int off, int sz, unsigned long val)
+{
+	if (off == QSPI_SPI_CMD_REG && (val & QSPI_INVAL))
+		g_inval = true;
+}
+
+static const struct logic_iomem_ops g_iops = {
+	.read = ti_qspi_io_r,
+	.write = ti_qspi_io_w,
+};
+
+static long ti_qspi_map(unsigned long o, size_t s, const struct logic_iomem_ops **ops, void **p)
+{
+	*ops = &g_iops;
+	return 0;
+}
+
+static const struct logic_iomem_region_ops g_rops = {
+	.map = ti_qspi_map,
+};
+
+static int ti_qspi_find_ctlr(struct device *d, void *data)
+{
+	*(struct spi_controller **)data = container_of(d, struct spi_controller, dev);
+	return 1;
+}
+
+static void kunit_done(void *p)
+{
+	complete(p);
+}
+
+static int ti_qspi_init(struct kunit *t)
+{
+	const struct resource res[] = {
+		DEFINE_RES_MEM(0x30000000, 0x1000),
+		DEFINE_RES_MEM(0x30001000, 0x1000),
+		DEFINE_RES_IRQ(1),
+	};
+	struct ti_qspi_test_ctx *ctx;
+	struct spi_board_info info = { .modalias = "ti-qspi-test" };
+	struct spi_controller *ctlr = NULL;
+	struct platform_device *pdev;
+
+	g_err = false;
+	g_inval = false;
+	ctx = kunit_kzalloc(t, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(t, ctx);
+	t->priv = ctx;
+
+	pdev = platform_device_register_simple("ti-qspi", -1, res, ARRAY_SIZE(res));
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(t, pdev);
+	ctx->pdev = pdev;
+
+	device_for_each_child(&pdev->dev, &ctlr, ti_qspi_find_ctlr);
+	if (!ctlr) {
+		platform_device_unregister(pdev);
+		ctx->pdev = NULL;
+		KUNIT_FAIL_AND_ABORT(t, "TI QSPI controller device not found");
+	}
+
+	ctlr->max_speed_hz = 50000000;
+	ctx->spi = spi_new_device(ctlr, &info);
+	if (!ctx->spi) {
+		pm_runtime_get_noresume(&pdev->dev);
+		platform_device_unregister(pdev);
+		ctx->pdev = NULL;
+		KUNIT_FAIL_AND_ABORT(t, "Failed to create spi child device");
+	}
+
+	init_completion(&ctx->done);
+	memcpy(ctx->tx, "ABCD", sizeof(ctx->tx));
+	ctx->xfer.tx_buf = ctx->tx;
+	ctx->xfer.len = sizeof(ctx->tx);
+	spi_message_init_with_transfers(&ctx->msg, &ctx->xfer, 1);
+	return 0;
+}
+
+static void ti_qspi_exit(struct kunit *t)
+{
+	struct ti_qspi_test_ctx *ctx = t->priv;
+
+	if (!ctx)
+		return;
+	if (ctx->spi)
+		spi_unregister_device(ctx->spi);
+	if (ctx->pdev) {
+		pm_runtime_get_noresume(&ctx->pdev->dev);
+		platform_device_unregister(ctx->pdev);
+	}
+}
+
+static void check_sync(struct kunit *t, struct spi_device *s, struct spi_message *m, int exp)
+{
+	g_inval = false;
+	KUNIT_EXPECT_EQ(t, spi_sync(s, m), exp);
+	KUNIT_EXPECT_EQ(t, m->status, exp);
+	/* Message must be finalized and unoptimized even when a transfer fails. */
+	KUNIT_EXPECT_FALSE(t, m->optimized);
+	/* QSPI_INVAL must be written to terminate the frame on both success and error. */
+	KUNIT_EXPECT_TRUE(t, g_inval);
+}
+
+static void ti_qspi_test(struct kunit *t)
+{
+	struct ti_qspi_test_ctx *ctx = t->priv;
+	unsigned long timeout;
+
+	g_err = true;
+	check_sync(t, ctx->spi, &ctx->msg, -ETIMEDOUT);
+
+	/* Exercise the queued kworker path via spi_async() on transfer timeout. */
+	ctx->msg.complete = kunit_done;
+	ctx->msg.context = &ctx->done;
+	g_inval = false;
+	reinit_completion(&ctx->done);
+	KUNIT_ASSERT_EQ(t, spi_async(ctx->spi, &ctx->msg), 0);
+
+	timeout = wait_for_completion_timeout(&ctx->done, msecs_to_jiffies(4000));
+	kthread_flush_worker(ctx->spi->controller->kworker);
+	if (!timeout)
+		KUNIT_FAIL_AND_ABORT(t, "ti-qspi async transfer timed out");
+
+	KUNIT_EXPECT_EQ(t, ctx->msg.status, -ETIMEDOUT);
+	KUNIT_EXPECT_FALSE(t, ctx->msg.optimized);
+	KUNIT_EXPECT_TRUE(t, g_inval);
+	/* Clear the injected fault and verify a subsequent transfer succeeds. */
+	g_err = false;
+	check_sync(t, ctx->spi, &ctx->msg, 0);
+}
+
+static int ti_qspi_suite_init(struct kunit_suite *s)
+{
+	int ret = g_region_registered ? 0 : logic_iomem_add_region(&g_iomem_res, &g_rops);
+
+	g_region_registered = !ret;
+	return ret;
+}
+
+static struct kunit_case ti_qspi_test_cases[] = {
+	KUNIT_CASE_SLOW(ti_qspi_test),
+	{}
+};
+
+static struct kunit_suite ti_qspi_test_suite = {
+	.name = "spi_ti_qspi",
+	.suite_init = ti_qspi_suite_init,
+	.init = ti_qspi_init,
+	.exit = ti_qspi_exit,
+	.test_cases = ti_qspi_test_cases,
+};
+
+kunit_test_suite(ti_qspi_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for TI QSPI controller driver");
+MODULE_LICENSE("GPL");
-- 
2.56.0.385.gac671c819a-goog


      parent reply	other threads:[~2026-11-03 22:51 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 22:51 [PATCH v2 0/2] spi: spi-ti-qspi: finalize message on transfer error Joseph CT Chan
2026-11-03 22:51 ` [PATCH v2 1/2] " Joseph CT Chan
2026-11-03 22:51 ` Joseph CT Chan [this message]

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=20261103225116.3624270-3-jctchan@gmail.com \
    --to=jctchan@gmail.com \
    --cc=broonie@kernel.org \
    --cc=bvanassche@acm.org \
    --cc=dlechner@baylibre.com \
    --cc=josephctchan@google.com \
    --cc=linux-spi@vger.kernel.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