[PATCH v16 18/18] cxl: Add KUnit checks for HDM decoder unpacking

From: Srirangan Madhavan

Date: Thu Oct 29 2026 - 11:02:50 EST


Test cxl_hdm_unpack_decoder() without a PCI device or HDM MMIO. Cover
range boundaries and overflow, an all-ones base, zero-size and uncommitted
decoders, and a locked host-only decoder with 12-way interleave.

Run the checks as a KUnit suite rather than during cxl_test module loading,
so a failure does not prevent unrelated CXL mock tests from running. An
uncommitted decoder has no meaningful target type, so do not check it.

Signed-off-by: Srirangan Madhavan <smadhavan@xxxxxxxxxx>
---
drivers/cxl/.kunitconfig | 4 ++
drivers/cxl/Kconfig | 10 ++++
drivers/cxl/Makefile | 1 +
drivers/cxl/core/hdm_regs.c | 2 +-
drivers/cxl/tests/Makefile | 5 ++
drivers/cxl/tests/hdm_unpack_kunit.c | 90 ++++++++++++++++++++++++++++
6 files changed, 111 insertions(+), 1 deletion(-)
create mode 100644 drivers/cxl/.kunitconfig
create mode 100644 drivers/cxl/tests/Makefile
create mode 100644 drivers/cxl/tests/hdm_unpack_kunit.c

diff --git a/drivers/cxl/.kunitconfig b/drivers/cxl/.kunitconfig
new file mode 100644
index 000000000000..4fee684f5ff6
--- /dev/null
+++ b/drivers/cxl/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_PCI=y
+CONFIG_CXL_BUS=y
+CONFIG_CXL_KUNIT_TEST=y
diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
index e927e69a3f7b..7f70f6babd74 100644
--- a/drivers/cxl/Kconfig
+++ b/drivers/cxl/Kconfig
@@ -213,6 +213,16 @@ config CXL_REGION

If unsure say 'y'

+config CXL_KUNIT_TEST
+ tristate "KUnit tests for CXL" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Build the KUnit tests for the CXL subsystem. When built as modules,
+ each test suite is its own module.
+
+ If unsure, say N.
+
config CXL_REGION_INVALIDATION_TEST
bool "CXL: Region Cache Management Bypass (TEST)"
depends on CXL_REGION
diff --git a/drivers/cxl/Makefile b/drivers/cxl/Makefile
index 2caa90fa4bf2..d57fb0e9e7da 100644
--- a/drivers/cxl/Makefile
+++ b/drivers/cxl/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o
obj-$(CONFIG_CXL_PMEM) += cxl_pmem.o
obj-$(CONFIG_CXL_MEM) += cxl_mem.o
obj-$(CONFIG_CXL_PCI) += cxl_pci.o
+obj-$(CONFIG_CXL_KUNIT_TEST) += tests/

cxl_port-y := port.o
cxl_acpi-y := acpi.o
diff --git a/drivers/cxl/core/hdm_regs.c b/drivers/cxl/core/hdm_regs.c
index fe5e65a9ddb1..ef1b0e07c352 100644
--- a/drivers/cxl/core/hdm_regs.c
+++ b/drivers/cxl/core/hdm_regs.c
@@ -174,7 +174,7 @@ int cxl_hdm_unpack_decoder(struct cxl_decoder_config *config, int id,

return 0;
}
-EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core");
+EXPORT_SYMBOL_FOR_MODULES(cxl_hdm_unpack_decoder, "cxl_core,cxl_hdm_unpack_kunit");

int cxl_hdm_read_decoder(struct cxl_decoder_config *config, void __iomem *hdm,
int id, u32 ctrl)
diff --git a/drivers/cxl/tests/Makefile b/drivers/cxl/tests/Makefile
new file mode 100644
index 000000000000..c29dccfa9b24
--- /dev/null
+++ b/drivers/cxl/tests/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+ccflags-y += -I$(srctree)/drivers/cxl
+
+obj-$(CONFIG_CXL_KUNIT_TEST) += cxl_hdm_unpack_kunit.o
+cxl_hdm_unpack_kunit-y := hdm_unpack_kunit.o
diff --git a/drivers/cxl/tests/hdm_unpack_kunit.c b/drivers/cxl/tests/hdm_unpack_kunit.c
new file mode 100644
index 000000000000..6e5b59ef6309
--- /dev/null
+++ b/drivers/cxl/tests/hdm_unpack_kunit.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 NVIDIA Corporation & Affiliates */
+#include <kunit/test.h>
+#include <linux/bitfield.h>
+#include <linux/sizes.h>
+
+#include "cxl.h"
+#include "../core/core.h"
+
+static void cxl_hdm_unpack_test(struct kunit *test)
+{
+ static const struct {
+ const char *name;
+ u32 ctrl;
+ u64 base;
+ u64 size;
+ int rc;
+ unsigned long flags;
+ int ways;
+ int granularity;
+ enum cxl_decoder_type target_type;
+ } cases[] = {
+ { "valid", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ SZ_1G, SZ_256M, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "overflow", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX - 127, 256, -ENXIO },
+ { "all-ones base", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX, SZ_256M, -ENXIO },
+ { "last address", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ U64_MAX - 255, 256, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "zero size", CXL_HDM_DECODER0_CTRL_COMMITTED,
+ SZ_1G, 0, 0, CXL_DECODER_F_ENABLE,
+ 1, 256, CXL_DECODER_DEVMEM },
+ { "uncommitted stale size", 0,
+ SZ_1G, SZ_256M, 0, 0, 1, 256 },
+ { "locked host-only interleave",
+ CXL_HDM_DECODER0_CTRL_COMMITTED | CXL_HDM_DECODER0_CTRL_LOCK |
+ CXL_HDM_DECODER0_CTRL_HOSTONLY |
+ FIELD_PREP_CONST(CXL_HDM_DECODER0_CTRL_IW_MASK, 10) |
+ FIELD_PREP_CONST(CXL_HDM_DECODER0_CTRL_IG_MASK, 2),
+ SZ_1G, SZ_256M, 0, CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK,
+ 12, SZ_1K, CXL_DECODER_HOSTONLYMEM },
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(cases); i++) {
+ struct cxl_decoder_config config;
+ u64 size;
+ int rc;
+
+ rc = cxl_hdm_unpack_decoder(&config, 3, cases[i].ctrl,
+ cases[i].base, cases[i].size);
+ KUNIT_EXPECT_EQ_MSG(test, rc, cases[i].rc, "%s", cases[i].name);
+ if (rc || rc != cases[i].rc)
+ continue;
+ size = cases[i].size;
+ if (!(cases[i].flags & CXL_DECODER_F_ENABLE))
+ size = 0;
+ KUNIT_EXPECT_EQ_MSG(test, config.id, 3, "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.hpa_range.start, cases[i].base,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, range_len(&config.hpa_range), size,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.flags, cases[i].flags,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.interleave_ways, cases[i].ways,
+ "%s", cases[i].name);
+ KUNIT_EXPECT_EQ_MSG(test, config.interleave_granularity,
+ cases[i].granularity, "%s", cases[i].name);
+ /* An uncommitted decoder has no meaningful target type. */
+ if (cases[i].flags & CXL_DECODER_F_ENABLE)
+ KUNIT_EXPECT_EQ_MSG(test, config.target_type,
+ cases[i].target_type, "%s", cases[i].name);
+ }
+}
+
+static struct kunit_case cxl_hdm_test_cases[] = {
+ KUNIT_CASE(cxl_hdm_unpack_test),
+ {}
+};
+
+static struct kunit_suite cxl_hdm_test_suite = {
+ .name = "cxl-hdm-unpack",
+ .test_cases = cxl_hdm_test_cases,
+};
+kunit_test_suite(cxl_hdm_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for CXL HDM decoder unpacking");
+MODULE_LICENSE("GPL");
--
2.43.0