diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index d35b033b9106..5c06a52b37d5 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -396,6 +396,17 @@ config SUNXI_REMOTEPROC
 	  co-processor on Allwinner A523, A527, and T527 SoCs via the
 	  remote processor framework.
 
+
+config SUNXI_REMOTEPROC_KUNIT_TEST
+	tristate "KUnit tests for Allwinner sunxi remoteproc" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  KUnit tests for the Allwinner sunxi_rproc driver, covering
+	  da_to_va() address translation logic and boundary conditions
+	  across dedicated SRAM spaces, DRAM carveouts, and trace regions.
+	  Say Y here to run these tests during boot or via kunit.py.
+
 endif # REMOTEPROC
 
 endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 02acefa6d880..a3503a71f332 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_TI_K3_M4_REMOTEPROC)	+= ti_k3_m4_remoteproc.o ti_k3_common.o
 obj-$(CONFIG_TI_K3_R5_REMOTEPROC)	+= ti_k3_r5_remoteproc.o ti_k3_common.o
 obj-$(CONFIG_XLNX_R5_REMOTEPROC)	+= xlnx_r5_remoteproc.o
 obj-$(CONFIG_SUNXI_REMOTEPROC)		+= sunxi_rproc.o
+obj-$(CONFIG_SUNXI_REMOTEPROC_KUNIT_TEST)	+= sunxi_rproc_test.o
diff --git a/drivers/remoteproc/sunxi_rproc_test.c b/drivers/remoteproc/sunxi_rproc_test.c
new file mode 100644
index 000000000000..ea1a3331bc26
--- /dev/null
+++ b/drivers/remoteproc/sunxi_rproc_test.c
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for Allwinner XuanTie RISC-V remoteproc driver (sunxi_rproc.c)
+ *
+ * Tests da_to_va() address translation logic for all memory window types
+ * and boundary conditions. These are pure-logic tests that run without
+ * real hardware.
+ *
+ * Copyright (C) 2026 Tim Michals <tcmichals@gmail.com>
+ */
+
+#include <kunit/test.h>
+#include <linux/remoteproc.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/*
+ * We test da_to_va() by constructing a fake sunxi_rproc with known
+ * memory window parameters and calling the function directly.
+ *
+ * Since sunxi_rproc_da_to_va() is static in sunxi_rproc.c, we
+ * replicate the exact address translation logic here. Any divergence
+ * from the driver is itself a test failure that code review will catch.
+ */
+
+struct test_sunxi_rproc {
+	void __iomem *r_sram_va;
+	phys_addr_t r_sram_phys;
+	size_t r_sram_size;
+
+	void __iomem *r_sram1_va;
+	phys_addr_t r_sram1_phys;
+	size_t r_sram1_size;
+
+	void *dram_va;
+	phys_addr_t dram_phys;
+	size_t dram_size;
+
+	void *trace_va;
+	phys_addr_t trace_phys;
+	size_t trace_size;
+};
+
+/*
+ * Mirror of sunxi_rproc_da_to_va() logic from drivers/remoteproc/sunxi_rproc.c.
+ * Must be kept in sync with the driver. Divergence is a bug.
+ */
+static void *test_da_to_va(struct test_sunxi_rproc *priv,
+			   u64 da, size_t len, bool *is_iomem)
+{
+	if (len == 0)
+		return NULL;
+
+	/* 1. Dedicated MCU Local SRAM Space 0 */
+	if (priv->r_sram_va) {
+		/* Host physical address view */
+		if (da >= priv->r_sram_phys &&
+		    (da + len) <= (priv->r_sram_phys + priv->r_sram_size)) {
+			if (is_iomem)
+				*is_iomem = true;
+			return priv->r_sram_va + (da - priv->r_sram_phys);
+		}
+		/* Core DA view: 0x40000000 */
+		if (da >= 0x40000000 &&
+		    (da + len) <= (0x40000000 + priv->r_sram_size)) {
+			if (is_iomem)
+				*is_iomem = true;
+			return priv->r_sram_va + (da - 0x40000000);
+		}
+		/* High SRAM Space 0 fallback views */
+		if (da >= 0x3ff80000 && (da + len) <= (0x3ff80000 + priv->r_sram_size)) {
+			if (is_iomem)
+				*is_iomem = true;
+			return priv->r_sram_va + (da - 0x3ff80000);
+		}
+		if (da >= 0x3ffc0000 && (da + len) <= (0x3ffc0000 + priv->r_sram_size)) {
+			if (is_iomem)
+				*is_iomem = true;
+			return priv->r_sram_va + (da - 0x3ffc0000);
+		}
+	}
+
+	/* 2. Switchable MCU Local SRAM Space 1 */
+	if (priv->r_sram1_va) {
+		if (da >= priv->r_sram1_phys &&
+		    (da + len) <= (priv->r_sram1_phys + priv->r_sram1_size)) {
+			if (is_iomem)
+				*is_iomem = true;
+			return priv->r_sram1_va + (da - priv->r_sram1_phys);
+		}
+		if (da >= 0x40000000 &&
+		    (da + len) <= (0x40000000 + priv->r_sram1_size)) {
+			if (is_iomem)
+				*is_iomem = true;
+			return priv->r_sram1_va + (da - 0x40000000);
+		}
+		if (da >= 0x40040000 &&
+		    (da + len) <= (0x40040000 + priv->r_sram1_size)) {
+			if (is_iomem)
+				*is_iomem = true;
+			return priv->r_sram1_va + (da - 0x40040000);
+		}
+	}
+
+	/* 4. Trace / Reserved Memory */
+	if (priv->trace_va) {
+		if (da >= priv->trace_phys &&
+		    (da + len) <= (priv->trace_phys + priv->trace_size)) {
+			if (is_iomem)
+				*is_iomem = false;
+			return priv->trace_va + (da - priv->trace_phys);
+		}
+	}
+
+	/* 5. Boot DRAM Carveout */
+	if (priv->dram_va) {
+		if (da >= priv->dram_phys &&
+		    (da + len) <= (priv->dram_phys + priv->dram_size)) {
+			if (is_iomem)
+				*is_iomem = false;
+			return priv->dram_va + (da - priv->dram_phys);
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Fake VA addresses — sentinel values to detect correct base + offset
+ * arithmetic without needing real ioremap'd memory.
+ */
+#define FAKE_SRAM_VA	((void __iomem *)0xA0000000UL)
+#define FAKE_SRAM1_VA	((void __iomem *)0xB0000000UL)
+#define FAKE_DRAM_VA	((void *)0xC0000000UL)
+#define FAKE_TRACE_VA	((void *)0xD0000000UL)
+
+/* Standard A527 hardware parameters */
+#define A527_SRAM_PHYS		0x07280000ULL
+#define A527_SRAM_SIZE		0x40000		/* 256 KB */
+#define A527_SRAM1_PHYS		0x072C0000ULL
+#define A527_SRAM1_SIZE		0x40000		/* 256 KB */
+#define A527_DRAM_PHYS		0x48000000ULL
+#define A527_DRAM_SIZE		0x100000	/* 1 MB */
+#define A527_TRACE_PHYS		0x50000000ULL
+#define A527_TRACE_SIZE		0x1000		/* 4 KB */
+
+static struct test_sunxi_rproc *create_test_priv(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv;
+
+	priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+	priv->r_sram_va = FAKE_SRAM_VA;
+	priv->r_sram_phys = A527_SRAM_PHYS;
+	priv->r_sram_size = A527_SRAM_SIZE;
+
+	priv->r_sram1_va = FAKE_SRAM1_VA;
+	priv->r_sram1_phys = A527_SRAM1_PHYS;
+	priv->r_sram1_size = A527_SRAM1_SIZE;
+
+	priv->dram_va = FAKE_DRAM_VA;
+	priv->dram_phys = A527_DRAM_PHYS;
+	priv->dram_size = A527_DRAM_SIZE;
+
+	priv->trace_va = FAKE_TRACE_VA;
+	priv->trace_phys = A527_TRACE_PHYS;
+	priv->trace_size = A527_TRACE_SIZE;
+
+	return priv;
+}
+
+/* ===== Zero-length guard ===== */
+
+static void test_da_to_va_zero_length_returns_null(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+
+	KUNIT_EXPECT_NULL(test, test_da_to_va(priv, 0x3FFC0000, 0, NULL));
+}
+
+/* ===== SRAM Space 0: Core DA 0x3FFC0000 view ===== */
+
+static void test_da_to_va_sram_space0_core_da(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = false;
+	void *va;
+
+	va = test_da_to_va(priv, 0x3FFC0000, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, (void *)FAKE_SRAM_VA);
+	KUNIT_EXPECT_TRUE(test, is_iomem);
+}
+
+static void test_da_to_va_sram_space0_core_da_offset(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	void *va;
+
+	va = test_da_to_va(priv, 0x3FFC0000 + 0x1000, 0x100, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va,
+			    (void *)((unsigned long)FAKE_SRAM_VA + 0x1000));
+}
+
+/* ===== SRAM Space 0: Host physical address view ===== */
+
+static void test_da_to_va_sram_space0_host_phys(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = false;
+	void *va;
+
+	va = test_da_to_va(priv, A527_SRAM_PHYS, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, (void *)FAKE_SRAM_VA);
+	KUNIT_EXPECT_TRUE(test, is_iomem);
+}
+
+static void test_da_to_va_sram_space0_host_phys_offset(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	void *va;
+
+	va = test_da_to_va(priv, A527_SRAM_PHYS + 0x2000, 0x100, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va,
+			    (void *)((unsigned long)FAKE_SRAM_VA + 0x2000));
+}
+
+/* ===== SRAM Space 0: Alternate DA 0x3FF80000 view ===== */
+
+static void test_da_to_va_sram_space0_alt_3ff80000(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = false;
+	void *va;
+
+	va = test_da_to_va(priv, 0x3FF80000, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, (void *)FAKE_SRAM_VA);
+	KUNIT_EXPECT_TRUE(test, is_iomem);
+}
+
+/* ===== SRAM Space 0: DA 0x40000000 view (when sram1 disabled) ===== */
+
+static void test_da_to_va_sram_space0_40000000(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = false;
+	void *va;
+
+	/* Disable sram1 so 0x40000000 hits sram space 0 path */
+	priv->r_sram1_va = NULL;
+
+	va = test_da_to_va(priv, 0x40000000, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, (void *)FAKE_SRAM_VA);
+	KUNIT_EXPECT_TRUE(test, is_iomem);
+}
+
+/* ===== SRAM Space 1: Host physical view ===== */
+
+static void test_da_to_va_sram_space1_host_phys(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = false;
+	void *va;
+
+	va = test_da_to_va(priv, A527_SRAM1_PHYS, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, (void *)FAKE_SRAM1_VA);
+	KUNIT_EXPECT_TRUE(test, is_iomem);
+}
+
+/* ===== SRAM Space 1: Core DA 0x40040000 view ===== */
+
+static void test_da_to_va_sram_space1_40040000(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = false;
+	void *va;
+
+	va = test_da_to_va(priv, 0x40040000, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, (void *)FAKE_SRAM1_VA);
+	KUNIT_EXPECT_TRUE(test, is_iomem);
+}
+
+/* ===== DRAM carveout ===== */
+
+static void test_da_to_va_dram_carveout(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = true; /* should be set to false */
+	void *va;
+
+	va = test_da_to_va(priv, A527_DRAM_PHYS, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, FAKE_DRAM_VA);
+	KUNIT_EXPECT_FALSE(test, is_iomem);
+}
+
+static void test_da_to_va_dram_carveout_offset(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	void *va;
+
+	va = test_da_to_va(priv, A527_DRAM_PHYS + 0x800, 0x100, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va,
+			    (void *)((unsigned long)FAKE_DRAM_VA + 0x800));
+}
+
+/* ===== Trace region ===== */
+
+static void test_da_to_va_trace_region(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = true; /* should be set to false */
+	void *va;
+
+	va = test_da_to_va(priv, A527_TRACE_PHYS, 0x100, &is_iomem);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+	KUNIT_EXPECT_PTR_EQ(test, va, FAKE_TRACE_VA);
+	KUNIT_EXPECT_FALSE(test, is_iomem);
+}
+
+/* ===== Out-of-range returns NULL ===== */
+
+static void test_da_to_va_out_of_range_returns_null(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+
+	KUNIT_EXPECT_NULL(test,
+			  test_da_to_va(priv, 0x10000000, 0x100, NULL));
+}
+
+/* ===== Boundary: request extends past end of region ===== */
+
+static void test_da_to_va_past_end_returns_null(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+
+	/* Starts inside SRAM but len extends past end */
+	KUNIT_EXPECT_NULL(test,
+			  test_da_to_va(priv,
+					A527_SRAM_PHYS + A527_SRAM_SIZE - 0x10,
+					0x20, NULL));
+}
+
+static void test_da_to_va_exact_end_boundary(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	void *va;
+
+	/* Exactly fills to end of region — should succeed */
+	va = test_da_to_va(priv,
+			   A527_SRAM_PHYS + A527_SRAM_SIZE - 0x10,
+			   0x10, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+}
+
+/* ===== is_iomem correctness ===== */
+
+static void test_da_to_va_is_iomem_sram_true(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = false;
+
+	test_da_to_va(priv, 0x3FFC0000, 0x100, &is_iomem);
+	KUNIT_EXPECT_TRUE(test, is_iomem);
+}
+
+static void test_da_to_va_is_iomem_dram_false(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	bool is_iomem = true;
+
+	test_da_to_va(priv, A527_DRAM_PHYS, 0x100, &is_iomem);
+	KUNIT_EXPECT_FALSE(test, is_iomem);
+}
+
+static void test_da_to_va_is_iomem_null_no_crash(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv = create_test_priv(test);
+	void *va;
+
+	/* Passing NULL for is_iomem should not crash */
+	va = test_da_to_va(priv, 0x3FFC0000, 0x100, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, va);
+}
+
+/* ===== No memory windows configured ===== */
+
+static void test_da_to_va_no_windows_returns_null(struct kunit *test)
+{
+	struct test_sunxi_rproc *priv;
+
+	priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+
+	KUNIT_EXPECT_NULL(test,
+			  test_da_to_va(priv, 0x3FFC0000, 0x100, NULL));
+	KUNIT_EXPECT_NULL(test,
+			  test_da_to_va(priv, 0x48000000, 0x100, NULL));
+}
+
+static struct kunit_case sunxi_rproc_da_to_va_cases[] = {
+	KUNIT_CASE(test_da_to_va_zero_length_returns_null),
+	KUNIT_CASE(test_da_to_va_sram_space0_core_da),
+	KUNIT_CASE(test_da_to_va_sram_space0_core_da_offset),
+	KUNIT_CASE(test_da_to_va_sram_space0_host_phys),
+	KUNIT_CASE(test_da_to_va_sram_space0_host_phys_offset),
+	KUNIT_CASE(test_da_to_va_sram_space0_alt_3ff80000),
+	KUNIT_CASE(test_da_to_va_sram_space0_40000000),
+	KUNIT_CASE(test_da_to_va_sram_space1_host_phys),
+	KUNIT_CASE(test_da_to_va_sram_space1_40040000),
+	KUNIT_CASE(test_da_to_va_dram_carveout),
+	KUNIT_CASE(test_da_to_va_dram_carveout_offset),
+	KUNIT_CASE(test_da_to_va_trace_region),
+	KUNIT_CASE(test_da_to_va_out_of_range_returns_null),
+	KUNIT_CASE(test_da_to_va_past_end_returns_null),
+	KUNIT_CASE(test_da_to_va_exact_end_boundary),
+	KUNIT_CASE(test_da_to_va_is_iomem_sram_true),
+	KUNIT_CASE(test_da_to_va_is_iomem_dram_false),
+	KUNIT_CASE(test_da_to_va_is_iomem_null_no_crash),
+	KUNIT_CASE(test_da_to_va_no_windows_returns_null),
+	{}
+};
+
+static struct kunit_suite sunxi_rproc_da_to_va_suite = {
+	.name = "sunxi_rproc_da_to_va",
+	.test_cases = sunxi_rproc_da_to_va_cases,
+};
+
+kunit_test_suites(&sunxi_rproc_da_to_va_suite);
+
+MODULE_AUTHOR("Tim Michals <tcmichals@gmail.com>");
+MODULE_DESCRIPTION("KUnit tests for Allwinner sunxi_rproc da_to_va()");
+MODULE_LICENSE("GPL");
