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;
+}
