All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC v2] Add "uunit" unit testing framework for CXL code
       [not found] <CGME20240215002612uscas1p2990a3c0029e653c10fd905a96800b6ba@uscas1p2.samsung.com>
@ 2024-02-15  0:26 ` Jim Harris
  0 siblings, 0 replies; only message in thread
From: Jim Harris @ 2024-02-15  0:26 UTC (permalink / raw
  To: linux-cxl@vger.kernel.org, dan.j.williams@intel.com,
	alison.schofield@intel.com, dave@stgolabs.net

uunit (userspace unit testing) generates wrappers for kernel source files
which are compiled and linked with necessary function stubs and executed
fully in userspace.

Existing unit testing frameworks (kunit, tools/testing/cxl) have some
limitations that uunit alleviates:

* mock referenced functions without requiring modifications to those functions
* mock register accesses
* does not require execution in a running kernel
* no restrictions currently on CONFIG parameters
  (Note: some CXL code cannot run in UML with kunit due to dependencies on
  CONFIG_SPARSEMEM)

This RFC contains some complex unit tests for core/region.c code,
a simple core/hdm.c test showing how register accesses can be mocked, and
core/port.c tests with a regression test for a specific patch fix and a
demonstration of how to mock memory allocation failures.

This RFC depends on CUnit, which needs to be installed on the system prior to build.

To build the unit tests (from within a repo that has already built a Linux
kernel):

  cd tools/testing/cxl/uunit
  make -j

To run the unit tests:

  cd tools/testing/cxl/uunit
  ./runtests.sh

There is a balance currently between creating stubs for referenced functions,
versus just building and linking the referenced source files directly. For
example, CXL uses xarray extensively, so we just build and link the xarray
code directly into unit tests rather than creating stubs for xarray. See
KERNEL_SRC in the Makefile for which kernel source files are directly built
and linked in this manner.

Stubs are in uunit/stub.c, and are grouped by the kernel source file containing
the real definition. Those groups are listed in alphabetical order.

The build process has three primary steps:

Modify headers - some kernel headers require modification - don't
modify them inline though, create a copy in the uunit/build directory,
modify them, and set the include directories so that the build will
pick up the modified copy first. Explanations for the modified headers
are in the Makefile.

Wrap and build kernel source - create .c files for everything in KERNEL_SRC
that just includes their corresponding source file in the main kernel tree.
This allows us to define our own Kbuild for building these files, specifically
specifying -O0 and enabling ASAN/UBSAN.

Build kernel source and unit test applications - this step uses kernel
build system plus Kbuild file from uunit directory. Unit test applications are
found in uunit/app directory, and named like core_region_ut.c to associate
with core/region.c. The test applications include the stub.c file directly to
simplify mocking stub behavior.

Note that the kernel defines many functions that are also in libc.
Kernel also defines some data structures with same name as
/usr/include data structures. So unit test applications that marry kernel
headers with POSIX headers wrap all kernel includes with
uunit/include/pre.h and uunit/include/post.h to do some pre-processor magic
to eliminate these conflicts.

Note that this framework is not CXL-specific - it could be used to unit
test a vast majority of other kernel code. Proposal is to just start
with this framework in tools/testing/cxl to meet CXL-specific needs.

Changes since RFC v1:
* Add Kbuild file to compile all uunit-related source file with kernel build
  system. Now only the link phase is handled by the uunit Makefile. This
  addresses feedback from djbw to look at simplifying how uunit gets
  include paths and cflags.
* Renamed "app" directory to "test" (djbw)
* Added prep_register() function for preparing mocked registers for testing,
  to follow kernel code's writel() style (djbw)
* Add regression test for d6488fee66 (reverting this patch will generate an
  ASAN failure as described in the patch's commit message)
* Add unit test demonstrating how to mock/inject memory allocation failures

Todo:
* Add more regression tests
* Continue investigating methods for avoiding/minimizing post-processing
  of auto-generated header files. Possibilities include creating a uunit-specific
  arch which would allow for setting cflags automatically for -O0 and
  the sanitizers (suggestion from Luis Chamberlain).

Signed-off-by: Jim Harris <jim.harris@samsung.com>
---
 tools/testing/cxl/uunit/.gitignore            |    2 
 tools/testing/cxl/uunit/Kbuild                |   39 +
 tools/testing/cxl/uunit/Makefile              |   98 +++
 tools/testing/cxl/uunit/post.h                |   26 +
 tools/testing/cxl/uunit/pre.h                 |   22 +
 tools/testing/cxl/uunit/runtests.sh           |    9 
 tools/testing/cxl/uunit/stub.c                |  969 +++++++++++++++++++++++++
 tools/testing/cxl/uunit/test/core_hdm_ut.c    |   84 ++
 tools/testing/cxl/uunit/test/core_port_ut.c   |   92 ++
 tools/testing/cxl/uunit/test/core_region_ut.c |  344 +++++++++
 tools/testing/cxl/uunit/uunit.h               |    6 
 11 files changed, 1691 insertions(+)
 create mode 100644 tools/testing/cxl/uunit/.gitignore
 create mode 100644 tools/testing/cxl/uunit/Kbuild
 create mode 100644 tools/testing/cxl/uunit/Makefile
 create mode 100644 tools/testing/cxl/uunit/post.h
 create mode 100644 tools/testing/cxl/uunit/pre.h
 create mode 100755 tools/testing/cxl/uunit/runtests.sh
 create mode 100644 tools/testing/cxl/uunit/stub.c
 create mode 100644 tools/testing/cxl/uunit/test/core_hdm_ut.c
 create mode 100644 tools/testing/cxl/uunit/test/core_port_ut.c
 create mode 100644 tools/testing/cxl/uunit/test/core_region_ut.c
 create mode 100644 tools/testing/cxl/uunit/uunit.h

diff --git a/tools/testing/cxl/uunit/.gitignore b/tools/testing/cxl/uunit/.gitignore
new file mode 100644
index 000000000000..f18cc2a9032f
--- /dev/null
+++ b/tools/testing/cxl/uunit/.gitignore
@@ -0,0 +1,2 @@
+build/
+test/*.o
diff --git a/tools/testing/cxl/uunit/Kbuild b/tools/testing/cxl/uunit/Kbuild
new file mode 100644
index 000000000000..d552ed007926
--- /dev/null
+++ b/tools/testing/cxl/uunit/Kbuild
@@ -0,0 +1,39 @@
+LINUXINCLUDE := -I$(srctree)/tools/testing/cxl/uunit $(LINUXINCLUDE)
+LINUXINCLUDE := -I$(srctree)/tools/testing/cxl/uunit/build/include $(LINUXINCLUDE)
+NOSTDINC_FLAGS :=
+
+undefine CONFIG_OBJTOOL
+
+ccflags-y += -I$(srctree)
+ccflags-y += -fno-sanitize=shadow-call-stack -Wno-frame-larger-than
+ccflags-y += -O0 -D__NO_FORTIFY
+ccflags-y += -fsanitize=address -fsanitize=undefined
+
+ccflags-remove-y += -mindirect-branch=thunk-extern
+ccflags-remove-y += -mfunction-return=thunk-extern
+ccflags-remove-y += -mfentry
+ccflags-remove-y += -DCC_USING_FENTRY
+ccflags-remove-y += -fstack-protector-strong
+ccflags-remove-y += -mstack-protector%
+
+CFLAGS_test/core_hdm_ut.o := -I$(srctree)/drivers/cxl
+obj-m += test/core_hdm_ut.o
+CFLAGS_test/core_port_ut.o := -I$(srctree)/drivers/cxl
+obj-m += test/core_port_ut.o
+CFLAGS_test/core_region_ut.o := -I$(srctree)/drivers/cxl
+obj-m += test/core_region_ut.o
+
+obj-m += build/drivers/base/bus.c
+obj-m += build/drivers/base/driver.c
+obj-m += build/lib/kobject.c
+obj-m += build/lib/klist.c
+obj-m += build/lib/refcount.c
+obj-m += build/lib/xarray.c
+obj-m += build/lib/find_bit.c
+obj-m += build/lib/hweight.c
+obj-m += build/lib/radix-tree.c
+obj-m += build/lib/uuid.c
+CFLAGS_build/lib/kstrtox.o := -O2
+obj-m += build/lib/kstrtox.c
+obj-m += build/lib/sort.c
+obj-m += build/lib/hexdump.c
diff --git a/tools/testing/cxl/uunit/Makefile b/tools/testing/cxl/uunit/Makefile
new file mode 100644
index 000000000000..6108ec5b3c3b
--- /dev/null
+++ b/tools/testing/cxl/uunit/Makefile
@@ -0,0 +1,98 @@
+# SPDX-License-Identifier: GPL-2.0
+THIS_DIR := tools/testing/cxl/uunit
+KERNEL_DIR := ../../../..
+
+Q ?= @
+.SECONDARY:
+
+LINK_FLAGS = -no-pie -lcunit -fsanitize=address -fsanitize=undefined
+
+# The following source files are built and linked into each unit test application
+# instead of creating stubs for them.
+KERNEL_SRC = \
+	drivers/base/bus.c \
+	drivers/base/driver.c \
+	lib/kobject.c \
+	lib/klist.c \
+	lib/refcount.c \
+	lib/xarray.c \
+	lib/find_bit.c \
+	lib/hweight.c \
+	lib/radix-tree.c \
+	lib/uuid.c \
+	lib/hexdump.c \
+	lib/kstrtox.c \
+	lib/sort.c
+
+UNIT_TEST_SRC = \
+	test/core_hdm_ut.c \
+	test/core_region_ut.c \
+	test/core_port_ut.c
+
+UNIT_TEST_BIN = $(patsubst test/%.c,build/bin/%,$(UNIT_TEST_SRC))
+
+all: $(UNIT_TEST_BIN)
+
+clean:
+	$(Q)rm -rf build test/*.o
+
+# compiler_attributes.h uses __attribute__(__error__) for static asserts - this depends
+# on the compiler optimizing away the function, so for unit test builds with -O0 we
+# create a new version of that header to ensure the attribute doesn't get used.
+MODIFIED_HEADERS = build/include/linux/compiler_attributes.h
+
+# We will disable some configuration settings for unit tests, to simplify the unit test
+# environment.
+MODIFIED_HEADERS += build/include/generated/autoconf.h
+
+# WARN_FLAGS generates constraint errors on x86. So we will modify bug.h slightly to
+# workaround this problem.
+MODIFIED_HEADERS += build/include/asm-generic/bug.h
+
+build/include/linux/compiler_attributes.h: $(KERNEL_DIR)/include/linux/compiler_attributes.h
+	$(Q)echo "  HEADER  $(THIS_DIR)/$@"
+	$(Q)mkdir -p $(dir $@)
+	$(Q)sed 's/__has_attribute(__error__)/& \&\& defined(__OPTIMIZE__)/' $< > $@
+
+build/include/generated/autoconf.h: $(KERNEL_DIR)/include/generated/autoconf.h
+	$(Q)echo "  HEADER  $(THIS_DIR)/$@"
+	$(Q)mkdir -p $(dir $@)
+	$(Q)cp $< $@
+	$(Q)sed -i 's/#define CONFIG_DYNAMIC_DEBUG 1//' $@
+	$(Q)sed -i 's/#define CONFIG_DYNAMIC_DEBUG_CORE 1//' $@
+	$(Q)sed -i 's/#define CONFIG_HAVE_FENTRY 1//' $@
+	$(Q)sed -i 's/#define CONFIG_LIST_HARDENED 1//' $@
+	$(Q)sed -i 's/#define CONFIG_PREEMPTION 1//' $@
+	$(Q)sed -i 's/#define CONFIG_PREEMPT_DYNAMIC 1//' $@
+	$(Q)sed -i 's/#define CONFIG_DEBUG_LOCK_ALLOC 1//' $@
+	$(Q)sed -i 's/#define CONFIG_DEBUG_PREEMPT 1//' $@
+	$(Q)sed -i 's/#define CONFIG_DEBUG_SPINLOCK 1//' $@
+	$(Q)sed -i 's/#define CONFIG_TRACE_MMIO_ACCESS 1//' $@
+	$(Q)sed -i 's/#define CONFIG_LOCKDEP 1//' $@
+
+build/include/asm-generic/bug.h: $(KERNEL_DIR)/include/asm-generic/bug.h
+	$(Q)echo "  HEADER  $(THIS_DIR)/$@"
+	$(Q)mkdir -p $(dir $@)
+	$(Q)cp $< $@
+	$(Q)sed -i 's/#ifndef __WARN_FLAGS/#undef __WARN_FLAGS\n&/' $@
+
+UUNIT_KERNEL_SRC = $(patsubst %.c,build/%.c,$(KERNEL_SRC))
+UUNIT_KERNEL_OBJ = $(subst .c,.o,$(UUNIT_KERNEL_SRC))
+
+MAKEFLAGS += --no-print-directory
+
+build/%.c: $(KERNEL_DIR)/%.c
+	$(Q)echo "  SRC     $(THIS_DIR)/$@"
+	$(Q)mkdir -p $(dir $@)
+	$(Q)echo \#include \"$(KERNEL_DIR)/$*.c\" > $@
+
+build/%.o: $(MODIFIED_HEADERS) build/%.c Makefile
+	$(Q)make -C $(KERNEL_DIR) M=$(THIS_DIR) $@
+
+test/%_ut.o: test/%_ut.c stub.c $(MODIFIED_HEADERS) Makefile
+	$(Q)make -C $(KERNEL_DIR) M=$(THIS_DIR) $@
+
+build/bin/%_ut: $(UUNIT_KERNEL_OBJ) test/%_ut.o
+	$(Q)echo "  LINK    $(THIS_DIR)/$@"
+	$(Q)mkdir -p $(dir $@)
+	$(Q)$(CC) -o $@ $^ $(LINK_FLAGS)
diff --git a/tools/testing/cxl/uunit/post.h b/tools/testing/cxl/uunit/post.h
new file mode 100644
index 000000000000..575ec0fb0386
--- /dev/null
+++ b/tools/testing/cxl/uunit/post.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+#undef strncpy
+#undef strcpy
+#undef strcat
+#undef strncat
+#undef strlcat
+#undef strlcpy
+#undef errno
+#undef loff_t
+#undef dev_t
+#undef timer_t
+#undef int64_t
+#undef u_int64_t
+#undef sigset_t
+#undef fd_set
+#undef blkcnt_t
+#undef nlink_t
+#undef ffs
+
+/*
+ * Kernel defines these macros which are also defined by userspace headers.
+ * So undefine them here after they've been used for kernel code, so they
+ * can be defined again by userspace headers.
+ */
+#undef abs
+#undef __alloc_size__
diff --git a/tools/testing/cxl/uunit/pre.h b/tools/testing/cxl/uunit/pre.h
new file mode 100644
index 000000000000..ba790f5785a7
--- /dev/null
+++ b/tools/testing/cxl/uunit/pre.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * These #defines rename kernel functions and types that are also defined
+ * by userspace. We redefine the names here, then undef them in post.c
+ * before any userspace headers are included.
+ */
+#define strncpy		__kstrncpy
+#define strcpy		__kstrcpy
+#define strcat		__kstrcat
+#define strncat		__kstrncat
+#define strlcat		__kstrlcat
+#define strlcpy		__kstrlcpy
+#define errno		__kerrno
+#define loff_t		__kloff_t
+#define dev_t		__kdev_t
+#define timer_t		__ktimer_t
+#define int64_t		__kint64_t
+#define u_int64_t	__ku_int64_t
+#define sigset_t	__ksigset_t
+#define fd_set		__kfd_set
+#define blkcnt_t	__kblkcnt_t
+#define nlink_t		__knlink_t
diff --git a/tools/testing/cxl/uunit/runtests.sh b/tools/testing/cxl/uunit/runtests.sh
new file mode 100755
index 000000000000..a88881265e40
--- /dev/null
+++ b/tools/testing/cxl/uunit/runtests.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+set -e
+
+UUNIT_DIR=$(dirname $0)
+
+$UUNIT_DIR/build/bin/core_hdm_ut
+$UUNIT_DIR/build/bin/core_port_ut
+$UUNIT_DIR/build/bin/core_region_ut
diff --git a/tools/testing/cxl/uunit/stub.c b/tools/testing/cxl/uunit/stub.c
new file mode 100644
index 000000000000..c2e42ffbf086
--- /dev/null
+++ b/tools/testing/cxl/uunit/stub.c
@@ -0,0 +1,969 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pre.h"
+#include "linux/cpuhotplug.h"
+#include "linux/rwsem.h"
+#include "linux/kobject.h"
+#include "linux/device/bus.h"
+#include "linux/debugfs.h"
+#include "linux/delay.h"
+#include "linux/memregion.h"
+#include "linux/smp.h"
+#include "asm-generic/delay.h"
+#include "drivers/base/base.h"
+#include "drivers/cxl/cxl.h"
+#include "drivers/cxl/cxlpci.h"
+#include "post.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+/* All modules require one of these. */
+struct module __this_module;
+
+/*
+ * All stubs must be place in a section for the associated kernel source file that defines it.
+ * All sections must be sorted by pathname.
+ */
+
+/*
+ * arch
+ * These cannot be mapped to a specific file, since archs are free to define them wherever they
+ * want in their arch directory.
+ */
+void __udelay(unsigned long usecs)
+{
+}
+
+unsigned long __stack_chk_guard;
+#ifdef CONFIG_ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION
+int cpu_cache_invalidate_memregion(int res_desc)
+{
+	return 0;
+}
+
+bool cpu_cache_has_invalidate_memregion(void)
+{
+	return true;
+}
+#endif
+
+#ifdef CONFIG_ARM64
+/* arch/arm64/kernel/alternative.c */
+void alt_cb_patch_nops(struct alt_instr *alt, __le32 *origptr, __le32 *updptr, int nr_inst)
+{
+}
+
+/* arch/arm64/lib/copy_from_user.S */
+unsigned long __arch_copy_from_user(void *to, const void *from, unsigned long n)
+{
+	return n;
+}
+
+DECLARE_BITMAP(system_cpucaps, ARM64_NCAPS);
+#endif
+
+#ifdef CONFIG_X86_64
+/* arch/x86/kernel/cpu/common.c */
+struct pcpu_hot pcpu_hot;
+
+/* arch/x86/kernel/setup_percpu.c */
+unsigned long this_cpu_off;
+#endif
+
+/* drivers/base/core.c */
+int device_add(struct device *dev)
+{
+	return 0;
+}
+
+struct device *get_device(struct device *dev)
+{
+	return NULL;
+}
+
+void put_device(struct device *dev)
+{
+}
+
+void device_unregister(struct device *dev)
+{
+}
+
+int device_for_each_child(struct device *dev, void *data, int (*fn)(struct device *dev, void *data))
+{
+	return 0;
+}
+
+struct device *device_find_child(struct device *dev, void *data,
+				 int (*match)(struct device *dev, void *data))
+{
+	return NULL;
+}
+
+struct device *device_find_child_by_name(struct device *parent, const char *name)
+{
+	return NULL;
+}
+
+const char *dev_driver_string(const struct device *dev)
+{
+	return NULL;
+}
+
+void device_del(struct device *dev)
+{
+}
+
+int device_match_name(struct device *dev, const void *name)
+{
+	return 0;
+}
+
+void device_initialize(struct device *dev)
+{
+}
+
+int dev_set_name(struct device *dev, const char *name, ...)
+{
+	return 0;
+}
+
+void device_remove_groups(struct device *dev, const struct attribute_group **groups)
+{
+}
+
+int device_register(struct device *dev)
+{
+	return 0;
+}
+
+int device_add_groups(struct device *dev, const struct attribute_group **groups)
+{
+	return 0;
+}
+
+struct kobject *virtual_device_parent(struct device *dev)
+{
+	return NULL;
+}
+
+struct kset *devices_kset;
+
+/* drivers/base/dd.c */
+int device_attach(struct device *dev)
+{
+	return 0;
+}
+
+void device_release_driver(struct device *dev)
+{
+}
+
+int device_driver_attach(struct device_driver *drv, struct device *dev)
+{
+	return 0;
+}
+
+void device_driver_detach(struct device *dev)
+{
+}
+
+int driver_attach(struct device_driver *drv)
+{
+	return 0;
+}
+
+void driver_detach(struct device_driver *drv)
+{
+}
+
+void device_initial_probe(struct device *dev)
+{
+}
+
+void deferred_probe_extend_timeout(void)
+{
+}
+
+/* drivers/base/devres.c */
+int __devm_add_action(struct device *dev, void (*action)(void *), void *data, const char *name)
+{
+	return 0;
+}
+
+void devm_release_action(struct device *dev, void (*action)(void *), void *data)
+{
+}
+
+void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp)
+{
+	return NULL;
+}
+
+void devm_remove_action(struct device *dev, void (*action)(void *), void *data)
+{
+}
+
+void devm_kfree(struct device *dev, const void *p)
+{
+}
+
+/* drivers/base/module.c */
+void module_add_driver(struct module *mod, struct device_driver *drv)
+{
+}
+
+void module_remove_driver(struct device_driver *drv)
+{
+}
+
+/* drivers/base/platform.c */
+struct device platform_bus;
+struct bus_type platform_bus_type;
+
+/* drivers/char/random.c */
+void get_random_bytes(void *buf, size_t len)
+{
+}
+
+/* drivers/cxl/core/hdm.c */
+#ifndef UUNIT_CORE_HDM_C
+DECLARE_RWSEM(cxl_dpa_rwsem);
+int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, unsigned long long size)
+{
+	return 0;
+}
+
+int cxl_dpa_free(struct cxl_endpoint_decoder *cxled)
+{
+	return 0;
+}
+
+int cxl_dpa_set_mode(struct cxl_endpoint_decoder *cxled, enum cxl_decoder_mode mode)
+{
+	return 0;
+}
+
+resource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled)
+{
+	return 0;
+}
+
+resource_size_t cxl_dpa_resource_start(struct cxl_endpoint_decoder *cxled)
+{
+	return 0;
+}
+#endif /* UUNIT_CORE_HDM_C */
+
+/* drivers/cxl/core/mbox.c */
+int cxl_mem_get_poison(struct cxl_memdev *cxlmd, u64 offset, u64 len, struct cxl_region *cxlr)
+{
+	return 0;
+}
+
+void cxl_mbox_init(void)
+{
+}
+
+/* drivers/cxl/core/memdev.c */
+bool is_cxl_memdev(const struct device *dev)
+{
+	return false;
+}
+
+int cxl_memdev_init(void)
+{
+	return 0;
+}
+
+void cxl_memdev_exit(void)
+{
+}
+
+/* drivers/cxl/core/pci.c */
+long cxl_pci_get_latency(struct pci_dev *pdev)
+{
+	return 0;
+}
+
+/* drivers/cxl/core/pmem.c */
+struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_memdev *cxlmd)
+{
+	return NULL;
+}
+const struct device_type cxl_nvdimm_type;
+const struct device_type cxl_nvdimm_bridge_type;
+
+/* drivers/cxl/core/pmu.c */
+const struct device_type cxl_pmu_type;
+
+/* drivers/cxl/core/port.c */
+#ifndef UUNIT_CORE_PORT_C
+struct attribute_group cxl_base_attribute_group;
+struct bus_type cxl_bus_type;
+DECLARE_RWSEM(cxl_region_rwsem);
+
+struct cxl_port *to_cxl_port(const struct device *dev)
+{
+	return container_of(dev, struct cxl_port, dev);
+}
+
+struct cxl_root_decoder *to_cxl_root_decoder(struct device *dev)
+{
+	return container_of(dev, struct cxl_root_decoder, cxlsd.cxld.dev);
+}
+
+struct cxl_switch_decoder *to_cxl_switch_decoder(struct device *dev)
+{
+	return container_of(dev, struct cxl_switch_decoder, cxld.dev);
+}
+
+struct cxl_endpoint_decoder *to_cxl_endpoint_decoder(struct device *dev)
+{
+	return NULL;
+}
+
+struct cxl_decoder *to_cxl_decoder(struct device *dev)
+{
+	return NULL;
+}
+
+bool is_endpoint_decoder(struct device *dev)
+{
+	return true;
+}
+
+bool is_root_decoder(struct device *dev)
+{
+	return true;
+}
+
+bool is_switch_decoder(struct device *dev)
+{
+	return true;
+}
+
+int __cxl_driver_register(struct cxl_driver *cxl_drv, struct module *owner, const char *modname)
+{
+	return 0;
+}
+
+void cxl_driver_unregister(struct cxl_driver *cxl_drv)
+{
+}
+
+int cxl_decoder_add_locked(struct cxl_decoder *cxld, int *target_map)
+{
+	return 0;
+}
+
+int cxl_decoder_autoremove(struct device *host, struct cxl_decoder *cxld)
+{
+	return 0;
+}
+
+struct cxl_switch_decoder *cxl_switch_decoder_alloc(struct cxl_port *port, unsigned int nr_targets)
+{
+	return NULL;
+}
+
+struct cxl_endpoint_decoder *cxl_endpoint_decoder_alloc(struct cxl_port *port)
+{
+	return NULL;
+}
+
+int cxl_num_decoders_committed(struct cxl_port *port)
+{
+	return 0;
+}
+#endif /* UUNIT_CORE_PORT_C */
+
+/* drivers/cxl/core/region.c */
+#ifndef UUNIT_CORE_REGION_C
+const struct device_type cxl_pmem_region_type;
+const struct device_type cxl_region_type;
+const struct device_type cxl_dax_region_type;
+
+int cxl_region_init(void)
+{
+	return 0;
+}
+
+void cxl_region_exit(void)
+{
+}
+void cxl_decoder_kill_region(struct cxl_endpoint_decoder *cxled)
+{
+}
+
+struct device_attribute dev_attr_delete_region;
+struct device_attribute dev_attr_region;
+struct device_attribute dev_attr_create_pmem_region;
+struct device_attribute dev_attr_create_ram_region;
+#endif /* UUNIT_CORE_REGION_C */
+
+/* drivers/cxl/core/regs.c */
+int cxl_setup_regs(struct cxl_register_map *map)
+{
+	return 0;
+}
+
+int cxl_map_component_regs(const struct cxl_register_map *map, struct cxl_component_regs *regs,
+			   unsigned long map_mask)
+{
+	return 0;
+}
+
+void cxl_probe_component_regs(struct device *dev, void *base, struct cxl_component_reg_map *map)
+{
+}
+
+int cxl_find_regblock(struct pci_dev *dev, enum cxl_regloc_type type, struct cxl_register_map *map)
+{
+	return 0;
+}
+
+resource_size_t __rcrb_to_component(struct device *dev, struct cxl_rcrb_info *ri,
+				    enum cxl_rcrb which)
+{
+	return 0;
+}
+
+/* drivers/irqchip/irq-gic-v3.c */
+struct static_key_false gic_nonsecure_priorities;
+
+/* drivers/pci/pci.c */
+u32 pcie_bandwidth_available(struct pci_dev *dev, struct pci_dev **limiting_dev,
+			     enum pci_bus_speed *speed, enum pcie_link_width *width)
+{
+	return 0;
+}
+
+/* drivers/pci/pci-driver.c */
+struct bus_type pci_bus_type;
+
+/* fs/debugfs/inode.c */
+struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
+{
+	return NULL;
+}
+
+void debugfs_remove(struct dentry *dentry)
+{
+}
+
+/* fs/kernfs/dir.c */
+void kernfs_get(struct kernfs_node *kn)
+{
+}
+
+void kernfs_put(struct kernfs_node *kn)
+{
+}
+
+/* fs/seq_file.c */
+void seq_printf(struct seq_file *m, const char *f, ...)
+{
+}
+
+/* fs/sysfs/file.c */
+int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr, const void *ns)
+{
+	return 0;
+}
+
+void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr, const void *ns)
+{
+}
+
+int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
+{
+	return 0;
+}
+
+int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, const void *new_ns)
+{
+	return 0;
+}
+
+int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name, const void *new_ns)
+{
+	return 0;
+}
+
+int sysfs_create_groups(struct kobject *kobj, const struct attribute_group **groups)
+{
+	return 0;
+}
+
+void sysfs_remove_groups(struct kobject *kobj, const struct attribute_group **groups)
+{
+}
+
+int sysfs_update_group(struct kobject *kobj, const struct attribute_group *grp)
+{
+	return 0;
+}
+
+int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name)
+{
+	return 0;
+}
+
+void sysfs_remove_link(struct kobject *kobj, const char *name)
+{
+}
+
+void sysfs_remove_dir(struct kobject *kobj)
+{
+}
+
+int sysfs_emit(char *buf, const char *fmt, ...)
+{
+	va_list argptr;
+	int rc;
+
+	va_start(argptr, fmt);
+	rc = vsprintf(buf, fmt, argptr);
+	va_end(argptr);
+
+	return rc;
+}
+
+int sysfs_emit_at(char *buf, int at, const char *fmt, ...)
+{
+	return 0;
+}
+
+bool sysfs_streq(const char *s1, const char *s2)
+{
+	return true;
+}
+
+/* include linux/bitfield.h */
+/* Resolves undefined reference error when compiling at -O0 */
+void __bad_mask(void)
+{
+}
+
+/* include/linux/dev_printk.h, lib/dynamic_debug.c */
+void _dev_err(const struct device *dev, const char *fmt, ...)
+{
+}
+
+void _dev_warn(const struct device *dev, const char *fmt, ...)
+{
+}
+
+void _dev_info(const struct device *dev, const char *fmt, ...)
+{
+}
+
+void __warn_printk(const char *fmt, ...)
+{
+}
+
+int _printk(const char *fmt, ...)
+{
+	return 0;
+}
+
+const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list ap)
+{
+	return NULL;
+}
+
+/* kernel/cpu.c */
+int __cpuhp_setup_state(enum cpuhp_state state, const char *name, bool invoke,
+			int (*startup)(unsigned int cpu), int (*teardown)(unsigned int cpu),
+			bool multi_instance)
+{
+	return 0;
+}
+
+/* kernel/locking/mutex.c */
+void mutex_lock(struct mutex *lock)
+{
+}
+
+void mutex_unlock(struct mutex *lock)
+{
+}
+
+void __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
+{
+}
+
+/* kernel/locking/rwsem.c */
+void __init_rwsem(struct rw_semaphore *sem, const char *name, struct lock_class_key *key)
+{
+}
+
+void up_read(struct rw_semaphore *sem)
+{
+}
+
+void down_read(struct rw_semaphore *sem)
+{
+}
+
+int down_read_interruptible(struct rw_semaphore *sem)
+{
+	return 0;
+}
+
+void up_write(struct rw_semaphore *sem)
+{
+}
+
+void down_write(struct rw_semaphore *sem)
+{
+}
+
+int down_write_killable(struct rw_semaphore *sem)
+{
+	return 0;
+}
+
+/* kernel/locking/spinlock.c */
+void _raw_spin_lock(raw_spinlock_t *lock)
+{
+}
+
+void _raw_spin_lock_irq(raw_spinlock_t *lock)
+{
+}
+
+void _raw_spin_lock_bh(raw_spinlock_t *lock)
+{
+}
+
+void _raw_spin_unlock(raw_spinlock_t *lock)
+{
+}
+
+void _raw_spin_unlock_irq(raw_spinlock_t *lock)
+{
+}
+
+void _raw_spin_unlock_bh(raw_spinlock_t *lock)
+{
+}
+
+unsigned long _raw_spin_lock_irqsave(raw_spinlock_t *lock)
+{
+	return 0;
+}
+
+void _raw_spin_unlock_irqrestore(raw_spinlock_t *lock, unsigned long flags)
+{
+}
+
+/* kernel/notifier.c */
+int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n)
+{
+	return 0;
+}
+
+int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *n)
+{
+	return 0;
+}
+
+int blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v)
+{
+	return 0;
+}
+
+/* kernel/panic.c */
+void __stack_chk_fail(void)
+{
+}
+
+void warn_slowpath_fmt(const char *file, const int line, unsigned taint, const char *fmt, ...)
+{
+}
+
+/* kernel/rcu/tree.c */
+void call_rcu(struct rcu_head *head, rcu_callback_t func)
+{
+	func(head);
+}
+
+/* kernel/rcu/tree_plugin.h */
+void __rcu_read_lock(void)
+{
+}
+
+void __rcu_read_unlock(void)
+{
+}
+
+/* kernel/resource.c */
+int insert_resource(struct resource *parent, struct resource *new)
+{
+	return 0;
+}
+
+int remove_resource(struct resource *old)
+{
+	return 0;
+}
+
+struct resource *alloc_free_mem_region(struct resource *base, unsigned long size,
+				       unsigned long align, const char *name)
+{
+	return NULL;
+}
+
+struct resource *__request_region(struct resource *parent, resource_size_t start, resource_size_t n,
+				  const char *name, int flags)
+{
+	return NULL;
+}
+
+void __release_region(struct resource *parent, resource_size_t start, resource_size_t n)
+{
+}
+
+int walk_iomem_res_desc(unsigned long desc, unsigned long flags, u64 start, u64 end, void *arg,
+			int (*func)(struct resource *, void *))
+{
+	return 0;
+}
+
+/* kernel/sched/core.c */
+int wake_up_process(struct task_struct *p)
+{
+	return 0;
+}
+
+void schedule(void)
+{
+}
+
+/* kernel/time/timer.c */
+unsigned int g_msecs;
+void msleep(unsigned int msecs)
+{
+	g_msecs += msecs;
+}
+
+void usleep_range_state(unsigned long min, unsigned long max, unsigned int state)
+{
+}
+
+/* kernel/workqueue.c */
+struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active, ...)
+{
+	return NULL;
+}
+
+void drain_workqueue(struct workqueue_struct *wq)
+{
+}
+
+void __flush_workqueue(struct workqueue_struct *wq)
+{
+}
+
+void destroy_workqueue(struct workqueue_struct *wq)
+{
+}
+
+bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work)
+{
+	return false;
+}
+
+/* lib/bitmap.c */
+void __bitmap_clear(unsigned long *map, unsigned int start, int len)
+{
+}
+
+/* lib/ctype.c */
+const unsigned char _ctype[1];
+
+/* lib/dump_stack.c */
+asmlinkage void dump_stack_lvl(const char *log_lvl)
+{
+}
+
+/* lib/idr.c */
+int ida_alloc_range(struct ida *ida, unsigned int min, unsigned int max, gfp_t gfp)
+{
+	return 0;
+}
+
+void ida_free(struct ida *ida, unsigned int id)
+{
+}
+
+/* lib/kobject_uevent.c */
+int kobject_uevent(struct kobject *kobj, enum kobject_action)
+{
+	return 0;
+}
+
+int kobject_uevent_env(struct kobject *kobj, enum kobject_action, char *envp[])
+{
+	return 0;
+}
+
+int kobject_synth_uevent(struct kobject *kobj, const char *buf, size_t count)
+{
+	return 0;
+}
+
+int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
+{
+	return 0;
+}
+
+/* lib/logic_iomem.c */
+#ifndef CONFIG_GENERIC_IOREMAP
+void *ioremap(phys_addr_t offset, size_t size)
+{
+	return NULL;
+}
+#endif
+
+void iounmap(volatile void *addr)
+{
+}
+
+/* lib/memregion.c */
+int memregion_alloc(gfp_t gfp)
+{
+	return 0;
+}
+
+void memregion_free(int id)
+{
+}
+
+/* lib/string.c */
+char *strnchr(const char *s, size_t count, int c)
+{
+	return NULL;
+}
+
+/* lib/string_helpers.c */
+char *strreplace(char *str, char old, char new)
+{
+	return str;
+}
+
+/* lib/usercopy.c */
+#ifndef INLINE_COPY_FROM_USER
+unsigned long _copy_from_user(void *to, const void *from, unsigned long n)
+{
+	return n;
+}
+#endif
+
+/* mm/maccess.c */
+void __copy_overflow(int size, unsigned long count)
+{
+}
+
+/* mm/percpu.c */
+unsigned long __per_cpu_offset[NR_CPUS];
+
+/* mm/slab_common.c */
+bool g_kmalloc_fail;
+void *__kmalloc(size_t size, gfp_t flags)
+{
+	if (g_kmalloc_fail) {
+		return NULL;
+	} else if (flags & __GFP_ZERO) {
+		return calloc(1, size);
+	} else {
+		return malloc(size);
+	}
+}
+
+void kfree(const void *objp)
+{
+	free((void *)objp);
+}
+
+void kfree_const(const void *x)
+{
+	free((void *)x);
+}
+
+struct kmem_cache {
+	unsigned int obj_size;
+	void (*ctor)(void *);
+};
+
+struct kmem_cache __kmem_cache[128];
+struct kmem_cache *g_next_kmem_cache = __kmem_cache;
+
+struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, unsigned int align,
+				     slab_flags_t flags, void (*ctor)(void *))
+{
+	struct kmem_cache *s = g_next_kmem_cache++;
+
+	s->obj_size = size;
+	s->ctor = ctor;
+	return s;
+}
+
+void *kmem_cache_alloc(struct kmem_cache *s, gfp_t flags)
+{
+	void *p;
+
+	if (flags & __GFP_ZERO) {
+		p = calloc(1, s->obj_size);
+	} else {
+		p = malloc(s->obj_size);
+	}
+	s->ctor(p);
+	return p;
+}
+
+void *kmem_cache_alloc_lru(struct kmem_cache *s, struct list_lru *lru, gfp_t flags)
+{
+	return kmem_cache_alloc(s, flags);
+}
+
+void kmem_cache_free(struct kmem_cache *s, void *objp)
+{
+	free(objp);
+}
+
+struct kmem_cache *kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];
+
+void *kmalloc_trace(struct kmem_cache *s, gfp_t flags, size_t size)
+{
+	return NULL;
+}
+
+/* mm/usercopy.c */
+void __check_object_size(const void *ptr, unsigned long n, bool to_user)
+{
+}
+
+/* mm/util.c */
+char *kstrdup(const char *s, gfp_t gfp)
+{
+	return strdup(s);
+}
+
+const char *kstrdup_const(const char *s, gfp_t gfp)
+{
+	return strdup(s);
+}
+
+char *kstrndup(const char *s, size_t max, gfp_t gfp)
+{
+	return strndup(s, max);
+}
+
+/* Initializer function - runs before main() */
+static void __attribute__((constructor)) __stub_init(void)
+{
+	radix_tree_init();
+}
diff --git a/tools/testing/cxl/uunit/test/core_hdm_ut.c b/tools/testing/cxl/uunit/test/core_hdm_ut.c
new file mode 100644
index 000000000000..b375e469671f
--- /dev/null
+++ b/tools/testing/cxl/uunit/test/core_hdm_ut.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pre.h"
+#include "drivers/cxl/core/hdm.c"
+#include "post.h"
+#include "uunit.h"
+#define UUNIT_CORE_HDM_C
+#include "../stub.c"
+
+#include <CUnit/Basic.h>
+#include <stdlib.h>
+
+static void
+add_hdm_decoder_test(void)
+{
+	struct cxl_port port;
+	struct cxl_decoder decoder;
+	int targets[8];
+
+	CU_ASSERT(add_hdm_decoder(&port, &decoder, targets) == 0);
+}
+
+static void
+cxl_settle_decoders_test(void)
+{
+	struct cxl_hdm hdm;
+	char _regs[0x200];
+	void *regs = _regs;
+	unsigned int msecs;
+
+	prep_register(CXL_HDM_DECODER0_CTRL_COMMITTED, regs + CXL_HDM_DECODER0_CTRL_OFFSET(0));
+	prep_register(CXL_HDM_DECODER0_CTRL_COMMITTED, regs + CXL_HDM_DECODER0_CTRL_OFFSET(1));
+	prep_register(0, regs + CXL_HDM_DECODER0_CTRL_OFFSET(2));
+
+	hdm.regs.hdm_decoder = regs;
+
+	/*
+	 * With just one decoder, we should not see a delay, since all decoders report they
+	 * are committed.
+	 */
+	hdm.decoder_count = 1;
+	msecs = g_msecs;
+	cxl_settle_decoders(&hdm);
+	CU_ASSERT(g_msecs == msecs);
+
+	/*
+	 * With two decoders, we should not see a delay, since all decoders report they
+	 * are committed.
+	 */
+	hdm.decoder_count = 2;
+	msecs = g_msecs;
+	cxl_settle_decoders(&hdm);
+	CU_ASSERT(g_msecs == msecs);
+
+	/*
+	 * With three decoders, we should see a delay, since not all decoders report they
+	 * are committed. Check that the delay is at least as big as the spec defined
+	 * 10ms commit timeout (CXL 2.0 8.2.5.12.20)..
+	 */
+	hdm.decoder_count = 3;
+	msecs = g_msecs;
+	cxl_settle_decoders(&hdm);
+	CU_ASSERT(g_msecs >= msecs + 10);
+}
+
+int
+main(int argc, char **argv)
+{
+	CU_pSuite suite = NULL;
+	unsigned int num_failures;
+
+	CU_set_error_action(CUEA_ABORT);
+	CU_initialize_registry();
+
+	suite = CU_add_suite("app_suite", NULL, NULL);
+	CU_ADD_TEST(suite, add_hdm_decoder_test);
+	CU_ADD_TEST(suite, cxl_settle_decoders_test);
+
+	CU_basic_set_mode(CU_BRM_VERBOSE);
+	CU_basic_run_tests();
+	num_failures = CU_get_number_of_failures();
+	CU_cleanup_registry();
+
+	return num_failures;
+}
diff --git a/tools/testing/cxl/uunit/test/core_port_ut.c b/tools/testing/cxl/uunit/test/core_port_ut.c
new file mode 100644
index 000000000000..a40f4675d0ee
--- /dev/null
+++ b/tools/testing/cxl/uunit/test/core_port_ut.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pre.h"
+#include "drivers/cxl/core/port.c"
+#include "post.h"
+#define UUNIT_CORE_PORT_C
+#include "../stub.c"
+
+#include <CUnit/Basic.h>
+#include <stdlib.h>
+
+/*
+ * Regression test:
+ * d6488fee664 (cxl/port: Fix decoder initialization when nr_targets > interleave_ways)
+ */
+static void
+decoder_populate_targets_test(void)
+{
+	struct cxl_switch_decoder *switch_decoder;
+	struct device dport_dev[2];
+	struct cxl_dport dport[2];
+	struct cxl_port port;
+	int nr_targets = 4;
+	int interleave_ways = 2;
+	int *target_map;
+	int i, rc;
+
+	switch_decoder = calloc(1, struct_size(switch_decoder, target, interleave_ways));
+	CU_ASSERT(switch_decoder != NULL);
+	switch_decoder->nr_targets = nr_targets;
+	switch_decoder->cxld.interleave_ways = interleave_ways;
+
+	xa_init(&port.dports);
+	for (i = 0; i < interleave_ways; i++) {
+		dport[i].dport_dev = &dport_dev[i];
+		dport[i].port_id = i;
+		CU_ASSERT(xa_insert(&port.dports, (unsigned long)dport[i].dport_dev,
+				    &dport[i], GFP_KERNEL) == 0);
+	}
+
+	target_map = calloc(1, sizeof(int) * interleave_ways);
+	CU_ASSERT(target_map != NULL);
+
+	target_map[0] = 1;
+	target_map[1] = 0;
+	/*
+	 * Before d6488fee664, this call would walk past the end of the target_map
+	 * because it would use the decoder's nr_targets instead of its
+	 * interleave_ways.
+	 */
+	rc = decoder_populate_targets(switch_decoder, &port, target_map);
+	CU_ASSERT(rc == 0);
+	CU_ASSERT(switch_decoder->target[0] == &dport[1]);
+	CU_ASSERT(switch_decoder->target[1] == &dport[0]);
+
+	xa_destroy(&port.dports);
+	free(switch_decoder);
+	free(target_map);
+}
+
+static void
+cxl_port_alloc_test(void)
+{
+	struct cxl_dport parent_dport;
+	struct device uport_dev;
+	struct cxl_port *port;
+
+	g_kmalloc_fail = true;
+	port = cxl_port_alloc(&uport_dev, &parent_dport);
+	g_kmalloc_fail = false;
+	CU_ASSERT(IS_ERR(port));
+}
+
+int
+main(int argc, char **argv)
+{
+	CU_pSuite suite = NULL;
+	unsigned int num_failures;
+
+	CU_set_error_action(CUEA_ABORT);
+	CU_initialize_registry();
+
+	suite = CU_add_suite("app_suite", NULL, NULL);
+	CU_ADD_TEST(suite, decoder_populate_targets_test);
+	CU_ADD_TEST(suite, cxl_port_alloc_test);
+
+	CU_basic_set_mode(CU_BRM_VERBOSE);
+	CU_basic_run_tests();
+	num_failures = CU_get_number_of_failures();
+	CU_cleanup_registry();
+
+	return num_failures;
+}
diff --git a/tools/testing/cxl/uunit/test/core_region_ut.c b/tools/testing/cxl/uunit/test/core_region_ut.c
new file mode 100644
index 000000000000..668af95f5d03
--- /dev/null
+++ b/tools/testing/cxl/uunit/test/core_region_ut.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "pre.h"
+#include "drivers/cxl/core/region.c"
+#include "post.h"
+#define UUNIT_CORE_REGION_C
+#include "../stub.c"
+
+#include <CUnit/Basic.h>
+#include <stdlib.h>
+
+static void
+uuid_show_test(void)
+{
+	struct cxl_region region = { .dev.type = &cxl_region_type };
+	char buf[128];
+
+	region.mode = CXL_DECODER_RAM;
+	CU_ASSERT(uuid_show(&region.dev, NULL, buf) == 1);
+}
+
+static void
+is_cxl_pmem_region_test(void)
+{
+	struct device dev;
+
+	dev.type = &cxl_pmem_region_type;
+	CU_ASSERT(is_cxl_pmem_region(&dev) == true);
+
+	dev.type = &cxl_dax_region_type;
+	CU_ASSERT(is_cxl_pmem_region(&dev) == false);
+}
+
+const struct device_type dummy_region_type = {
+	.name = "dummy"
+};
+
+static void
+is_dup_test(void)
+{
+	struct device dummy = { .type = &dummy_region_type };
+	struct cxl_region region = { .dev.type = &cxl_region_type };
+	uuid_t uuid = { 0 };
+
+	/* non-region devices should always return 0 */
+	CU_ASSERT(is_dup(&dummy, NULL) == 0);
+
+	/*
+	 * uuid matches, indicates the specified uuid duplicates
+	 * the uuid for an existing region
+	 * return -EBUSY
+	 */
+	CU_ASSERT(is_dup(&region.dev, &uuid) == -EBUSY);
+
+	/*
+	 * uuid does not match
+	 */
+	uuid.b[0] = 1;
+	CU_ASSERT(is_dup(&region.dev, &uuid) == 0);
+}
+
+static void
+interleave_ways_store_test(void)
+{
+	struct cxl_region *region = calloc(1, sizeof(*region));
+	const char *str0 = "0";
+	const char *str1 = "1";
+	const char *str16 = "16";
+	char buf[32];
+	struct cxl_root_decoder *root_decoder = calloc(1, sizeof(*root_decoder));
+
+	region->dev.type = &cxl_region_type;
+	region->dev.parent = &root_decoder->cxlsd.cxld.dev;
+	root_decoder->cxlsd.cxld.interleave_ways = 1;
+	region->params.interleave_ways = 0xFF;
+
+	CU_ASSERT(interleave_ways_store(&region->dev, NULL, str0, strlen(str0)) < 0);
+	CU_ASSERT(region->params.interleave_ways == 0xFF);
+
+	CU_ASSERT(interleave_ways_store(&region->dev, NULL, str1, strlen(str1)) == strlen(str1));
+	CU_ASSERT(region->params.interleave_ways == 1);
+	/* interleave_ways_show appends a newline to the value string */
+	CU_ASSERT(interleave_ways_show(&region->dev, NULL, buf) == strlen(str1) + 1);
+	CU_ASSERT(strncmp(buf, str1, strlen(str1)) == 0);
+
+	region->params.interleave_ways = 0xFF;
+	CU_ASSERT(interleave_ways_store(&region->dev, NULL, str16, strlen(str16)) == strlen(str16));
+	CU_ASSERT(region->params.interleave_ways == 16);
+	/* interleave_ways_show appends a newline to the value string */
+	CU_ASSERT(interleave_ways_show(&region->dev, NULL, buf) == strlen(str16) + 1);
+	CU_ASSERT(strncmp(buf, str16, strlen(str16)) == 0);
+
+	free(root_decoder);
+	free(region);
+}
+
+static struct cxl_region *
+alloc_cxl_region(struct cxl_root_decoder *root_decoder, int nr_targets,
+		 struct cxl_endpoint_decoder **targets)
+{
+	struct cxl_region *region;
+	struct resource *resource;
+	int i;
+
+	region = calloc(1, sizeof(*region));
+	CU_ASSERT(region != NULL);
+
+	resource = calloc(1, sizeof(*resource));
+	resource->start = 0x1000000000;
+	resource->end = 0x1FFFFFFFFF;
+	region->params.res = resource;
+
+	region->dev.parent = &root_decoder->cxlsd.cxld.dev;
+	region->params.interleave_granularity = 256;
+	region->params.nr_targets = nr_targets;
+	for (i = 0; i < nr_targets; i++) {
+		region->params.targets[i] = targets[i];
+	}
+
+	return region;
+}
+
+static void
+free_cxl_region(struct cxl_region *region)
+{
+	free(region->params.res);
+	free(region);
+}
+
+static struct cxl_endpoint_decoder **
+alloc_cxl_endpoint_decoders(int nr_decoders, struct cxl_port *parent_port)
+{
+	struct cxl_endpoint_decoder **decoders;
+	struct cxl_endpoint_decoder *decoder;
+	struct cxl_dev_state *dev_state;
+	struct cxl_memdev *memdev;
+	struct cxl_port *port;
+	int i;
+
+	decoders = calloc(1, nr_decoders * sizeof(*decoders));
+	CU_ASSERT(decoders != NULL);
+
+	for (i = 0; i < nr_decoders; i++) {
+		dev_state = calloc(1, sizeof(*dev_state));
+		dev_state->rcd = 0;
+
+		memdev = calloc(1, sizeof(*memdev));
+		memdev->cxlds = dev_state;
+
+		port = calloc(1, sizeof(*port));
+		port->uport_dev = &memdev->dev;
+		port->dev.parent = &parent_port->dev;
+
+		decoder = calloc(1, sizeof(*decoder));
+		decoder->pos = i;
+		decoder->cxld.dev.parent = &port->dev;
+		decoders[i] = decoder;
+	}
+
+	return decoders;
+}
+
+static void
+free_cxl_endpoint_decoders(int nr_decoders, struct cxl_endpoint_decoder **decoders)
+{
+	struct cxl_memdev *memdev;
+	int i;
+
+	for (i = 0; i < nr_decoders; i++) {
+		memdev = cxled_to_memdev(decoders[i]);
+		free(memdev->cxlds);
+		free(memdev);
+		free(cxled_to_port(decoders[i]));
+		free(decoders[i]);
+	}
+
+	free(decoders);
+}
+
+static struct cxl_port *
+alloc_cxl_port(struct cxl_port *parent_port)
+{
+	struct cxl_port *port;
+
+	port = calloc(1, sizeof(*port));
+	port->dev.parent = &parent_port->dev;
+	xa_init(&port->regions);
+	xa_init(&port->endpoints);
+
+	return port;
+}
+
+static void
+free_cxl_port(struct cxl_port *port)
+{
+	xa_destroy(&port->regions);
+	xa_destroy(&port->endpoints);
+	free(port);
+}
+
+static struct cxl_region_ref *
+alloc_cxl_region_ref(struct cxl_region *region, struct cxl_port *port,
+		     struct cxl_decoder *decoder, int nr_targets)
+{
+	struct cxl_region_ref *region_ref;
+
+	region_ref = calloc(1, sizeof(*region_ref));
+	region_ref->nr_targets = nr_targets;
+	region_ref->region = region;
+	region_ref->port = port;
+	region_ref->decoder = decoder;
+	CU_ASSERT(xa_insert(&port->regions, (unsigned long)region, region_ref, GFP_KERNEL) == 0);
+
+	return region_ref;
+}
+
+static void
+free_cxl_region_ref(struct cxl_region_ref *region_ref)
+{
+	xa_erase(&region_ref->port->regions, (unsigned long)region_ref->region);
+	free(region_ref);
+}
+
+static void
+cxl_port_setup_targets_test(void)
+{
+	struct cxl_port			*port;
+	struct cxl_region		*region;
+	struct cxl_endpoint_decoder	**ep_decoders;
+	struct cxl_region_ref		*region_ref;
+	struct cxl_port			parent_port;
+	struct cxl_switch_decoder	*switch_decoder;
+	struct cxl_root_decoder		root_decoder;
+	struct cxl_ep			ep, ep2;
+	struct cxl_dport		dport;
+	struct cxl_memdev		*memdev;
+
+	root_decoder.cxlsd.cxld.interleave_ways = 2;
+
+	switch_decoder = calloc(1, sizeof(*switch_decoder) + 2 * sizeof(struct cxl_dport *));
+	switch_decoder->nr_targets = 2;
+
+	port = alloc_cxl_port(&parent_port);
+
+	ep_decoders = alloc_cxl_endpoint_decoders(2, port);
+
+	ep.dport = &dport;
+	ep2.dport = &dport;
+
+	region = alloc_cxl_region(&root_decoder, 2, ep_decoders);
+
+	region_ref = alloc_cxl_region_ref(region, port, &switch_decoder->cxld, 2);
+
+	memdev = cxled_to_memdev(ep_decoders[0]);
+	CU_ASSERT(xa_insert(&port->endpoints, (unsigned long)memdev, &ep, GFP_KERNEL) == 0);
+	CU_ASSERT(cxl_port_setup_targets(port, region, ep_decoders[0]) == 0);
+	CU_ASSERT(region_ref->nr_targets_set == 1);
+	CU_ASSERT(switch_decoder->target[0] == &dport);
+
+	memdev = cxled_to_memdev(ep_decoders[1]);
+	CU_ASSERT(xa_insert(&port->endpoints, (unsigned long)memdev, &ep2, GFP_KERNEL) == 0);
+	CU_ASSERT(cxl_port_setup_targets(port, region, ep_decoders[1]) == 0);
+	CU_ASSERT(region_ref->nr_targets_set == 1);
+	CU_ASSERT(switch_decoder->target[0] == &dport);
+	CU_ASSERT(switch_decoder->target[1] == NULL);
+
+	free(switch_decoder);
+	free_cxl_region_ref(region_ref);
+	free_cxl_port(port);
+	free_cxl_region(region);
+	free_cxl_endpoint_decoders(2, ep_decoders);
+}
+
+static void
+cxl_region_setup_targets_test(void)
+{
+	struct cxl_port			*port;
+	struct cxl_region		*region;
+	struct cxl_endpoint_decoder	**ep_decoders;
+	struct cxl_region_ref		*region_ref;
+	struct cxl_port			parent_port;
+	struct cxl_switch_decoder	*switch_decoder;
+	struct cxl_root_decoder		root_decoder;
+	struct cxl_memdev		*memdev;
+	struct cxl_ep			ep, ep2;
+	struct cxl_dport		dport;
+
+	root_decoder.cxlsd.cxld.interleave_ways = 2;
+
+	switch_decoder = calloc(1, sizeof(*switch_decoder) + 2 * sizeof(struct cxl_dport *));
+	switch_decoder->nr_targets = 2;
+
+	port = alloc_cxl_port(&parent_port);
+
+	ep_decoders = alloc_cxl_endpoint_decoders(2, port);
+
+	region = alloc_cxl_region(&root_decoder, 2, ep_decoders);
+
+	region_ref = alloc_cxl_region_ref(region, port, &switch_decoder->cxld, 2);
+
+	ep.dport = &dport;
+	ep2.dport = &dport;
+
+	memdev = cxled_to_memdev(ep_decoders[0]);
+	CU_ASSERT(xa_insert(&port->endpoints, (unsigned long)memdev, &ep, GFP_KERNEL) == 0);
+	memdev = cxled_to_memdev(ep_decoders[1]);
+	CU_ASSERT(xa_insert(&port->endpoints, (unsigned long)memdev, &ep2, GFP_KERNEL) == 0);
+
+	CU_ASSERT(cxl_region_setup_targets(region) == 0);
+
+	CU_ASSERT(region_ref->nr_targets_set == 1);
+	CU_ASSERT(switch_decoder->target[0] == &dport);
+	CU_ASSERT(switch_decoder->target[1] == NULL);
+
+	free(switch_decoder);
+	free_cxl_region_ref(region_ref);
+	free_cxl_port(port);
+	free_cxl_region(region);
+	free_cxl_endpoint_decoders(2, ep_decoders);
+}
+
+int
+main(int argc, char **argv)
+{
+	CU_pSuite suite = NULL;
+	unsigned int num_failures;
+
+	CU_set_error_action(CUEA_ABORT);
+	CU_initialize_registry();
+
+	suite = CU_add_suite("app_suite", NULL, NULL);
+	CU_ADD_TEST(suite, uuid_show_test);
+	CU_ADD_TEST(suite, is_dup_test);
+	CU_ADD_TEST(suite, is_cxl_pmem_region_test);
+	CU_ADD_TEST(suite, interleave_ways_store_test);
+	CU_ADD_TEST(suite, cxl_port_setup_targets_test);
+	CU_ADD_TEST(suite, cxl_region_setup_targets_test);
+
+	CU_basic_set_mode(CU_BRM_VERBOSE);
+	CU_basic_run_tests();
+	num_failures = CU_get_number_of_failures();
+	CU_cleanup_registry();
+
+	return num_failures;
+}
diff --git a/tools/testing/cxl/uunit/uunit.h b/tools/testing/cxl/uunit/uunit.h
new file mode 100644
index 000000000000..2a7a748c0d63
--- /dev/null
+++ b/tools/testing/cxl/uunit/uunit.h
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
+static inline void
+prep_register(u32 value, void *addr)
+{
+	*(u32 *)addr = value;
+}


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2024-02-15  0:26 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <CGME20240215002612uscas1p2990a3c0029e653c10fd905a96800b6ba@uscas1p2.samsung.com>
2024-02-15  0:26 ` [PATCH RFC v2] Add "uunit" unit testing framework for CXL code Jim Harris

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.