[PATCH v2 3/7] mailbox: sun55i: add KUnit test suite for sun55i msgbox driver
From: Tim Michals <hidden>
Date: 2026-09-27 00:20:55
Also in:
linux-devicetree, linux-remoteproc, linux-sunxi, lkml
Subsystem:
mailbox api, the rest · Maintainers:
Jassi Brar, Linus Torvalds
Add comprehensive in-kernel KUnit unit test suite (34 tests) for the Allwinner sun55i hardware Message Box driver. Tests validate: - Channel routing calculations and boundary limits across all 12 channels. - Register offset and bit formula accuracy against hardware specifications. - Mock MMIO message sending, FIFO status polling, and backpressure. - Stale FIFO drain on startup/shutdown capped at SUN55I_FIFO_MAX. - Hardirq multi-port concurrency and simultaneous 3-route message bursts. - Hardware FIFO pop emulation and crosstalk isolation across CPUS/DSP/RV. Signed-off-by: Tim Michals <redacted> --- drivers/mailbox/Kconfig | 10 + drivers/mailbox/Makefile | 1 + drivers/mailbox/sun55i_msgbox_test.c | 823 +++++++++++++++++++++++++++ 3 files changed, 834 insertions(+) create mode 100644 drivers/mailbox/sun55i_msgbox_test.c
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 9067f5161276..af51794d260d 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig@@ -342,6 +342,16 @@ config SUN55I_MSGBOX provides 12 hardware FIFO channels for communication between the Cortex-A55 host, CPUS, HiFi4 DSP, and XuanTie E907 RISC-V coprocessors. +config SUN55I_MSGBOX_KUNIT_TEST + tristate "KUnit tests for Allwinner sun55i msgbox" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + KUnit tests for the Allwinner sun55i-msgbox driver, covering + channel routing table entries, register offset macros, and IRQ bit + positions for communication with CPUS, HiFi4 DSP, and XuanTie RISC-V. + Say Y here to run these tests during boot or via kunit.py. + config SPRD_MBOX tristate "Spreadtrum Mailbox" depends on ARCH_SPRD || COMPILE_TEST
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index aa17f9d0acd4..6fe1f1112c10 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile@@ -71,6 +71,7 @@ obj-$(CONFIG_ZYNQMP_IPI_MBOX) += zynqmp-ipi-mailbox.o obj-$(CONFIG_SUN6I_MSGBOX) += sun6i-msgbox.o obj-$(CONFIG_SUN55I_MSGBOX) += sun55i-msgbox.o +obj-$(CONFIG_SUN55I_MSGBOX_KUNIT_TEST) += sun55i_msgbox_test.o obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o
diff --git a/drivers/mailbox/sun55i_msgbox_test.c b/drivers/mailbox/sun55i_msgbox_test.c
new file mode 100644
index 000000000000..19041b4225da
--- /dev/null
+++ b/drivers/mailbox/sun55i_msgbox_test.c@@ -0,0 +1,823 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for Allwinner sun55i 4-port Message Box driver (sun55i-msgbox.c) + * + * Comprehensive, industry-standard test suite validating: + * - Full 12-channel routing table covering CPUS, DSP, and XuanTie RISC-V cores + * - Boundary and out-of-range channel limits + * - Register offset formulas and IRQ bitmask macros + * - Functional driver ops (send_data, last_tx_done, peek_data, startup, shutdown) + * - Exhaustive 12-channel physical destination register write verification + * - Full threshold sweeps for last_tx_done (0..15) and peek_data (0..15) + * - Multi-message burst FIFO receiving with exact payload sequencing + * - Anti-lockup hardirq bounded loop guarantee (FIFO_MAX limit) + * - Multi-channel and multi-port concurrency in a single hardirq pass + * - Stale FIFO purging on startup under varying depths (0, 4, stuck) + * - Interrupt isolation between channels sharing the same port + * + * Directly invokes driver operations using mock register banks. + * + * Copyright (C) 2026 Tim Michals <tcmichals@gmail.com> + */ + +#include <kunit/test.h> +#include <linux/io.h> +#include <linux/mailbox_client.h> +#include "sun55i-msgbox.h" + +/* =============== Channel Routing Table Tests =============== */ + +static void test_chan_to_route_cpus_all(struct kunit *test) +{ + int local_n, p, remote_id, remote_n; + + /* Ch 0..3: CPUS (remote_id = SUN55I_PROC_CPUS, remote_n = 0, local_n = 0) */ + sun55i_chan_to_route(0, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 0); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_CPUS); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + sun55i_chan_to_route(1, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 1); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_CPUS); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + sun55i_chan_to_route(2, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 2); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_CPUS); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + sun55i_chan_to_route(3, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 3); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_CPUS); + KUNIT_EXPECT_EQ(test, remote_n, 0); +} + +static void test_chan_to_route_dsp_all(struct kunit *test) +{ + int local_n, p, remote_id, remote_n; + + /* Ch 4..7: DSP (remote_id = SUN55I_PROC_DSP, remote_n = 0, local_n = 1) */ + sun55i_chan_to_route(4, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 1); + KUNIT_EXPECT_EQ(test, p, 0); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_DSP); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + sun55i_chan_to_route(5, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 1); + KUNIT_EXPECT_EQ(test, p, 1); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_DSP); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + sun55i_chan_to_route(6, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 1); + KUNIT_EXPECT_EQ(test, p, 2); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_DSP); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + sun55i_chan_to_route(7, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 1); + KUNIT_EXPECT_EQ(test, p, 3); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_DSP); + KUNIT_EXPECT_EQ(test, remote_n, 0); +} + +static void test_chan_to_route_rv_all(struct kunit *test) +{ + int local_n, p, remote_id, remote_n; + + /* Ch 8..11: XuanTie RISC-V (remote_id = SUN55I_PROC_RV, remote_n = 2, local_n = 2) */ + sun55i_chan_to_route(8, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 2); + KUNIT_EXPECT_EQ(test, p, 0); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_RV); + KUNIT_EXPECT_EQ(test, remote_n, 2); + + sun55i_chan_to_route(9, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 2); + KUNIT_EXPECT_EQ(test, p, 1); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_RV); + KUNIT_EXPECT_EQ(test, remote_n, 2); + + sun55i_chan_to_route(10, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 2); + KUNIT_EXPECT_EQ(test, p, 2); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_RV); + KUNIT_EXPECT_EQ(test, remote_n, 2); + + sun55i_chan_to_route(11, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 2); + KUNIT_EXPECT_EQ(test, p, 3); + KUNIT_EXPECT_EQ(test, remote_id, SUN55I_PROC_RV); + KUNIT_EXPECT_EQ(test, remote_n, 2); +} + +static void test_chan_to_route_boundary_limits(struct kunit *test) +{ + int local_n, p, remote_id, remote_n; + + /* Channel 0 (lowest valid) */ + sun55i_chan_to_route(0, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 0); + + /* Channel 11 (highest valid) */ + sun55i_chan_to_route(11, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 2); + KUNIT_EXPECT_EQ(test, p, 3); +} + +static void test_chan_to_route_invalid_channels(struct kunit *test) +{ + int local_n, p, remote_id, remote_n; + + /* Negative channel index -> safely clamped to 0 */ + sun55i_chan_to_route(-1, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 0); + KUNIT_EXPECT_EQ(test, remote_id, 0); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + /* Out of range channel 12 -> safely clamped to 0 */ + sun55i_chan_to_route(12, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 0); + KUNIT_EXPECT_EQ(test, remote_id, 0); + KUNIT_EXPECT_EQ(test, remote_n, 0); + + /* Far out of range channel 100 -> safely clamped to 0 */ + sun55i_chan_to_route(100, &local_n, &p, &remote_id, &remote_n); + KUNIT_EXPECT_EQ(test, local_n, 0); + KUNIT_EXPECT_EQ(test, p, 0); + KUNIT_EXPECT_EQ(test, remote_id, 0); + KUNIT_EXPECT_EQ(test, remote_n, 0); +} + +/* =============== Register Offset Macro Tests =============== */ + +static void test_msgbox_offset_values(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_OFFSET(0), 0x000); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_OFFSET(1), 0x100); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_OFFSET(2), 0x200); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_OFFSET(3), 0x300); +} + +static void test_msgbox_irq_reg_offsets(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_READ_IRQ_ENABLE(0), 0x020); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_READ_IRQ_STATUS(0), 0x024); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_WRITE_IRQ_ENABLE(0), 0x030); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_WRITE_IRQ_STATUS(0), 0x034); + + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_READ_IRQ_ENABLE(1), 0x120); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_READ_IRQ_STATUS(1), 0x124); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_READ_IRQ_ENABLE(2), 0x220); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_READ_IRQ_STATUS(2), 0x224); +} + +static void test_msgbox_fifo_reg_offsets(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_FIFO_STATUS(0, 0), 0x050); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_FIFO_STATUS(0, 3), 0x05c); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_MSG_STATUS(0, 0), 0x060); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_MSG_STATUS(0, 3), 0x06c); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_MSG_FIFO(0, 0), 0x070); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_MSG_FIFO(0, 3), 0x07c); + + /* Port 2 (RV remote in sun55i_msgbox_arm_routes[2]) */ + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_MSG_FIFO(2, 0), 0x270); + KUNIT_EXPECT_EQ(test, SUNXI_MSGBOX_MSG_FIFO(2, 3), 0x27c); +} + +static void test_msgbox_irq_bit_positions(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, RD_IRQ_EN_BIT(0), BIT(0)); + KUNIT_EXPECT_EQ(test, RD_IRQ_EN_BIT(1), BIT(2)); + KUNIT_EXPECT_EQ(test, RD_IRQ_EN_BIT(2), BIT(4)); + KUNIT_EXPECT_EQ(test, RD_IRQ_EN_BIT(3), BIT(6)); + + KUNIT_EXPECT_EQ(test, RD_IRQ_PEND_BIT(0), BIT(0)); + KUNIT_EXPECT_EQ(test, RD_IRQ_PEND_BIT(1), BIT(2)); + KUNIT_EXPECT_EQ(test, RD_IRQ_PEND_BIT(2), BIT(4)); + KUNIT_EXPECT_EQ(test, RD_IRQ_PEND_BIT(3), BIT(6)); +} + +static void test_msgbox_constants(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, SUN55I_MAX_PROCESSORS, 4); + KUNIT_EXPECT_EQ(test, SUN55I_CHANS_PER_PROC, 4); + KUNIT_EXPECT_EQ(test, SUN55I_NUM_CHANS, 12); + KUNIT_EXPECT_EQ(test, SUN55I_FIFO_MAX, 8); + KUNIT_EXPECT_EQ(test, (u32)MSG_NUM_MASK, 0xfU); +} + +/* =============== Functional Driver Ops Tests (Mock MMIO) =============== */ + +struct mock_rx_sink { + struct mbox_client client; + int count; + u32 last_msg; + u32 msgs[SUN55I_FIFO_MAX * 2]; + u32 *status_reg; +}; + +struct mock_msgbox_fixture { + struct sun55i_msgbox mbox; + struct mbox_chan chans[SUN55I_NUM_CHANS]; + struct mock_rx_sink sinks[SUN55I_NUM_CHANS]; + u32 regs[SUN55I_MAX_PROCESSORS][0x400 / 4]; +}; + +static void mock_rx_cb(struct mbox_client *cl, void *data) +{ + struct mock_rx_sink *sink = container_of(cl, struct mock_rx_sink, client); + + if (sink->count < ARRAY_SIZE(sink->msgs)) + sink->msgs[sink->count] = *(u32 *)data; + sink->count++; + sink->last_msg = *(u32 *)data; + + /* + * Emulate hardware FIFO pop behavior: reading a word from the + * message FIFO decrements MSG_STATUS in real hardware. + */ + if (sink->status_reg && (*sink->status_reg & MSG_NUM_MASK) > 0) + (*sink->status_reg)--; +} + +static struct mock_msgbox_fixture *create_mock_fixture(struct kunit *test) +{ + struct mock_msgbox_fixture *fix; + int i; + + fix = kunit_kzalloc(test, sizeof(*fix), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fix); + + spin_lock_init(&fix->mbox.lock); + fix->mbox.controller.chans = fix->chans; + fix->mbox.controller.num_chans = SUN55I_NUM_CHANS; + + for (i = 0; i < SUN55I_MAX_PROCESSORS; i++) + fix->mbox.regs[i] = (void __iomem *)fix->regs[i]; + + for (i = 0; i < SUN55I_NUM_CHANS; i++) { + int local_n, p, remote_id, remote_n; + + sun55i_chan_to_route(i, &local_n, &p, &remote_id, &remote_n); + fix->chans[i].con_priv = &fix->mbox; + fix->sinks[i].client.rx_callback = mock_rx_cb; + fix->sinks[i].status_reg = &fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(local_n, p) / 4]; + fix->chans[i].cl = &fix->sinks[i].client; + } + + return fix; +} + +static void test_functional_send_data_all_twelve_channels(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + int ch; + + /* Send unique test pattern over every channel from 0 to 11 */ + for (ch = 0; ch < SUN55I_NUM_CHANS; ch++) { + int local_n, p, remote_id, remote_n; + u32 val = 0x55000000U | (u32)ch; + u32 reg_idx; + + sun55i_chan_to_route(ch, &local_n, &p, &remote_id, &remote_n); + sun55i_msgbox_chan_ops.send_data(&fix->chans[ch], &val); + + reg_idx = SUNXI_MSGBOX_MSG_FIFO(remote_n, p) / 4; + KUNIT_EXPECT_EQ(test, fix->regs[remote_id][reg_idx], val); + } +} + +static void test_functional_send_data_null(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 reg_idx; + + /* NULL data pointer writes 0 to FIFO without dereferencing */ + sun55i_msgbox_chan_ops.send_data(&fix->chans[8], NULL); + + reg_idx = SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4; + KUNIT_EXPECT_EQ(test, fix->regs[3][reg_idx], 0U); +} + +static void test_functional_send_data_patterns(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + static const u32 patterns[] = { + 0x00000000, 0xFFFFFFFF, 0x55555555, 0xAAAAAAAA, 0x12345678, + }; + int i; + u32 reg_idx = SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4; + + for (i = 0; i < ARRAY_SIZE(patterns); i++) { + sun55i_msgbox_chan_ops.send_data(&fix->chans[8], (void *)&patterns[i]); + KUNIT_EXPECT_EQ(test, fix->regs[3][reg_idx], patterns[i]); + } +} + +static void test_functional_last_tx_done_sweep(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 reg_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + u32 count; + + /* Counts 0..7: FIFO has space -> last_tx_done returns true */ + for (count = 0; count < 8; count++) { + fix->regs[3][reg_idx] = count; + KUNIT_EXPECT_TRUE(test, sun55i_msgbox_chan_ops.last_tx_done(&fix->chans[8])); + } + + /* Counts 8..15: FIFO is full or overflow -> last_tx_done returns false */ + for (count = 8; count <= 15; count++) { + fix->regs[3][reg_idx] = count; + KUNIT_EXPECT_FALSE(test, sun55i_msgbox_chan_ops.last_tx_done(&fix->chans[8])); + } +} + +static void test_functional_peek_data_sweep(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 reg_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + u32 count; + + /* Count 0: no messages -> returns false */ + fix->regs[0][reg_idx] = 0; + KUNIT_EXPECT_FALSE(test, sun55i_msgbox_chan_ops.peek_data(&fix->chans[8])); + + /* Counts 1..15: has messages -> returns true */ + for (count = 1; count <= 15; count++) { + fix->regs[0][reg_idx] = count; + KUNIT_EXPECT_TRUE(test, sun55i_msgbox_chan_ops.peek_data(&fix->chans[8])); + } +} + +static void test_functional_startup_and_shutdown(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4; + + /* Startup on Channel 8: enables IRQ and clears pending status */ + sun55i_msgbox_chan_ops.startup(&fix->chans[8]); + KUNIT_EXPECT_EQ(test, fix->regs[0][en_idx] & RD_IRQ_EN_BIT(0), RD_IRQ_EN_BIT(0)); + KUNIT_EXPECT_EQ(test, fix->regs[0][stat_idx] & RD_IRQ_PEND_BIT(0), RD_IRQ_PEND_BIT(0)); + + /* Shutdown on Channel 8: disables IRQ */ + sun55i_msgbox_chan_ops.shutdown(&fix->chans[8]); + KUNIT_EXPECT_EQ(test, fix->regs[0][en_idx] & RD_IRQ_EN_BIT(0), 0U); +} + +static void test_functional_shutdown_isolation(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4; + + /* Enable both Channel 0 and Channel 1 on Port 0 */ + sun55i_msgbox_chan_ops.startup(&fix->chans[0]); + sun55i_msgbox_chan_ops.startup(&fix->chans[1]); + KUNIT_EXPECT_EQ(test, fix->regs[0][en_idx] & (RD_IRQ_EN_BIT(0) | RD_IRQ_EN_BIT(1)), + RD_IRQ_EN_BIT(0) | RD_IRQ_EN_BIT(1)); + + /* Shutting down Channel 0 must leave Channel 1 enabled */ + sun55i_msgbox_chan_ops.shutdown(&fix->chans[0]); + KUNIT_EXPECT_EQ(test, fix->regs[0][en_idx] & RD_IRQ_EN_BIT(0), 0U); + KUNIT_EXPECT_EQ(test, fix->regs[0][en_idx] & RD_IRQ_EN_BIT(1), RD_IRQ_EN_BIT(1)); +} + +static void test_functional_startup_flushes_stale_fifo(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4; + u32 msg_stat_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + u32 fifo_idx = SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4; + int ret; + + /* Simulate stale messages present in hardware FIFO before channel open */ + fix->regs[0][msg_stat_idx] = 4; + fix->regs[0][fifo_idx] = 0xDEADBEEF; + + ret = sun55i_msgbox_chan_ops.startup(&fix->chans[8]); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* IRQ must be enabled and pending status cleared */ + KUNIT_EXPECT_EQ(test, fix->regs[0][en_idx] & RD_IRQ_EN_BIT(0), RD_IRQ_EN_BIT(0)); + KUNIT_EXPECT_EQ(test, fix->regs[0][stat_idx] & RD_IRQ_PEND_BIT(0), RD_IRQ_PEND_BIT(0)); +} + +static void test_functional_startup_capped_at_fifo_max(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 msg_stat_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + u32 fifo_idx = SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4; + int ret; + + /* Hardware defect: MSG_STATUS always returns non-zero */ + fix->regs[0][msg_stat_idx] = 15; + fix->regs[0][fifo_idx] = 0xCAFEBABE; + + /* Must terminate after SUN55I_FIFO_MAX reads and return 0 */ + ret = sun55i_msgbox_chan_ops.startup(&fix->chans[8]); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +static void test_functional_shutdown_flushes_and_bounds(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4; + u32 msg_stat_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + u32 fifo_idx = SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4; + + /* First startup */ + sun55i_msgbox_chan_ops.startup(&fix->chans[8]); + + /* Simulate residual messages arriving before shutdown */ + fix->regs[0][msg_stat_idx] = 15; + fix->regs[0][fifo_idx] = 0x11223344; + + /* Must terminate after SUN55I_FIFO_MAX without hanging */ + sun55i_msgbox_chan_ops.shutdown(&fix->chans[8]); + KUNIT_EXPECT_EQ(test, fix->regs[0][en_idx] & RD_IRQ_EN_BIT(0), 0U); + KUNIT_EXPECT_EQ(test, fix->regs[0][stat_idx] & RD_IRQ_PEND_BIT(0), RD_IRQ_PEND_BIT(0)); +} + +/* =============== Interrupt Handler (Hardirq) Simulation =============== */ + +static void test_irq_spurious_returns_none(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + irqreturn_t ret; + + /* All IRQ enable and status registers are 0 -> IRQ_NONE */ + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_NONE); +} + +static void test_irq_single_message_received(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4; + u32 msg_stat_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + u32 fifo_idx = SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4; + irqreturn_t ret; + + /* Configure pending IRQ on Channel 8 (local_n = 2, p = 0) */ + fix->regs[0][en_idx] = RD_IRQ_EN_BIT(0); + fix->regs[0][stat_idx] = RD_IRQ_PEND_BIT(0); + fix->regs[0][msg_stat_idx] = 1; /* 1 message in FIFO */ + fix->regs[0][fifo_idx] = 0xCAFEF00D; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* Verify data delivered to mailbox client */ + KUNIT_EXPECT_EQ(test, fix->sinks[8].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[8].last_msg, 0xCAFEF00DU); + + /* Verify write-1-to-clear bit was written */ + KUNIT_EXPECT_EQ(test, fix->regs[0][stat_idx], RD_IRQ_PEND_BIT(0)); +} + +static void test_irq_empty_fifo_status_clear(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4; + u32 msg_stat_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + irqreturn_t ret; + + /* Pending IRQ bit set, but MSG_STATUS has 0 messages */ + fix->regs[0][en_idx] = RD_IRQ_EN_BIT(0); + fix->regs[0][stat_idx] = RD_IRQ_PEND_BIT(0); + fix->regs[0][msg_stat_idx] = 0; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* No messages should be dispatched to client */ + KUNIT_EXPECT_EQ(test, fix->sinks[8].count, 0); + + /* Status must still be cleared */ + KUNIT_EXPECT_EQ(test, fix->regs[0][stat_idx], RD_IRQ_PEND_BIT(0)); +} + +static void test_irq_multi_channel_concurrency(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(0) / 4; + irqreturn_t ret; + + /* Both Channel 0 (p=0) and Channel 1 (p=1) pending on Port 0 */ + fix->regs[0][en_idx] = RD_IRQ_EN_BIT(0) | RD_IRQ_EN_BIT(1); + fix->regs[0][stat_idx] = RD_IRQ_PEND_BIT(0) | RD_IRQ_PEND_BIT(1); + + /* Ch 0 has message 0x11111111 */ + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(0, 0) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(0, 0) / 4] = 0x11111111; + + /* Ch 1 has message 0x22222222 */ + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(0, 1) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(0, 1) / 4] = 0x22222222; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* Both channels must be serviced in the same interrupt invocation */ + KUNIT_EXPECT_EQ(test, fix->sinks[0].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[0].last_msg, 0x11111111U); + + KUNIT_EXPECT_EQ(test, fix->sinks[1].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[1].last_msg, 0x22222222U); +} + +static void test_irq_multi_port_concurrency(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + irqreturn_t ret; + + /* Port 0 (Channel 0: CPUS) has pending interrupt */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(0) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(0, 0) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(0, 0) / 4] = 0xAAAAAAAA; + + /* Port 2 (Channel 8: RV) has pending interrupt */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4] = 0xBBBBBBBB; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* Both ports must be processed in the single pass across local_n = 0..2 */ + KUNIT_EXPECT_EQ(test, fix->sinks[0].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[0].last_msg, 0xAAAAAAAAU); + + KUNIT_EXPECT_EQ(test, fix->sinks[8].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[8].last_msg, 0xBBBBBBBBU); +} + +static void test_irq_fifo_drain_capped_at_max(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4; + u32 msg_stat_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + u32 fifo_idx = SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4; + irqreturn_t ret; + + /* + * Simulate hardware defect or runaway remote: MSG_STATUS is permanently + * non-zero. The ISR MUST NOT hang in an infinite loop; it must drain + * at most SUN55I_FIFO_MAX (8) messages and return. + */ + fix->regs[0][en_idx] = RD_IRQ_EN_BIT(0); + fix->regs[0][stat_idx] = RD_IRQ_PEND_BIT(0); + fix->regs[0][msg_stat_idx] = 15; /* Always reports data */ + fix->regs[0][fifo_idx] = 0x12345678; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* Drain must be strictly bounded at SUN55I_FIFO_MAX */ + KUNIT_EXPECT_EQ(test, fix->sinks[8].count, SUN55I_FIFO_MAX); +} + +static void test_irq_channel_crosstalk_isolation(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(0) / 4; + irqreturn_t ret; + int ch; + + /* Only Channel 2 (Port 0, p=2) has an interrupt enabled and pending */ + fix->regs[0][en_idx] = RD_IRQ_EN_BIT(2); + fix->regs[0][stat_idx] = RD_IRQ_PEND_BIT(2); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(0, 2) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(0, 2) / 4] = 0x33333333; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* Exactly Channel 2 received the message */ + KUNIT_EXPECT_EQ(test, fix->sinks[2].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[2].last_msg, 0x33333333U); + + /* All other 11 channels must have received 0 messages */ + for (ch = 0; ch < SUN55I_NUM_CHANS; ch++) { + if (ch == 2) + continue; + KUNIT_EXPECT_EQ(test, fix->sinks[ch].count, 0); + } +} + +static void test_irq_all_three_routes_simultaneous(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + irqreturn_t ret; + + /* Port 0: Channel 0 (CPUS) */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(0) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(0, 0) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(0, 0) / 4] = 0x11110001; + + /* Port 1: Channel 4 (DSP) */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(1) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(1) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(1, 0) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(1, 0) / 4] = 0x22220001; + + /* Port 2: Channel 8 (RV) */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4] = 0x33330001; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* All three destinations serviced cleanly in one ISR pass */ + KUNIT_EXPECT_EQ(test, fix->sinks[0].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[0].last_msg, 0x11110001U); + + KUNIT_EXPECT_EQ(test, fix->sinks[4].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[4].last_msg, 0x22220001U); + + KUNIT_EXPECT_EQ(test, fix->sinks[8].count, 1); + KUNIT_EXPECT_EQ(test, fix->sinks[8].last_msg, 0x33330001U); +} + +static void test_irq_disabled_channel_ignored(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 en_idx = SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4; + u32 stat_idx = SUNXI_MSGBOX_READ_IRQ_STATUS(0) / 4; + irqreturn_t ret; + + /* Channel 1 has pending bit set, but IRQ_ENABLE is 0 (channel disabled) */ + fix->regs[0][en_idx] = 0; + fix->regs[0][stat_idx] = RD_IRQ_PEND_BIT(1); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(0, 1) / 4] = 1; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(0, 1) / 4] = 0x12345678; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + /* Must return IRQ_NONE and must NOT deliver message */ + KUNIT_EXPECT_EQ(test, ret, IRQ_NONE); + KUNIT_EXPECT_EQ(test, fix->sinks[1].count, 0); +} + +static void test_irq_spurious_noise_bits(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + irqreturn_t ret; + + /* Non-channel upper bits set, no valid channel enabled */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4] = 0; + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(0) / 4] = 0xFFFF0000U; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_NONE); +} + +static void test_functional_last_tx_done_backpressure_boundary(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + u32 reg_idx = SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4; + bool done; + + /* 7 entries in FIFO -> space available (< 8) -> returns true */ + fix->regs[3][reg_idx] = 7; + done = sun55i_msgbox_chan_ops.last_tx_done(&fix->chans[8]); + KUNIT_EXPECT_TRUE(test, done); + + /* 8 entries in FIFO -> full (== SUN55I_FIFO_MAX) -> backpressure active (false) */ + fix->regs[3][reg_idx] = 8; + done = sun55i_msgbox_chan_ops.last_tx_done(&fix->chans[8]); + KUNIT_EXPECT_FALSE(test, done); + + /* 15 entries in FIFO -> full -> backpressure active (false) */ + fix->regs[3][reg_idx] = 15; + done = sun55i_msgbox_chan_ops.last_tx_done(&fix->chans[8]); + KUNIT_EXPECT_FALSE(test, done); +} + +static void test_irq_multi_port_burst_interleaved(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + irqreturn_t ret; + + /* Port 0 (Ch 0: CPUS) has 2 messages */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(0) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(0) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(0, 0) / 4] = 2; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(0, 0) / 4] = 0xAA01; + + /* Port 1 (Ch 4: DSP) has 2 messages */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(1) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(1) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(1, 0) / 4] = 2; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(1, 0) / 4] = 0xBB01; + + /* Port 2 (Ch 8: RV) has 2 messages */ + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_ENABLE(2) / 4] = RD_IRQ_EN_BIT(0); + fix->regs[0][SUNXI_MSGBOX_READ_IRQ_STATUS(2) / 4] = RD_IRQ_PEND_BIT(0); + fix->regs[0][SUNXI_MSGBOX_MSG_STATUS(2, 0) / 4] = 2; + fix->regs[0][SUNXI_MSGBOX_MSG_FIFO(2, 0) / 4] = 0xCC01; + + ret = sun55i_msgbox_irq(0, &fix->mbox); + KUNIT_EXPECT_EQ(test, ret, IRQ_HANDLED); + + /* Check deliveries to respective sinks */ + KUNIT_EXPECT_EQ(test, fix->sinks[0].count, 2); + KUNIT_EXPECT_EQ(test, fix->sinks[4].count, 2); + KUNIT_EXPECT_EQ(test, fix->sinks[8].count, 2); +} + +static void test_msgbox_controller_invariants(struct kunit *test) +{ + struct mock_msgbox_fixture *fix = create_mock_fixture(test); + + KUNIT_EXPECT_EQ(test, fix->mbox.controller.num_chans, SUN55I_NUM_CHANS); + KUNIT_EXPECT_PTR_EQ(test, fix->mbox.controller.chans, &fix->chans[0]); +} + +static void test_msgbox_chan_ops_completeness(struct kunit *test) +{ + KUNIT_EXPECT_NOT_NULL(test, sun55i_msgbox_chan_ops.send_data); + KUNIT_EXPECT_NOT_NULL(test, sun55i_msgbox_chan_ops.startup); + KUNIT_EXPECT_NOT_NULL(test, sun55i_msgbox_chan_ops.shutdown); + KUNIT_EXPECT_NOT_NULL(test, sun55i_msgbox_chan_ops.last_tx_done); + KUNIT_EXPECT_NOT_NULL(test, sun55i_msgbox_chan_ops.peek_data); +} + +/* =============== Test Suite Registration =============== */ + +static struct kunit_case sun55i_msgbox_cases[] = { + /* Routing Table Tests */ + KUNIT_CASE(test_chan_to_route_cpus_all), + KUNIT_CASE(test_chan_to_route_dsp_all), + KUNIT_CASE(test_chan_to_route_rv_all), + KUNIT_CASE(test_chan_to_route_boundary_limits), + KUNIT_CASE(test_chan_to_route_invalid_channels), + /* Register Macro Tests */ + KUNIT_CASE(test_msgbox_offset_values), + KUNIT_CASE(test_msgbox_irq_reg_offsets), + KUNIT_CASE(test_msgbox_fifo_reg_offsets), + KUNIT_CASE(test_msgbox_irq_bit_positions), + KUNIT_CASE(test_msgbox_constants), + /* Functional Driver Ops Tests */ + KUNIT_CASE(test_functional_send_data_all_twelve_channels), + KUNIT_CASE(test_functional_send_data_null), + KUNIT_CASE(test_functional_send_data_patterns), + KUNIT_CASE(test_functional_last_tx_done_sweep), + KUNIT_CASE(test_functional_peek_data_sweep), + KUNIT_CASE(test_functional_startup_and_shutdown), + KUNIT_CASE(test_functional_shutdown_isolation), + KUNIT_CASE(test_functional_startup_flushes_stale_fifo), + KUNIT_CASE(test_functional_startup_capped_at_fifo_max), + KUNIT_CASE(test_functional_shutdown_flushes_and_bounds), + /* Hardirq Simulation Tests */ + KUNIT_CASE(test_irq_spurious_returns_none), + KUNIT_CASE(test_irq_spurious_noise_bits), + KUNIT_CASE(test_irq_disabled_channel_ignored), + KUNIT_CASE(test_irq_single_message_received), + KUNIT_CASE(test_irq_empty_fifo_status_clear), + KUNIT_CASE(test_irq_multi_channel_concurrency), + KUNIT_CASE(test_irq_multi_port_concurrency), + KUNIT_CASE(test_irq_multi_port_burst_interleaved), + KUNIT_CASE(test_irq_fifo_drain_capped_at_max), + KUNIT_CASE(test_irq_channel_crosstalk_isolation), + KUNIT_CASE(test_irq_all_three_routes_simultaneous), + KUNIT_CASE(test_functional_last_tx_done_backpressure_boundary), + /* Ops & Controller Invariants */ + KUNIT_CASE(test_msgbox_chan_ops_completeness), + KUNIT_CASE(test_msgbox_controller_invariants), + {} +}; + +static struct kunit_suite sun55i_msgbox_test_suite = { + .name = "sun55i_msgbox", + .test_cases = sun55i_msgbox_cases, +}; + +kunit_test_suite(sun55i_msgbox_test_suite); + +MODULE_AUTHOR("Tim Michals <tcmichals@gmail.com>"); +MODULE_DESCRIPTION("Comprehensive KUnit tests for Allwinner sun55i Message Box driver"); +MODULE_LICENSE("GPL");
--
2.43.0