Merge branch '2023-10-17-spl-test-some-load-methods'

To quote the author:
This series adds some tests for various SPL load methods, with the
intent of helping debug v6 of [1]. With that in mind, notable omissions
include NAND and ROMAPI, which both lack sandbox implementations, and
OS_BOOT, which I have deferred due to its complexity. Semihosting is
also omitted, but I think we can test that with qemu.

In order to test all of these methods, we must first generate suitable
images, possibly on filesystems. While other tests have historically
generated these images using external tools (e.g. mkimage, mkfs, etc.),
I have chosen to generate them on the fly. This is for a few reasons:

- By removing external dependencies on pytest to create certain files,
  the tests become self-contained. This makes them easier to iterate on
  and debug.
- By generating tests at runtime, we can dynamically vary the content.
  This helps detect test failures, as even if tests are loaded to the
  same location, the expected content will be different.
- We are not testing the image parsers themselves (e.g.
  spl_load_simple_fit or fs_read) but rather the load methods (e.g.
  spl_mmc_load_image). It is unnecessary to exercise full functionality
  or generate 100% correct images.
- By reducing functionality to only what is necessary, the complexity of
  various formats can often be greatly reduced.

This series depends on [2-3], which are small fixes identified through
this patch set. The organization of patches in this series is as
follows:

- General fixes for bugs which are unlikely to be triggered outside of
  this series
- Changes to IMX8 container images to facilitate testing
- General prep. work, particularly regarding linker issues
- The tests themselves

Passing CI at [4].

[1] https://lore.kernel.org/all/20230731224304.111081-1-sean.anderson@seco.com/
[2] https://lore.kernel.org/all/20230930204246.515254-1-seanga2@gmail.com/
[3] https://lore.kernel.org/all/20231008014748.1987840-1-seanga2@gmail.com/
[4] https://source.denx.de/u-boot/custodians/u-boot-clk/-/pipelines/18128
diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml
index 7985ff5..6f91553 100644
--- a/.azure-pipelines.yml
+++ b/.azure-pipelines.yml
@@ -299,6 +299,10 @@
         sandbox_noinst:
           TEST_PY_BD: "sandbox_noinst"
           TEST_PY_TEST_SPEC: "test_ofplatdata or test_handoff or test_spl"
+        sandbox_noinst_load_fit_full:
+          TEST_PY_BD: "sandbox_noinst"
+          TEST_PY_TEST_SPEC: "test_ofplatdata or test_handoff or test_spl"
+          OVERRIDE: "-a CONFIG_SPL_LOAD_FIT_FULL=y"
         sandbox_flattree:
           TEST_PY_BD: "sandbox_flattree"
         sandbox_trace:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 129234b..6decdfd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -293,6 +293,13 @@
     TEST_PY_TEST_SPEC: "test_ofplatdata or test_handoff or test_spl"
   <<: *buildman_and_testpy_dfn
 
+sandbox_noinst with LOAD_FIT_FULL test.py:
+  variables:
+    TEST_PY_BD: "sandbox_noinst"
+    TEST_PY_TEST_SPEC: "test_ofplatdata or test_handoff or test_spl"
+    OVERRIDE: "-a CONFIG_SPL_LOAD_FIT_FULL=y"
+  <<: *buildman_and_testpy_dfn
+
 sandbox_vpl test.py:
   variables:
     TEST_PY_BD: "sandbox_vpl"
diff --git a/MAINTAINERS b/MAINTAINERS
index 16b17fd..cde778b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -300,7 +300,9 @@
 F:	arch/arm/include/asm/mach-imx/
 F:	board/freescale/*mx*/
 F:	board/freescale/common/
+F:	common/spl/spl_imx_container.c
 F:	drivers/serial/serial_mxc.c
+F:	include/imx_container.h
 
 ARM HISILICON
 M:	Peter Griffin <peter.griffin@linaro.org>
diff --git a/arch/arm/include/asm/mach-imx/ahab.h b/arch/arm/include/asm/mach-imx/ahab.h
index 4222e3d..4884f05 100644
--- a/arch/arm/include/asm/mach-imx/ahab.h
+++ b/arch/arm/include/asm/mach-imx/ahab.h
@@ -6,7 +6,7 @@
 #ifndef __IMX_AHAB_H__
 #define __IMX_AHAB_H__
 
-#include <asm/mach-imx/image.h>
+#include <imx_container.h>
 
 int ahab_auth_cntr_hdr(struct container_hdr *container, u16 length);
 int ahab_auth_release(void);
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig
index 266bb20..08ab706 100644
--- a/arch/arm/mach-imx/Kconfig
+++ b/arch/arm/mach-imx/Kconfig
@@ -194,19 +194,6 @@
 	  This information is shared with the user via mkimage -l just so the
 	  image can be signed.
 
-config SPL_LOAD_IMX_CONTAINER
-	bool "Enable SPL loading U-Boot as a i.MX Container image"
-	depends on SPL
-	help
-	  This is to let SPL could load i.MX Container image
-
-config IMX_CONTAINER_CFG
-	string "i.MX Container config file"
-	depends on SPL
-	help
-	  This is to specific the cfg file for generating container
-	  image which will be loaded by SPL.
-
 config IOMUX_LPSR
 	bool
 
diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
index 6904cf3..a3b44c9 100644
--- a/arch/arm/mach-imx/Makefile
+++ b/arch/arm/mach-imx/Makefile
@@ -79,7 +79,7 @@
 endif
 
 ifeq ($(CONFIG_SPL_BUILD),y)
-obj-$(CONFIG_SPL_LOAD_IMX_CONTAINER) += image-container.o parse-container.o
+obj-$(CONFIG_SPL_LOAD_IMX_CONTAINER) += image-container.o
 endif
 
 ifeq ($(SOC),$(filter $(SOC),imx8ulp imx9))
diff --git a/arch/arm/mach-imx/cmd_dek.c b/arch/arm/mach-imx/cmd_dek.c
index 6fa5b41..2f389db 100644
--- a/arch/arm/mach-imx/cmd_dek.c
+++ b/arch/arm/mach-imx/cmd_dek.c
@@ -18,12 +18,12 @@
 #include <mapmem.h>
 #include <tee.h>
 #ifdef CONFIG_IMX_SECO_DEK_ENCAP
+#include <imx_container.h>
 #include <firmware/imx/sci/sci.h>
-#include <asm/mach-imx/image.h>
 #endif
 #ifdef CONFIG_IMX_ELE_DEK_ENCAP
+#include <imx_container.h>
 #include <asm/mach-imx/ele_api.h>
-#include <asm/mach-imx/image.h>
 #endif
 
 #include <cpu_func.h>
diff --git a/arch/arm/mach-imx/ele_ahab.c b/arch/arm/mach-imx/ele_ahab.c
index 785b0d6..295c055 100644
--- a/arch/arm/mach-imx/ele_ahab.c
+++ b/arch/arm/mach-imx/ele_ahab.c
@@ -6,12 +6,12 @@
 #include <common.h>
 #include <command.h>
 #include <errno.h>
+#include <imx_container.h>
 #include <asm/io.h>
 #include <asm/mach-imx/ele_api.h>
 #include <asm/mach-imx/sys_proto.h>
 #include <asm/arch-imx/cpu.h>
 #include <asm/arch/sys_proto.h>
-#include <asm/mach-imx/image.h>
 #include <console.h>
 #include <cpu_func.h>
 #include <asm/global_data.h>
@@ -343,7 +343,7 @@
 	}
 
 	phdr = (struct container_hdr *)addr;
-	if (phdr->tag != 0x87 || phdr->version != 0x0) {
+	if (!valid_container_hdr(phdr)) {
 		printf("Error: Wrong container header\n");
 		return -EFAULT;
 	}
diff --git a/arch/arm/mach-imx/image-container.c b/arch/arm/mach-imx/image-container.c
index 5f188ab..ebc8021 100644
--- a/arch/arm/mach-imx/image-container.c
+++ b/arch/arm/mach-imx/image-container.c
@@ -5,6 +5,7 @@
 
 #include <common.h>
 #include <errno.h>
+#include <imx_container.h>
 #include <log.h>
 #include <malloc.h>
 #include <asm/io.h>
@@ -12,7 +13,6 @@
 #include <spi_flash.h>
 #include <spl.h>
 #include <nand.h>
-#include <asm/mach-imx/image.h>
 #include <asm/arch/sys_proto.h>
 #include <asm/mach-imx/boot_mode.h>
 
@@ -50,7 +50,7 @@
 	u32 max_offset = 0, img_end;
 
 	phdr = (struct container_hdr *)addr;
-	if (phdr->tag != 0x87 || phdr->version != 0x0) {
+	if (!valid_container_hdr(phdr)) {
 		debug("Wrong container header\n");
 		return -EFAULT;
 	}
diff --git a/arch/arm/mach-imx/imx8/ahab.c b/arch/arm/mach-imx/imx8/ahab.c
index b58b14c..994becc 100644
--- a/arch/arm/mach-imx/imx8/ahab.c
+++ b/arch/arm/mach-imx/imx8/ahab.c
@@ -6,6 +6,7 @@
 #include <common.h>
 #include <command.h>
 #include <errno.h>
+#include <imx_container.h>
 #include <log.h>
 #include <asm/global_data.h>
 #include <asm/io.h>
@@ -13,7 +14,6 @@
 #include <asm/mach-imx/sys_proto.h>
 #include <asm/arch-imx/cpu.h>
 #include <asm/arch/sys_proto.h>
-#include <asm/mach-imx/image.h>
 #include <console.h>
 #include <cpu_func.h>
 #include "u-boot/sha256.h"
@@ -146,7 +146,7 @@
 	}
 
 	phdr = (struct container_hdr *)addr;
-	if (phdr->tag != 0x87 && phdr->version != 0x0) {
+	if (!valid_container_hdr(phdr)) {
 		printf("Error: Wrong container header\n");
 		return -EFAULT;
 	}
diff --git a/arch/arm/mach-imx/spl_imx_romapi.c b/arch/arm/mach-imx/spl_imx_romapi.c
index c4a4185..93d48e5 100644
--- a/arch/arm/mach-imx/spl_imx_romapi.c
+++ b/arch/arm/mach-imx/spl_imx_romapi.c
@@ -6,11 +6,11 @@
 #include <common.h>
 #include <errno.h>
 #include <image.h>
+#include <imx_container.h>
 #include <log.h>
 #include <asm/global_data.h>
 #include <linux/libfdt.h>
 #include <spl.h>
-#include <asm/mach-imx/image.h>
 #include <asm/arch/sys_proto.h>
 
 DECLARE_GLOBAL_DATA_PTR;
@@ -111,7 +111,8 @@
 		load.read = spl_romapi_read_seekable;
 		load.priv = &pagesize;
 		return spl_load_simple_fit(spl_image, &load, offset / pagesize, header);
-	} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
+	} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER) &&
+		   valid_container_hdr((void *)header)) {
 		struct spl_load_info load;
 
 		memset(&load, 0, sizeof(load));
@@ -202,7 +203,8 @@
 
 	for (i = 0; i < size; i += 4) {
 		hdr = p + i;
-		if (*(hdr + 3) == 0x87 && *hdr == 0 && (*(hdr + 1) != 0 || *(hdr + 2) != 0))
+		if (valid_container_hdr((void *)hdr) &&
+		    (*(hdr + 1) != 0 || *(hdr + 2) != 0))
 			return p + i;
 	}
 
diff --git a/arch/sandbox/cpu/spl.c b/arch/sandbox/cpu/spl.c
index 7590f1b..16b7662 100644
--- a/arch/sandbox/cpu/spl.c
+++ b/arch/sandbox/cpu/spl.c
@@ -129,6 +129,10 @@
 	if (!CONFIG_IS_ENABLED(UNIT_TEST))
 		return;
 
+	/* These are necessary so TFTP can use LMBs to check its load address */
+	gd->bd->bi_dram[0].start = gd->ram_base;
+	gd->bd->bi_dram[0].size = get_effective_memsize();
+
 	if (state->run_unittests) {
 		struct unit_test *tests = UNIT_TEST_ALL_START();
 		const int count = UNIT_TEST_ALL_COUNT();
diff --git a/arch/sandbox/cpu/start.c b/arch/sandbox/cpu/start.c
index 2c8a725..2589c2e 100644
--- a/arch/sandbox/cpu/start.c
+++ b/arch/sandbox/cpu/start.c
@@ -13,6 +13,7 @@
 #include <log.h>
 #include <os.h>
 #include <sort.h>
+#include <spl.h>
 #include <asm/getopt.h>
 #include <asm/global_data.h>
 #include <asm/io.h>
@@ -202,10 +203,14 @@
 {
 	char buf[256];
 	char *fname;
+	char *relname;
 	int len;
 
-	len = state_get_rel_filename("arch/sandbox/dts/test.dtb", buf,
-				     sizeof(buf));
+	if (spl_phase() <= PHASE_SPL)
+		relname = "../arch/sandbox/dts/test.dtb";
+	else
+		relname = "arch/sandbox/dts/test.dtb";
+	len = state_get_rel_filename(relname, buf, sizeof(buf));
 	if (len < 0)
 		return len;
 
diff --git a/arch/sandbox/cpu/u-boot-spl.lds b/arch/sandbox/cpu/u-boot-spl.lds
index ef885fd..a81d66a 100644
--- a/arch/sandbox/cpu/u-boot-spl.lds
+++ b/arch/sandbox/cpu/u-boot-spl.lds
@@ -26,6 +26,8 @@
 		KEEP(*(_u_boot_sandbox_getopt))
 		*(_u_boot_sandbox_getopt_end)
 	}
+
+	_image_binary_end = .;
 }
 
 INSERT AFTER .data;
diff --git a/arch/sandbox/include/asm/spl.h b/arch/sandbox/include/asm/spl.h
index 2f8b5fc..f349ea1 100644
--- a/arch/sandbox/include/asm/spl.h
+++ b/arch/sandbox/include/asm/spl.h
@@ -12,6 +12,9 @@
 	BOOT_DEVICE_MMC2_2,
 	BOOT_DEVICE_BOARD,
 	BOOT_DEVICE_VBE,
+	BOOT_DEVICE_CPGMAC,
+	BOOT_DEVICE_NOR,
+	BOOT_DEVICE_SPI,
 };
 
 /**
diff --git a/common/spl/Kconfig b/common/spl/Kconfig
index 4632359..6bc4066 100644
--- a/common/spl/Kconfig
+++ b/common/spl/Kconfig
@@ -330,6 +330,20 @@
 	  If disabled, Legacy images are booted if the image magic and size
 	  are correct, without further integrity checks.
 
+config SPL_LOAD_IMX_CONTAINER
+	bool "Enable SPL loading and booting of i.MX8 Containers"
+	depends on SPL
+	help
+	  Support booting U-Boot from an i.MX8 container image. If you are not
+	  using i.MX8, say 'n'.
+
+config IMX_CONTAINER_CFG
+	string "i.MX8 Container config file"
+	depends on SPL && SPL_LOAD_IMX_CONTAINER
+	help
+	  Specify the cfg file for generating the container image which will be
+	  loaded by SPL.
+
 config SPL_SYS_MALLOC_SIMPLE
 	bool "Only use malloc_simple functions in the SPL"
 	help
@@ -643,6 +657,7 @@
 
 config SPL_FS_EXT4
 	bool "Support EXT filesystems"
+	select SPL_CRC16 if EXT4_WRITE
 	help
 	  Enable support for EXT2/3/4 filesystems with SPL. This permits
 	  U-Boot (or Linux in Falcon mode) to be loaded from an EXT
diff --git a/common/spl/Makefile b/common/spl/Makefile
index bad2bbf..4f8eb2e 100644
--- a/common/spl/Makefile
+++ b/common/spl/Makefile
@@ -28,6 +28,7 @@
 obj-$(CONFIG_$(SPL_TPL_)USB_STORAGE) += spl_usb.o
 obj-$(CONFIG_$(SPL_TPL_)FS_FAT) += spl_fat.o
 obj-$(CONFIG_$(SPL_TPL_)FS_EXT4) += spl_ext.o
+obj-$(CONFIG_$(SPL_TPL_)LOAD_IMX_CONTAINER) += spl_imx_container.o
 obj-$(CONFIG_$(SPL_TPL_)SATA) += spl_sata.o
 obj-$(CONFIG_$(SPL_TPL_)NVME) += spl_nvme.o
 obj-$(CONFIG_$(SPL_TPL_)SEMIHOSTING) += spl_semihosting.o
diff --git a/common/spl/spl.c b/common/spl/spl.c
index 66eeea4..732d90d 100644
--- a/common/spl/spl.c
+++ b/common/spl/spl.c
@@ -653,7 +653,9 @@
 	spl_set_bd();
 
 	if (IS_ENABLED(CONFIG_SPL_SYS_MALLOC)) {
-		mem_malloc_init(SPL_SYS_MALLOC_START, SPL_SYS_MALLOC_SIZE);
+		mem_malloc_init((ulong)map_sysmem(SPL_SYS_MALLOC_START,
+						  SPL_SYS_MALLOC_SIZE),
+				SPL_SYS_MALLOC_SIZE);
 		gd->flags |= GD_FLG_FULL_MALLOC_INIT;
 	}
 	if (!(gd->flags & GD_FLG_SPL_INIT)) {
diff --git a/common/spl/spl_blk_fs.c b/common/spl/spl_blk_fs.c
index ea5d1a5..63825d6 100644
--- a/common/spl/spl_blk_fs.c
+++ b/common/spl/spl_blk_fs.c
@@ -9,6 +9,7 @@
 #include <spl.h>
 #include <image.h>
 #include <fs.h>
+#include <asm/io.h>
 
 struct blk_dev {
 	const char *ifname;
@@ -29,7 +30,8 @@
 		return ret;
 	}
 
-	ret = fs_read(load->filename, (ulong)buf, file_offset, size, &actlen);
+	ret = fs_read(load->filename, virt_to_phys(buf), file_offset, size,
+		      &actlen);
 	if (ret < 0) {
 		printf("spl: error reading image %s. Err - %d\n",
 		       load->filename, ret);
@@ -69,7 +71,7 @@
 		goto out;
 	}
 
-	ret = fs_read(filename, (ulong)header, 0,
+	ret = fs_read(filename, virt_to_phys(header), 0,
 		      sizeof(struct legacy_img_hdr), &actlen);
 	if (ret) {
 		printf("spl: unable to read file %s. Err - %d\n", filename,
diff --git a/common/spl/spl_ext.c b/common/spl/spl_ext.c
index 902564a..af836ca 100644
--- a/common/spl/spl_ext.c
+++ b/common/spl/spl_ext.c
@@ -2,6 +2,7 @@
 
 #include <common.h>
 #include <env.h>
+#include <mapmem.h>
 #include <part.h>
 #include <spl.h>
 #include <asm/u-boot.h>
@@ -53,7 +54,8 @@
 		goto end;
 	}
 
-	err = ext4fs_read((char *)spl_image->load_addr, 0, filelen, &actlen);
+	err = ext4fs_read(map_sysmem(spl_image->load_addr, filelen), 0, filelen,
+			  &actlen);
 
 end:
 #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
diff --git a/common/spl/spl_fat.c b/common/spl/spl_fat.c
index c6e2526..014074f 100644
--- a/common/spl/spl_fat.c
+++ b/common/spl/spl_fat.c
@@ -11,6 +11,7 @@
 #include <common.h>
 #include <env.h>
 #include <log.h>
+#include <mapmem.h>
 #include <spl.h>
 #include <asm/u-boot.h>
 #include <fat.h>
@@ -20,6 +21,11 @@
 
 static int fat_registered;
 
+void spl_fat_force_reregister(void)
+{
+	fat_registered = 0;
+}
+
 static int spl_register_fat_device(struct blk_desc *block_dev, int partition)
 {
 	int err = 0;
@@ -74,11 +80,13 @@
 
 	if (IS_ENABLED(CONFIG_SPL_LOAD_FIT_FULL) &&
 	    image_get_magic(header) == FDT_MAGIC) {
-		err = file_fat_read(filename, (void *)CONFIG_SYS_LOAD_ADDR, 0);
+		err = file_fat_read(filename,
+				    map_sysmem(CONFIG_SYS_LOAD_ADDR, 0), 0);
 		if (err <= 0)
 			goto end;
 		err = spl_parse_image_header(spl_image, bootdev,
-				(struct legacy_img_hdr *)CONFIG_SYS_LOAD_ADDR);
+					     map_sysmem(CONFIG_SYS_LOAD_ADDR,
+							err));
 		if (err == -EAGAIN)
 			return err;
 		if (err == 0)
@@ -99,8 +107,8 @@
 		if (err)
 			goto end;
 
-		err = file_fat_read(filename,
-				    (u8 *)(uintptr_t)spl_image->load_addr, 0);
+		err = file_fat_read(filename, map_sysmem(spl_image->load_addr,
+							 spl_image->size), 0);
 	}
 
 end:
diff --git a/common/spl/spl_fit.c b/common/spl/spl_fit.c
index 1409b926..c026e1a 100644
--- a/common/spl/spl_fit.c
+++ b/common/spl/spl_fit.c
@@ -16,6 +16,7 @@
 #include <sysinfo.h>
 #include <asm/cache.h>
 #include <asm/global_data.h>
+#include <asm/io.h>
 #include <linux/libfdt.h>
 #include <linux/printk.h>
 
@@ -393,25 +394,32 @@
 	/* Figure out which device tree the board wants to use */
 	node = spl_fit_get_image_node(ctx, FIT_FDT_PROP, index++);
 	if (node < 0) {
+		size_t size;
+
 		debug("%s: cannot find FDT node\n", __func__);
 
 		/*
 		 * U-Boot did not find a device tree inside the FIT image. Use
 		 * the U-Boot device tree instead.
 		 */
-		if (gd->fdt_blob)
-			memcpy((void *)image_info.load_addr, gd->fdt_blob,
-			       fdt_totalsize(gd->fdt_blob));
-		else
+		if (!gd->fdt_blob)
 			return node;
+
+		/*
+		 * Make the load-address of the FDT available for the SPL
+		 * framework
+		 */
+		size = fdt_totalsize(gd->fdt_blob);
+		spl_image->fdt_addr = map_sysmem(image_info.load_addr, size);
+		memcpy(spl_image->fdt_addr, gd->fdt_blob, size);
 	} else {
 		ret = load_simple_fit(info, sector, ctx, node, &image_info);
 		if (ret < 0)
 			return ret;
+
+		spl_image->fdt_addr = phys_to_virt(image_info.load_addr);
 	}
 
-	/* Make the load-address of the FDT available for the SPL framework */
-	spl_image->fdt_addr = map_sysmem(image_info.load_addr, 0);
 	if (CONFIG_IS_ENABLED(FIT_IMAGE_TINY))
 		return 0;
 
@@ -876,7 +884,7 @@
 #ifdef CONFIG_SPL_FIT_SIGNATURE
 	images.verify = 1;
 #endif
-	ret = fit_image_load(&images, (ulong)header,
+	ret = fit_image_load(&images, virt_to_phys((void *)header),
 			     NULL, &fit_uname_config,
 			     IH_ARCH_DEFAULT, IH_TYPE_STANDALONE, -1,
 			     FIT_LOAD_OPTIONAL, &fw_data, &fw_len);
@@ -884,15 +892,15 @@
 		printf("DEPRECATED: 'standalone = ' property.");
 		printf("Please use either 'firmware =' or 'kernel ='\n");
 	} else {
-		ret = fit_image_load(&images, (ulong)header, NULL,
-				     &fit_uname_config, IH_ARCH_DEFAULT,
+		ret = fit_image_load(&images, virt_to_phys((void *)header),
+				     NULL, &fit_uname_config, IH_ARCH_DEFAULT,
 				     IH_TYPE_FIRMWARE, -1, FIT_LOAD_OPTIONAL,
 				     &fw_data, &fw_len);
 	}
 
 	if (ret < 0) {
-		ret = fit_image_load(&images, (ulong)header, NULL,
-				     &fit_uname_config, IH_ARCH_DEFAULT,
+		ret = fit_image_load(&images, virt_to_phys((void *)header),
+				     NULL, &fit_uname_config, IH_ARCH_DEFAULT,
 				     IH_TYPE_KERNEL, -1, FIT_LOAD_OPTIONAL,
 				     &fw_data, &fw_len);
 	}
@@ -901,8 +909,9 @@
 		return ret;
 
 	spl_image->size = fw_len;
-	spl_image->entry_point = fw_data;
 	spl_image->load_addr = fw_data;
+	if (fit_image_get_entry(header, ret, &spl_image->entry_point))
+		spl_image->entry_point = fw_data;
 	if (fit_image_get_os(header, ret, &spl_image->os))
 		spl_image->os = IH_OS_INVALID;
 	spl_image->name = genimg_get_os_name(spl_image->os);
@@ -913,9 +922,9 @@
 #ifdef CONFIG_SPL_FIT_SIGNATURE
 	images.verify = 1;
 #endif
-	ret = fit_image_load(&images, (ulong)header, NULL, &fit_uname_config,
-			     IH_ARCH_DEFAULT, IH_TYPE_FLATDT, -1,
-			     FIT_LOAD_OPTIONAL, &dt_data, &dt_len);
+	ret = fit_image_load(&images, virt_to_phys((void *)header), NULL,
+			     &fit_uname_config, IH_ARCH_DEFAULT, IH_TYPE_FLATDT,
+			     -1, FIT_LOAD_OPTIONAL, &dt_data, &dt_len);
 	if (ret >= 0) {
 		spl_image->fdt_addr = (void *)dt_data;
 
diff --git a/arch/arm/mach-imx/parse-container.c b/common/spl/spl_imx_container.c
similarity index 91%
rename from arch/arm/mach-imx/parse-container.c
rename to common/spl/spl_imx_container.c
index e2a9e2b..127802f 100644
--- a/arch/arm/mach-imx/parse-container.c
+++ b/common/spl/spl_imx_container.c
@@ -3,12 +3,14 @@
  * Copyright 2018-2021 NXP
  */
 
+#define LOG_CATEGORY LOGC_ARCH
 #include <common.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <imx_container.h>
 #include <log.h>
+#include <mapmem.h>
 #include <spl.h>
-#include <asm/mach-imx/image.h>
 #ifdef CONFIG_AHAB_BOOT
 #include <asm/mach-imx/ahab.h>
 #endif
@@ -45,7 +47,8 @@
 	debug("%s: container: %p sector: %lu sectors: %u\n", __func__,
 	      container, sector, sectors);
 	if (info->read(info, sector, sectors,
-		       (void *)images[image_index].entry) != sectors) {
+		       map_sysmem(images[image_index].dst,
+				  images[image_index].size)) != sectors) {
 		printf("%s wrong\n", __func__);
 		return NULL;
 	}
@@ -84,14 +87,14 @@
 		goto end;
 	}
 
-	if (container->tag != 0x87 && container->version != 0x0) {
-		printf("Wrong container header");
+	if (!valid_container_hdr(container)) {
+		log_err("Wrong container header\n");
 		ret = -ENOENT;
 		goto end;
 	}
 
 	if (!container->num_images) {
-		printf("Wrong container, no image found");
+		log_err("Wrong container, no image found\n");
 		ret = -ENOENT;
 		goto end;
 	}
diff --git a/common/spl/spl_legacy.c b/common/spl/spl_legacy.c
index 095443c..51656fb 100644
--- a/common/spl/spl_legacy.c
+++ b/common/spl/spl_legacy.c
@@ -7,6 +7,7 @@
 #include <image.h>
 #include <log.h>
 #include <malloc.h>
+#include <mapmem.h>
 #include <asm/sections.h>
 #include <spl.h>
 
@@ -19,7 +20,7 @@
 static void spl_parse_legacy_validate(uintptr_t start, uintptr_t size)
 {
 	uintptr_t spl_start = (uintptr_t)_start;
-	uintptr_t spl_end = (uintptr_t)_image_binary_end;
+	uintptr_t spl_end = (uintptr_t)&_image_binary_end;
 	uintptr_t end = start + size;
 
 	if ((start >= spl_start && start < spl_end) ||
@@ -129,7 +130,7 @@
 			dataptr += sizeof(*hdr);
 
 		load->read(load, dataptr, spl_image->size,
-			   (void *)(unsigned long)spl_image->load_addr);
+			   map_sysmem(spl_image->load_addr, spl_image->size));
 		break;
 
 	case IH_COMP_LZMA:
@@ -148,7 +149,8 @@
 		}
 
 		load->read(load, dataptr, spl_image->size, src);
-		ret = lzmaBuffToBuffDecompress((void *)spl_image->load_addr,
+		ret = lzmaBuffToBuffDecompress(map_sysmem(spl_image->load_addr,
+							  spl_image->size),
 					       &lzma_len, src, spl_image->size);
 		if (ret) {
 			printf("LZMA decompression error: %d\n", ret);
diff --git a/common/spl/spl_mmc.c b/common/spl/spl_mmc.c
index 0ab85d2..0b01368 100644
--- a/common/spl/spl_mmc.c
+++ b/common/spl/spl_mmc.c
@@ -8,6 +8,7 @@
 #include <common.h>
 #include <dm.h>
 #include <log.h>
+#include <mapmem.h>
 #include <part.h>
 #include <spl.h>
 #include <linux/compiler.h>
@@ -16,6 +17,7 @@
 #include <errno.h>
 #include <mmc.h>
 #include <image.h>
+#include <imx_container.h>
 
 static int mmc_load_legacy(struct spl_image_info *spl_image,
 			   struct spl_boot_device *bootdev,
@@ -45,7 +47,8 @@
 	count = blk_dread(mmc_get_blk_desc(mmc),
 			  sector + image_offset_sectors,
 			  image_size_sectors,
-			  (void *)(ulong)spl_image->load_addr);
+			  map_sysmem(spl_image->load_addr,
+				     image_size_sectors * mmc->read_bl_len));
 	debug("read %x sectors to %lx\n", image_size_sectors,
 	      spl_image->load_addr);
 	if (count != image_size_sectors)
@@ -108,7 +111,8 @@
 		load.bl_len = mmc->read_bl_len;
 		load.read = h_spl_load_read;
 		ret = spl_load_simple_fit(spl_image, &load, sector, header);
-	} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
+	} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER) &&
+		   valid_container_hdr((void *)header)) {
 		struct spl_load_info load;
 
 		load.dev = mmc;
@@ -396,18 +400,24 @@
 #if !CONFIG_IS_ENABLED(BLK)
 	block_dev = &mmc->block_dev;
 #else
-	block_dev = dev_get_uclass_plat(mmc->dev);
+	block_dev = mmc_get_blk_desc(mmc);
 #endif
 	return block_dev->devnum;
 }
 
+static struct mmc *mmc;
+
+void spl_mmc_clear_cache(void)
+{
+	mmc = NULL;
+}
+
 int spl_mmc_load(struct spl_image_info *spl_image,
 		 struct spl_boot_device *bootdev,
 		 const char *filename,
 		 int raw_part,
 		 unsigned long raw_sect)
 {
-	static struct mmc *mmc;
 	u32 boot_mode;
 	int err = 0;
 	__maybe_unused int part = 0;
diff --git a/common/spl/spl_nand.c b/common/spl/spl_nand.c
index 6cc3400..07916be 100644
--- a/common/spl/spl_nand.c
+++ b/common/spl/spl_nand.c
@@ -7,6 +7,7 @@
 #include <config.h>
 #include <fdt_support.h>
 #include <image.h>
+#include <imx_container.h>
 #include <log.h>
 #include <spl.h>
 #include <asm/io.h>
@@ -99,7 +100,8 @@
 		load.bl_len = bl_len;
 		load.read = spl_nand_fit_read;
 		return spl_load_simple_fit(spl_image, &load, offset / bl_len, header);
-	} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
+	} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER) &&
+		   valid_container_hdr((void *)header)) {
 		struct spl_load_info load;
 
 		load.dev = NULL;
diff --git a/common/spl/spl_net.c b/common/spl/spl_net.c
index b2c901b..f01d4df 100644
--- a/common/spl/spl_net.c
+++ b/common/spl/spl_net.c
@@ -11,6 +11,7 @@
 #include <errno.h>
 #include <image.h>
 #include <log.h>
+#include <mapmem.h>
 #include <spl.h>
 #include <net.h>
 #include <linux/libfdt.h>
@@ -21,14 +22,15 @@
 {
 	debug("%s: sector %lx, count %lx, buf %lx\n",
 	      __func__, sector, count, (ulong)buf);
-	memcpy(buf, (void *)(image_load_addr + sector), count);
+	memcpy(buf, map_sysmem(image_load_addr + sector, count), count);
 	return count;
 }
 
 static int spl_net_load_image(struct spl_image_info *spl_image,
 			      struct spl_boot_device *bootdev)
 {
-	struct legacy_img_hdr *header = (struct legacy_img_hdr *)image_load_addr;
+	struct legacy_img_hdr *header = map_sysmem(image_load_addr,
+						   sizeof(*header));
 	int rv;
 
 	env_init();
@@ -62,7 +64,9 @@
 		if (rv)
 			return rv;
 
-		memcpy((void *)spl_image->load_addr, header, spl_image->size);
+		memcpy(map_sysmem(spl_image->load_addr, spl_image->size),
+		       map_sysmem(image_load_addr, spl_image->size),
+		       spl_image->size);
 	}
 
 	return rv;
diff --git a/common/spl/spl_nor.c b/common/spl/spl_nor.c
index 79d4f1d..236b071 100644
--- a/common/spl/spl_nor.c
+++ b/common/spl/spl_nor.c
@@ -5,7 +5,9 @@
 
 #include <common.h>
 #include <image.h>
+#include <imx_container.h>
 #include <log.h>
+#include <mapmem.h>
 #include <spl.h>
 
 static ulong spl_nor_load_read(struct spl_load_info *load, ulong sector,
@@ -13,7 +15,7 @@
 {
 	debug("%s: sector %lx, count %lx, buf %p\n",
 	      __func__, sector, count, buf);
-	memcpy(buf, (void *)sector, count);
+	memcpy(buf, map_sysmem(sector, count), count);
 
 	return count;
 }
@@ -26,7 +28,7 @@
 static int spl_nor_load_image(struct spl_image_info *spl_image,
 			      struct spl_boot_device *bootdev)
 {
-	__maybe_unused const struct legacy_img_hdr *header;
+	struct legacy_img_hdr *header;
 	__maybe_unused struct spl_load_info load;
 
 	/*
@@ -41,7 +43,7 @@
 		 * Load Linux from its location in NOR flash to its defined
 		 * location in SDRAM
 		 */
-		header = (const struct legacy_img_hdr *)CONFIG_SYS_OS_BASE;
+		header = (void *)CONFIG_SYS_OS_BASE;
 #ifdef CONFIG_SPL_LOAD_FIT
 		if (image_get_magic(header) == FDT_MAGIC) {
 			int ret;
@@ -91,8 +93,8 @@
 	 * Load real U-Boot from its location in NOR flash to its
 	 * defined location in SDRAM
 	 */
+	header = map_sysmem(spl_nor_get_uboot_base(), sizeof(*header));
 #ifdef CONFIG_SPL_LOAD_FIT
-	header = (const struct legacy_img_hdr *)spl_nor_get_uboot_base();
 	if (image_get_magic(header) == FDT_MAGIC) {
 		debug("Found FIT format U-Boot\n");
 		load.bl_len = 1;
@@ -102,7 +104,8 @@
 					   (void *)header);
 	}
 #endif
-	if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
+	if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER) &&
+	    valid_container_hdr((void *)header)) {
 		load.bl_len = 1;
 		load.read = spl_nor_load_read;
 		return spl_load_imx_container(spl_image, &load,
@@ -111,14 +114,11 @@
 
 	/* Legacy image handling */
 	if (IS_ENABLED(CONFIG_SPL_LEGACY_IMAGE_FORMAT)) {
-		struct legacy_img_hdr hdr;
-
 		load.bl_len = 1;
 		load.read = spl_nor_load_read;
-		spl_nor_load_read(&load, spl_nor_get_uboot_base(), sizeof(hdr), &hdr);
 		return spl_load_legacy_img(spl_image, bootdev, &load,
 					   spl_nor_get_uboot_base(),
-					   &hdr);
+					   header);
 	}
 
 	return -EINVAL;
diff --git a/common/spl/spl_spi.c b/common/spl/spl_spi.c
index d69069a..3ac4b1b 100644
--- a/common/spl/spl_spi.c
+++ b/common/spl/spl_spi.c
@@ -10,12 +10,15 @@
 
 #include <common.h>
 #include <image.h>
+#include <imx_container.h>
 #include <log.h>
+#include <mapmem.h>
 #include <spi.h>
 #include <spi_flash.h>
 #include <errno.h>
 #include <spl.h>
 #include <asm/global_data.h>
+#include <asm/io.h>
 #include <dm/ofnode.h>
 
 #if CONFIG_IS_ENABLED(OS_BOOT)
@@ -133,13 +136,16 @@
 
 		if (IS_ENABLED(CONFIG_SPL_LOAD_FIT_FULL) &&
 		    image_get_magic(header) == FDT_MAGIC) {
+			u32 size = roundup(fdt_totalsize(header), 4);
+
 			err = spi_flash_read(flash, payload_offs,
-					     roundup(fdt_totalsize(header), 4),
-					     (void *)CONFIG_SYS_LOAD_ADDR);
+					     size,
+					     map_sysmem(CONFIG_SYS_LOAD_ADDR,
+							size));
 			if (err)
 				return err;
 			err = spl_parse_image_header(spl_image, bootdev,
-					(struct legacy_img_hdr *)CONFIG_SYS_LOAD_ADDR);
+					phys_to_virt(CONFIG_SYS_LOAD_ADDR));
 		} else if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) &&
 			   image_get_magic(header) == FDT_MAGIC) {
 			struct spl_load_info load;
@@ -153,7 +159,8 @@
 			err = spl_load_simple_fit(spl_image, &load,
 						  payload_offs,
 						  header);
-		} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
+		} else if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER) &&
+			   valid_container_hdr((void *)header)) {
 			struct spl_load_info load;
 
 			load.dev = flash;
@@ -170,7 +177,8 @@
 				return err;
 			err = spi_flash_read(flash, payload_offs + spl_image->offset,
 					     spl_image->size,
-					     (void *)spl_image->load_addr);
+					     map_sysmem(spl_image->load_addr,
+							spl_image->size));
 		}
 		if (IS_ENABLED(CONFIG_SPI_FLASH_SOFT_RESET)) {
 			err = spi_nor_remove(flash);
diff --git a/configs/deneb_defconfig b/configs/deneb_defconfig
index 82869e4..ee2478a 100644
--- a/configs/deneb_defconfig
+++ b/configs/deneb_defconfig
@@ -44,6 +44,8 @@
 CONFIG_SPL_BSS_START_ADDR=0x128000
 CONFIG_SPL_BSS_MAX_SIZE=0x1000
 CONFIG_SPL_BOARD_INIT=y
+# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set
+# CONFIG_SPL_LEGACY_IMAGE_FORMAT is not set
 CONFIG_SPL_SYS_MALLOC_SIMPLE=y
 # CONFIG_SPL_SHARES_INIT_SP_ADDR is not set
 CONFIG_SPL_SYS_MALLOC=y
diff --git a/configs/giedi_defconfig b/configs/giedi_defconfig
index b56b736..5e403c9 100644
--- a/configs/giedi_defconfig
+++ b/configs/giedi_defconfig
@@ -44,6 +44,8 @@
 CONFIG_SPL_BSS_START_ADDR=0x128000
 CONFIG_SPL_BSS_MAX_SIZE=0x1000
 CONFIG_SPL_BOARD_INIT=y
+# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set
+# CONFIG_SPL_LEGACY_IMAGE_FORMAT is not set
 CONFIG_SPL_SYS_MALLOC_SIMPLE=y
 # CONFIG_SPL_SHARES_INIT_SP_ADDR is not set
 CONFIG_SPL_SYS_MALLOC=y
diff --git a/configs/imx8qm_mek_defconfig b/configs/imx8qm_mek_defconfig
index b9083b0..4c52063 100644
--- a/configs/imx8qm_mek_defconfig
+++ b/configs/imx8qm_mek_defconfig
@@ -38,6 +38,8 @@
 CONFIG_SPL_BSS_START_ADDR=0x128000
 CONFIG_SPL_BSS_MAX_SIZE=0x1000
 CONFIG_SPL_BOARD_INIT=y
+# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set
+# CONFIG_SPL_LEGACY_IMAGE_FORMAT is not set
 CONFIG_SPL_SYS_MALLOC_SIMPLE=y
 # CONFIG_SPL_SHARES_INIT_SP_ADDR is not set
 CONFIG_SPL_SYS_MALLOC=y
diff --git a/configs/imx8qxp_mek_defconfig b/configs/imx8qxp_mek_defconfig
index f516b0b..f312d39 100644
--- a/configs/imx8qxp_mek_defconfig
+++ b/configs/imx8qxp_mek_defconfig
@@ -38,6 +38,8 @@
 CONFIG_SPL_BSS_START_ADDR=0x128000
 CONFIG_SPL_BSS_MAX_SIZE=0x1000
 CONFIG_SPL_BOARD_INIT=y
+# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set
+# CONFIG_SPL_LEGACY_IMAGE_FORMAT is not set
 CONFIG_SPL_SYS_MALLOC_SIMPLE=y
 # CONFIG_SPL_SHARES_INIT_SP_ADDR is not set
 CONFIG_SPL_SYS_MALLOC=y
diff --git a/configs/sandbox_noinst_defconfig b/configs/sandbox_noinst_defconfig
index d39e54f..db05e63 100644
--- a/configs/sandbox_noinst_defconfig
+++ b/configs/sandbox_noinst_defconfig
@@ -4,13 +4,18 @@
 CONFIG_SPL_LIBGENERIC_SUPPORT=y
 CONFIG_NR_DRAM_BANKS=1
 CONFIG_ENV_SIZE=0x2000
+CONFIG_SPL_DM_SPI=y
 CONFIG_DEFAULT_DEVICE_TREE="sandbox"
 CONFIG_DM_RESET=y
+CONFIG_SPL_MMC=y
 CONFIG_SPL_SERIAL=y
 CONFIG_SPL_DRIVERS_MISC=y
 CONFIG_SPL_SYS_MALLOC_F_LEN=0x8000
 CONFIG_SPL=y
-CONFIG_SYS_LOAD_ADDR=0x0
+CONFIG_SPL_FS_FAT=y
+CONFIG_SPL_SPI_FLASH_SUPPORT=y
+CONFIG_SPL_SPI=y
+CONFIG_SYS_LOAD_ADDR=0x1000000
 CONFIG_PCI=y
 CONFIG_SANDBOX_SPL=y
 CONFIG_DEBUG_UART=y
@@ -32,9 +37,26 @@
 CONFIG_SPL_NO_BSS_LIMIT=y
 CONFIG_HANDOFF=y
 CONFIG_SPL_BOARD_INIT=y
+# CONFIG_SPL_RAW_IMAGE_SUPPORT is not set
+CONFIG_SPL_LEGACY_IMAGE_FORMAT=y
+CONFIG_SPL_LOAD_IMX_CONTAINER=y
+CONFIG_SPL_SYS_MALLOC=y
+CONFIG_SPL_HAS_CUSTOM_MALLOC_START=y
+CONFIG_SPL_CUSTOM_SYS_MALLOC_ADDR=0xa000000
+CONFIG_SPL_SYS_MALLOC_SIZE=0x4000000
+CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR=y
+CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR=0x0
 CONFIG_SPL_ENV_SUPPORT=y
+CONFIG_SPL_ETH=y
+CONFIG_SPL_FS_EXT4=y
 CONFIG_SPL_I2C=y
+CONFIG_SPL_MMC_WRITE=y
+CONFIG_SPL_DM_SPI_FLASH=y
+CONFIG_SPL_NET=y
+CONFIG_SPL_NOR_SUPPORT=y
 CONFIG_SPL_RTC=y
+# CONFIG_SPL_SPI_FLASH_TINY is not set
+CONFIG_SPL_SPI_LOAD=y
 CONFIG_CMD_CPU=y
 CONFIG_CMD_LICENSE=y
 CONFIG_CMD_BOOTZ=y
@@ -91,10 +113,13 @@
 CONFIG_OF_CONTROL=y
 CONFIG_SPL_OF_CONTROL=y
 CONFIG_SPL_OF_PLATDATA=y
+CONFIG_SPL_OF_REAL=y
 CONFIG_ENV_IS_NOWHERE=y
 CONFIG_ENV_IS_IN_EXT4=y
 CONFIG_ENV_EXT4_INTERFACE="host"
 CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0"
+CONFIG_USE_BOOTFILE=y
+CONFIG_BOOTFILE="uImage"
 CONFIG_BOOTP_SEND_HOSTNAME=y
 CONFIG_NETCONSOLE=y
 CONFIG_IP_DEFRAG=y
@@ -112,6 +137,7 @@
 CONFIG_ADC_SANDBOX=y
 CONFIG_AXI=y
 CONFIG_AXI_SANDBOX=y
+CONFIG_SPL_BLK_FS=y
 CONFIG_SYS_IDE_MAXBUS=1
 CONFIG_SYS_ATA_BASE_ADDR=0x100
 CONFIG_SYS_ATA_STRIDE=4
@@ -153,6 +179,7 @@
 CONFIG_P2SB=y
 CONFIG_PWRSEQ=y
 CONFIG_SPL_PWRSEQ=y
+CONFIG_FS_LOADER=y
 CONFIG_MMC_SANDBOX=y
 CONFIG_SPI_FLASH_SANDBOX=y
 CONFIG_SPI_FLASH_ATMEL=y
@@ -214,6 +241,7 @@
 CONFIG_SPL_SYSRESET=y
 CONFIG_DM_THERMAL=y
 CONFIG_TIMER=y
+CONFIG_SPL_TIMER=y
 CONFIG_TIMER_EARLY=y
 CONFIG_SANDBOX_TIMER=y
 CONFIG_USB=y
@@ -236,6 +264,7 @@
 CONFIG_TPM=y
 CONFIG_LZ4=y
 CONFIG_ZSTD=y
+CONFIG_SPL_LZMA=y
 CONFIG_ERRNO_STR=y
 CONFIG_EFI_CAPSULE_ON_DISK=y
 CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y
diff --git a/configs/sandbox_spl_defconfig b/configs/sandbox_spl_defconfig
index 4a67af2..56072b1 100644
--- a/configs/sandbox_spl_defconfig
+++ b/configs/sandbox_spl_defconfig
@@ -32,9 +32,16 @@
 CONFIG_SPL_NO_BSS_LIMIT=y
 CONFIG_HANDOFF=y
 CONFIG_SPL_BOARD_INIT=y
+CONFIG_SPL_LEGACY_IMAGE_FORMAT=y
+CONFIG_SPL_LOAD_IMX_CONTAINER=y
+CONFIG_SPL_SYS_MALLOC=y
+CONFIG_SPL_HAS_CUSTOM_MALLOC_START=y
+CONFIG_SPL_CUSTOM_SYS_MALLOC_ADDR=0xa000000
+CONFIG_SPL_SYS_MALLOC_SIZE=0x4000000
 CONFIG_SPL_ENV_SUPPORT=y
 CONFIG_SPL_FPGA=y
 CONFIG_SPL_I2C=y
+CONFIG_SPL_NOR_SUPPORT=y
 CONFIG_SPL_RTC=y
 CONFIG_CMD_CPU=y
 CONFIG_CMD_LICENSE=y
@@ -243,6 +250,7 @@
 CONFIG_SPL_CRC8=y
 CONFIG_LZ4=y
 CONFIG_ZSTD=y
+CONFIG_SPL_LZMA=y
 CONFIG_ERRNO_STR=y
 CONFIG_SPL_HEXDUMP=y
 CONFIG_EFI_CAPSULE_ON_DISK=y
diff --git a/configs/sandbox_vpl_defconfig b/configs/sandbox_vpl_defconfig
index 8d76f19..5bd0281 100644
--- a/configs/sandbox_vpl_defconfig
+++ b/configs/sandbox_vpl_defconfig
@@ -262,3 +262,4 @@
 CONFIG_SPL_UNIT_TEST=y
 CONFIG_UT_TIME=y
 CONFIG_UT_DM=y
+# CONFIG_SPL_UT_LOAD_OS is not set
diff --git a/drivers/core/Makefile b/drivers/core/Makefile
index bce0a3f..acbd2bf 100644
--- a/drivers/core/Makefile
+++ b/drivers/core/Makefile
@@ -15,6 +15,7 @@
 ifndef CONFIG_DM_DEV_READ_INLINE
 obj-$(CONFIG_OF_CONTROL) += read.o
 endif
+obj-$(CONFIG_$(SPL_)OF_PLATDATA) += read.o
 obj-$(CONFIG_OF_CONTROL) += of_extra.o ofnode.o read_extra.o
 
 ccflags-$(CONFIG_DM_DEBUG) += -DDEBUG
diff --git a/drivers/core/root.c b/drivers/core/root.c
index 126b314..d4ae652 100644
--- a/drivers/core/root.c
+++ b/drivers/core/root.c
@@ -426,7 +426,7 @@
 		stats->tag_size;
 }
 
-#ifdef CONFIG_ACPIGEN
+#if CONFIG_IS_ENABLED(ACPIGEN)
 static int root_acpi_get_name(const struct udevice *dev, char *out_name)
 {
 	return acpi_copy_name(out_name, "\\_SB");
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index d5b85f3..a96a8c7 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -3,7 +3,7 @@
 # (C) Copyright 2000-2007
 # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 obj-$(CONFIG_$(SPL_)DM_I2C) += i2c-uclass.o
-ifdef CONFIG_ACPIGEN
+ifdef CONFIG_$(SPL_)ACPIGEN
 obj-$(CONFIG_$(SPL_)DM_I2C) += acpi_i2c.o
 endif
 obj-$(CONFIG_$(SPL_)DM_I2C_GPIO) += i2c-gpio.o
diff --git a/drivers/i2c/i2c-emul-uclass.c b/drivers/i2c/i2c-emul-uclass.c
index 1107cf3..d421ddf 100644
--- a/drivers/i2c/i2c-emul-uclass.c
+++ b/drivers/i2c/i2c-emul-uclass.c
@@ -46,7 +46,7 @@
 	struct udevice *emul;
 	int ret;
 
-	if (!CONFIG_IS_ENABLED(OF_PLATDATA)) {
+	if (CONFIG_IS_ENABLED(OF_REAL)) {
 		ret = uclass_find_device_by_phandle(UCLASS_I2C_EMUL, dev,
 						    "sandbox,emul", &emul);
 	} else {
diff --git a/drivers/serial/sandbox.c b/drivers/serial/sandbox.c
index f400381..f6ac3d2 100644
--- a/drivers/serial/sandbox.c
+++ b/drivers/serial/sandbox.c
@@ -280,7 +280,7 @@
 	.flags = DM_FLAG_PRE_RELOC,
 };
 
-#if CONFIG_IS_ENABLED(OF_REAL)
+#if CONFIG_IS_ENABLED(OF_REAL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
 static const struct sandbox_serial_plat platdata_non_fdt = {
 	.colour = -1,
 };
diff --git a/drivers/sysreset/sysreset_sandbox.c b/drivers/sysreset/sysreset_sandbox.c
index 3750c60..f485a13 100644
--- a/drivers/sysreset/sysreset_sandbox.c
+++ b/drivers/sysreset/sysreset_sandbox.c
@@ -132,7 +132,7 @@
 	.ops		= &sandbox_warm_sysreset_ops,
 };
 
-#if CONFIG_IS_ENABLED(OF_REAL)
+#if CONFIG_IS_ENABLED(OF_REAL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
 /* This is here in case we don't have a device tree */
 U_BOOT_DRVINFO(sysreset_sandbox_non_fdt) = {
 	.name = "sysreset_sandbox",
diff --git a/drivers/usb/gadget/f_sdp.c b/drivers/usb/gadget/f_sdp.c
index 2b3a9c5..ee9384f 100644
--- a/drivers/usb/gadget/f_sdp.c
+++ b/drivers/usb/gadget/f_sdp.c
@@ -34,6 +34,7 @@
 #include <spl.h>
 #include <image.h>
 #include <imximage.h>
+#include <imx_container.h>
 #include <watchdog.h>
 
 #define HID_REPORT_ID_MASK	0x000000ff
@@ -852,7 +853,8 @@
 				return SDP_EXIT;
 			}
 #endif
-			if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER)) {
+			if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER) &&
+			    valid_container_hdr((void *)header)) {
 				struct spl_load_info load;
 
 				load.dev = header;
diff --git a/dts/Kconfig b/dts/Kconfig
index 9152f58..00c0aef 100644
--- a/dts/Kconfig
+++ b/dts/Kconfig
@@ -410,12 +410,14 @@
 	  declarations for each node. See of-plat.txt for more information.
 
 config SPL_OF_REAL
-	bool
+	bool "Support a real devicetree in SPL" if SANDBOX
+	depends on SPL_OF_CONTROL
+	select SPL_OF_LIBFDT
 	help
 	  Indicates that a real devicetree is available which can be accessed
 	  at runtime. This means that dev_read_...() functions can be used to
-	  read data from the devicetree for each device. This is true if
-	  SPL_OF_CONTROL is enabled and not SPL_OF_PLATDATA
+	  read data from the devicetree for each device. You do not need to
+	  enable this option if you have enabled SPL_OF_PLATDATA.
 
 if SPL_OF_PLATDATA
 
diff --git a/fs/ext4/ext4_common.c b/fs/ext4/ext4_common.c
index 9a9c520..f50de7c 100644
--- a/fs/ext4/ext4_common.c
+++ b/fs/ext4/ext4_common.c
@@ -2373,10 +2373,6 @@
 	struct ext2_data *data;
 	int status;
 	struct ext_filesystem *fs = get_fs();
-
-	if (part_length < SUPERBLOCK_SIZE)
-		return 0;
-
 	data = zalloc(SUPERBLOCK_SIZE);
 	if (!data)
 		return 0;
diff --git a/fs/fs.c b/fs/fs.c
index cfc781b..4cb4310 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -237,7 +237,7 @@
 		.mkdir = fs_mkdir_unsupported,
 	},
 #endif
-#ifdef CONFIG_SANDBOX
+#if IS_ENABLED(CONFIG_SANDBOX) && !IS_ENABLED(CONFIG_SPL_BUILD)
 	{
 		.fstype = FS_TYPE_SANDBOX,
 		.name = "sandbox",
diff --git a/include/configs/sandbox.h b/include/configs/sandbox.h
index 4e5653d..2372485 100644
--- a/include/configs/sandbox.h
+++ b/include/configs/sandbox.h
@@ -18,4 +18,7 @@
 #define CFG_SYS_BAUDRATE_TABLE	{4800, 9600, 19200, 38400, 57600,\
 					115200}
 
+/* Unused but necessary to build */
+#define CFG_SYS_UBOOT_BASE	CONFIG_TEXT_BASE
+
 #endif
diff --git a/include/ext4fs.h b/include/ext4fs.h
index cb5d9cc..dd66d27 100644
--- a/include/ext4fs.h
+++ b/include/ext4fs.h
@@ -31,6 +31,7 @@
 struct disk_partition;
 
 #define EXT4_INDEX_FL		0x00001000 /* Inode uses hash tree index */
+#define EXT4_TOPDIR_FL		0x00020000 /* Top of directory hierarchies*/
 #define EXT4_EXTENTS_FL		0x00080000 /* Inode uses extents */
 #define EXT4_EXT_MAGIC			0xf30a
 #define EXT4_FEATURE_RO_COMPAT_GDT_CSUM	0x0010
diff --git a/include/ext_common.h b/include/ext_common.h
index 30a0c24..b09bbde 100644
--- a/include/ext_common.h
+++ b/include/ext_common.h
@@ -35,6 +35,16 @@
 #define EXT2_PATH_MAX				4096
 /* Maximum nesting of symlinks, used to prevent a loop.  */
 #define	EXT2_MAX_SYMLINKCNT		8
+/* Maximum file name length */
+#define EXT2_NAME_LEN 255
+
+/*
+ * Revision levels
+ */
+#define EXT2_GOOD_OLD_REV	0	/* The good old (original) format */
+#define EXT2_DYNAMIC_REV	1	/* V2 format w/ dynamic inode sizes */
+
+#define EXT2_GOOD_OLD_INODE_SIZE 128
 
 /* Filetype used in directory entry.  */
 #define	FILETYPE_UNKNOWN		0
@@ -48,6 +58,10 @@
 #define FILETYPE_INO_DIRECTORY		0040000
 #define FILETYPE_INO_SYMLINK		0120000
 #define EXT2_ROOT_INO			2 /* Root inode */
+#define EXT2_BOOT_LOADER_INO		5 /* Boot loader inode */
+
+/* First non-reserved inode for old ext2 filesystems */
+#define EXT2_GOOD_OLD_FIRST_INO	11
 
 /* The size of an ext2 block in bytes.  */
 #define EXT2_BLOCK_SIZE(data)	   (1 << LOG2_BLOCK_SIZE(data))
diff --git a/arch/arm/include/asm/mach-imx/image.h b/include/imx_container.h
similarity index 82%
rename from arch/arm/include/asm/mach-imx/image.h
rename to include/imx_container.h
index ee67ca9..54cd684 100644
--- a/arch/arm/include/asm/mach-imx/image.h
+++ b/include/imx_container.h
@@ -18,6 +18,9 @@
 #define CONTAINER_HDR_QSPI_OFFSET SZ_4K
 #define CONTAINER_HDR_NAND_OFFSET SZ_128M
 
+#define CONTAINER_HDR_TAG 0x87
+#define CONTAINER_HDR_VERSION 0
+
 struct container_hdr {
 	u8 version;
 	u8 length_lsb;
@@ -66,4 +69,10 @@
 } __packed;
 
 int get_container_size(ulong addr, u16 *header_length);
+
+static inline bool valid_container_hdr(struct container_hdr *container)
+{
+	return container->tag == CONTAINER_HDR_TAG &&
+	       container->version == CONTAINER_HDR_VERSION;
+}
 #endif
diff --git a/include/spl.h b/include/spl.h
index 1d416b4..0d49e4a 100644
--- a/include/spl.h
+++ b/include/spl.h
@@ -416,6 +416,16 @@
 void preloader_console_init(void);
 u32 spl_boot_device(void);
 
+struct spi_flash;
+
+/**
+ * spl_spi_get_uboot_offs() - Lookup function for the SPI boot offset
+ * @flash: The spi flash to boot from
+ *
+ * Return: The offset of U-Boot within the SPI flash
+ */
+unsigned int spl_spi_get_uboot_offs(struct spi_flash *flash);
+
 /**
  * spl_spi_boot_bus() - Lookup function for the SPI boot bus source.
  *
@@ -673,7 +683,23 @@
 	}
 #endif
 
+#define SPL_LOAD_IMAGE_GET(_priority, _boot_device, _method) \
+	ll_entry_get(struct spl_image_loader, \
+		     _boot_device ## _priority ## _method, spl_image_loader)
+
 /* SPL FAT image functions */
+
+/**
+ * spl_fat_force_reregister() - Force reregistration of FAT block devices
+ *
+ * To avoid repeatedly looking up block devices, spl_load_image_fat keeps track
+ * of whether it has already registered a block device. This is fine for most
+ * cases, but when running unit tests all devices are removed and recreated
+ * in-between tests. This function will force re-registration of any block
+ * devices, ensuring that we don't try to use an invalid block device.
+ */
+void spl_fat_force_reregister(void);
+
 int spl_load_image_fat(struct spl_image_info *spl_image,
 		       struct spl_boot_device *bootdev,
 		       struct blk_desc *block_dev, int partition,
@@ -753,6 +779,16 @@
  */
 int spl_dfu_cmd(int usbctrl, char *dfu_alt_info, char *interface, char *devstr);
 
+/**
+ * spl_mmc_clear_cache() - Clear cached MMC devices
+ *
+ * To avoid reinitializing MMCs, spl_mmc_load caches the most-recently-used MMC
+ * device. This is fine for most cases, but when running unit tests all devices
+ * are removed and recreated in-between tests. This function will clear any
+ * cached state, ensuring that we don't try to use an invalid MMC.
+ */
+void spl_mmc_clear_cache(void);
+
 int spl_mmc_load_image(struct spl_image_info *spl_image,
 		       struct spl_boot_device *bootdev);
 
diff --git a/include/test/spl.h b/include/test/spl.h
new file mode 100644
index 0000000..c1f6465
--- /dev/null
+++ b/include/test/spl.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
+ */
+
+#ifndef TEST_SPL_H
+#define TEST_SPL_H
+
+struct unit_test_state;
+struct spl_image_info;
+
+/* Declare a new SPL test */
+#define SPL_TEST(_name, _flags)		UNIT_TEST(_name, _flags, spl_test)
+
+/**
+ * generate_data() - Generate some test payload data
+ * @data: The location to fill
+ * @size: The size of @data
+ * @test_name: The seed for the data
+ *
+ * Fill @data with data. The upper nibbles will be an incrementing counter
+ * (0x00, 0x10, 0x20...) to make the data identifiable in a hex dump. The lower
+ * nibbles are random bits seeded with @test_name.
+ */
+void generate_data(char *data, size_t size, const char *test_name);
+
+/**
+ * enum spl_test_image - Image types for testing
+ * @LEGACY: "Legacy" uImages
+ * @LEGACY_LZMA: "Legacy" uImages, LZMA compressed
+ * @IMX8: i.MX8 Container images
+ * @FIT_INTERNAL: FITs with internal data
+ * @FIT_EXTERNAL: FITs with external data
+ */
+enum spl_test_image {
+	LEGACY,
+	LEGACY_LZMA,
+	IMX8,
+	FIT_INTERNAL,
+	FIT_EXTERNAL,
+};
+
+/**
+ * create_image() - Create an image for testing
+ * @dst: The location to create the image at
+ * @type: The type of image to create
+ * @info: Image parameters
+ * @data_offset: Offset of payload data within the image
+ *
+ * Create a new image at @dst. @dst must be initialized to all zeros. @info
+ * should already have name and size filled in. All other parameters will be
+ * filled in by this function. @info can later be passed to check_image_info().
+ *
+ * If @dst is %NULL, then no data is written. Otherwise, @dst must be
+ * initialized to zeros, except payload data which must already be present at
+ * @data_offset. @data_offset may be %NULL if unnecessary.
+ *
+ * Typically, this function will be called as follows:
+ *
+ *     size = create_image(NULL, type, &info, &off);
+ *     img = calloc(size, 1);
+ *     generate_data(img + off, ...);
+ *     create_image(img, type, &info, NULL);
+ *
+ * Return: The size of the image, or 0 on error
+ */
+size_t create_image(void *dst, enum spl_test_image type,
+		    struct spl_image_info *info, size_t *data_offset);
+
+/**
+ * check_image_info() - Check image info after loading
+ * @uts: Current unit test state
+ * @info1: The base, known good info
+ * @info2: The info to check
+ *
+ * Check @info2 against @info1. This function is typically called after calling
+ * a function to load/parse an image. Image data is not checked.
+ *
+ * Return: 0 on success, or 1 on failure
+ */
+int check_image_info(struct unit_test_state *uts, struct spl_image_info *info1,
+		     struct spl_image_info *info2);
+
+/**
+ * typedef write_image_t - Callback for writing an image
+ * @uts: Current unit test state
+ * @img: Image to write
+ * @size: Size of @img
+ *
+ * Write @img to a location which will be read by a &struct spl_image_loader.
+ *
+ * Return: 0 on success or 1 on failure
+ */
+typedef int write_image_t(struct unit_test_state *its, void *img, size_t size);
+
+/**
+ * do_spl_test_load() - Test loading with an SPL image loader
+ * @uts: Current unit test state
+ * @test_name: Name of the current test
+ * @type: Type of image to try loading
+ * @loader: The loader to test
+ * @write_image: Callback to write the image to the backing storage
+ *
+ * Test @loader, performing the common tasks of setting up the image and
+ * checking it was loaded correctly. The caller must supply a @write_image
+ * callback to write the image to a location which will be read by @loader.
+ *
+ * Return: 0 on success or 1 on failure
+ */
+int do_spl_test_load(struct unit_test_state *uts, const char *test_name,
+		     enum spl_test_image type, struct spl_image_loader *loader,
+		     write_image_t write_image);
+
+/**
+ * image_supported() - Determine whether an image type is supported
+ * @type: The image type to check
+ *
+ * Return: %true if supported and %false otherwise
+ */
+static inline bool image_supported(enum spl_test_image type)
+{
+	switch (type) {
+	case LEGACY_LZMA:
+		if (!IS_ENABLED(CONFIG_SPL_LZMA))
+			return false;
+	case LEGACY:
+		return IS_ENABLED(CONFIG_SPL_LEGACY_IMAGE_FORMAT);
+	case IMX8:
+		return IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER);
+	case FIT_INTERNAL:
+	case FIT_EXTERNAL:
+		return IS_ENABLED(CONFIG_SPL_LOAD_FIT) ||
+		       IS_ENABLED(CONFIG_SPL_LOAD_FIT_FULL);
+	}
+
+	return false;
+}
+
+/* Declare an image test (skipped if the image type is unsupported) */
+#define SPL_IMG_TEST(func, type, flags) \
+static int func##_##type(struct unit_test_state *uts) \
+{ \
+	if (!image_supported(type)) \
+		return -EAGAIN; \
+	return func(uts, __func__, type); \
+} \
+SPL_TEST(func##_##type, flags)
+
+/* More than a couple blocks, and will not be aligned to anything */
+#define SPL_TEST_DATA_SIZE	4099
+
+/* Flags necessary for accessing DM devices */
+#define DM_FLAGS (UT_TESTF_DM | UT_TESTF_SCAN_FDT)
+
+#endif /* TEST_SPL_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 79cf9ef..f6ca559 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -687,6 +687,12 @@
 	  checksum with feedback to produce an 8-bit result. The code is small
 	  and it does not require a lookup table (unlike CRC32).
 
+config SPL_CRC16
+	bool "Support CRC16 in SPL"
+	depends on SPL
+	help
+	  Enables CRC16 support in SPL. This is not normally required.
+
 config CRC32
 	def_bool y
 	help
diff --git a/lib/Makefile b/lib/Makefile
index 1c31ad9..2a76acf 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -64,6 +64,7 @@
 endif
 
 obj-$(CONFIG_$(SPL_TPL_)CRC8) += crc8.o
+obj-$(CONFIG_$(SPL_TPL_)CRC16) += crc16.o
 
 obj-y += crypto/
 
diff --git a/net/Makefile b/net/Makefile
index 3e2d061..64ab7ec 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -27,8 +27,8 @@
 obj-$(CONFIG_CMD_RARP) += rarp.o
 obj-$(CONFIG_CMD_SNTP) += sntp.o
 obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o
-obj-$(CONFIG_UDP_FUNCTION_FASTBOOT)  += fastboot_udp.o
-obj-$(CONFIG_TCP_FUNCTION_FASTBOOT)  += fastboot_tcp.o
+obj-$(CONFIG_$(SPL_TPL_)UDP_FUNCTION_FASTBOOT)  += fastboot_udp.o
+obj-$(CONFIG_$(SPL_TPL_)TCP_FUNCTION_FASTBOOT)  += fastboot_tcp.o
 obj-$(CONFIG_CMD_WOL)  += wol.o
 obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
diff --git a/net/bootp.c b/net/bootp.c
index 8b1a4ae..7b0f45e 100644
--- a/net/bootp.c
+++ b/net/bootp.c
@@ -41,9 +41,6 @@
  */
 #define TIMEOUT_MS	((3 + (CONFIG_NET_RETRY_COUNT * 5)) * 1000)
 
-#define PORT_BOOTPS	67		/* BOOTP server UDP port */
-#define PORT_BOOTPC	68		/* BOOTP client UDP port */
-
 #ifndef CFG_DHCP_MIN_EXT_LEN		/* minimal length of extension list */
 #define CFG_DHCP_MIN_EXT_LEN 64
 #endif
@@ -1076,6 +1073,11 @@
 			    CONFIG_SYS_BOOTFILE_PREFIX,
 			    strlen(CONFIG_SYS_BOOTFILE_PREFIX)) == 0) {
 #endif	/* CONFIG_SYS_BOOTFILE_PREFIX */
+			if (CONFIG_IS_ENABLED(UNIT_TEST) &&
+			    dhcp_message_type((u8 *)bp->bp_vend) == -1) {
+				debug("got BOOTP response; transitioning to BOUND\n");
+				goto dhcp_got_bootp;
+			}
 			dhcp_packet_process_options(bp);
 			if (CONFIG_IS_ENABLED(EFI_LOADER) &&
 			    IS_ENABLED(CONFIG_NETDEVICES))
@@ -1102,6 +1104,7 @@
 		debug("DHCP State: REQUESTING\n");
 
 		if (dhcp_message_type((u8 *)bp->bp_vend) == DHCP_ACK) {
+dhcp_got_bootp:
 			dhcp_packet_process_options(bp);
 			/* Store net params from reply */
 			store_net_params(bp);
diff --git a/net/bootp.h b/net/bootp.h
index 567340e..4e32b19 100644
--- a/net/bootp.h
+++ b/net/bootp.h
@@ -15,6 +15,9 @@
 
 /**********************************************************************/
 
+#define PORT_BOOTPS	67		/* BOOTP server UDP port */
+#define PORT_BOOTPC	68		/* BOOTP client UDP port */
+
 /*
  *	BOOTP header.
  */
diff --git a/net/net.c b/net/net.c
index e6f61f0..8357f08 100644
--- a/net/net.c
+++ b/net/net.c
@@ -511,12 +511,12 @@
 			tftp_start_server();
 			break;
 #endif
-#if defined(CONFIG_UDP_FUNCTION_FASTBOOT)
+#if CONFIG_IS_ENABLED(UDP_FUNCTION_FASTBOOT)
 		case FASTBOOT_UDP:
 			fastboot_udp_start_server();
 			break;
 #endif
-#if defined(CONFIG_TCP_FUNCTION_FASTBOOT)
+#if CONFIG_IS_ENABLED(TCP_FUNCTION_FASTBOOT)
 		case FASTBOOT_TCP:
 			fastboot_tcp_start_server();
 			break;
diff --git a/test/Kconfig b/test/Kconfig
index 830245b..ca648d2 100644
--- a/test/Kconfig
+++ b/test/Kconfig
@@ -101,6 +101,7 @@
 
 source "test/dm/Kconfig"
 source "test/env/Kconfig"
+source "test/image/Kconfig"
 source "test/lib/Kconfig"
 source "test/optee/Kconfig"
 source "test/overlay/Kconfig"
diff --git a/test/Makefile b/test/Makefile
index 1787736..8e1fed2 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -3,9 +3,6 @@
 # (C) Copyright 2012 The Chromium Authors
 
 obj-y += test-main.o
-ifdef CONFIG_SPL_LOAD_FIT
-obj-$(CONFIG_SANDBOX) += image/
-endif
 
 ifneq ($(CONFIG_$(SPL_)BLOBLIST),)
 obj-$(CONFIG_$(SPL_)CMDLINE) += bloblist.o
@@ -30,4 +27,6 @@
 obj-$(CONFIG_UNIT_TEST) += common/
 obj-y += log/
 obj-$(CONFIG_$(SPL_)UT_UNICODE) += unicode_ut.o
+else
+obj-$(CONFIG_SPL_UT_LOAD) += image/
 endif
diff --git a/test/image/Kconfig b/test/image/Kconfig
new file mode 100644
index 0000000..8f9e6ae
--- /dev/null
+++ b/test/image/Kconfig
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
+
+config SPL_UT_LOAD
+	bool "Unit tests for SPL load methods"
+	depends on SPL_UNIT_TEST
+	default y if SANDBOX
+	help
+	  Test various SPL load methods.
+
+if SPL_UT_LOAD
+
+config SPL_UT_LOAD_FS
+	bool "Unit tests for filesystems"
+	depends on SANDBOX && SPL_OF_REAL
+	depends on FS_LOADER
+	depends on SPL_BLK_FS
+	depends on SPL_FS_FAT
+	depends on SPL_FS_EXT4
+	depends on SPL_MMC_WRITE
+	depends on SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR
+	default y
+	help
+	  Test filesystems and the various load methods which use them.
+
+config SPL_UT_LOAD_NET
+	bool "Test loading over TFTP"
+	depends on SANDBOX && SPL_OF_REAL
+	depends on SPL_ETH
+	depends on USE_BOOTFILE
+	default y
+	help
+	  Test loading images over TFTP using the NET image load method.
+
+config SPL_UT_LOAD_SPI
+	bool "Test loading from SPI Flash"
+	depends on SANDBOX && SPL_OF_REAL
+	depends on SPL_SPI_LOAD
+	default y
+	help
+	  Test the SPI flash image load metod.
+
+config SPL_UT_LOAD_OS
+	bool "Test loading from the host OS"
+	depends on SANDBOX && SPL_LOAD_FIT
+	default y
+	help
+	  Smoke test to ensure that loading U-boot works in sandbox.
+
+endif
diff --git a/test/image/Makefile b/test/image/Makefile
index c4039df..b302101 100644
--- a/test/image/Makefile
+++ b/test/image/Makefile
@@ -2,4 +2,9 @@
 #
 # Copyright 2021 Google LLC
 
-obj-$(CONFIG_SPL_BUILD) += spl_load.o
+obj-y += spl_load.o
+obj-$(CONFIG_SPL_UT_LOAD_FS) += spl_load_fs.o
+obj-$(CONFIG_SPL_UT_LOAD_NET) += spl_load_net.o
+obj-$(CONFIG_SPL_NOR_SUPPORT) += spl_load_nor.o
+obj-$(CONFIG_SPL_UT_LOAD_OS) += spl_load_os.o
+obj-$(CONFIG_SPL_UT_LOAD_SPI) += spl_load_spi.o
diff --git a/test/image/spl_load.c b/test/image/spl_load.c
index 4e27ff4..ab4c14d 100644
--- a/test/image/spl_load.c
+++ b/test/image/spl_load.c
@@ -1,91 +1,661 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
- * Copyright 2021 Google LLC
- * Written by Simon Glass <sjg@chromium.org>
+ * Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
  */
 
 #include <common.h>
 #include <image.h>
+#include <imx_container.h>
 #include <mapmem.h>
-#include <os.h>
+#include <memalign.h>
+#include <rand.h>
+#include <spi_flash.h>
 #include <spl.h>
+#include <test/spl.h>
 #include <test/ut.h>
+#include <u-boot/crc.h>
 
-/* Declare a new SPL test */
-#define SPL_TEST(_name, _flags)		UNIT_TEST(_name, _flags, spl_test)
+int board_fit_config_name_match(const char *name)
+{
+	return 0;
+}
 
-/* Context used for this test */
-struct text_ctx {
-	int fd;
-};
+struct legacy_img_hdr *spl_get_load_buffer(ssize_t offset, size_t size)
+{
+	return map_sysmem(0x100000, 0);
+}
+
+/* Try to reuse the load buffer to conserve memory */
+void *board_spl_fit_buffer_addr(ulong fit_size, int sectors, int bl_len)
+{
+	static void *buf;
+	static size_t size;
+
+	if (size < sectors * bl_len) {
+		free(buf);
+		size = sectors * bl_len;
+		buf = malloc_cache_aligned(size);
+	}
+	return buf;
+}
+
+/* Local flags for spl_image; start from the "top" to avoid conflicts */
+#define SPL_IMX_CONTAINER	0x80000000
+#define SPL_COMP_LZMA		0x40000000
+
+void generate_data(char *data, size_t size, const char *test_name)
+{
+	int i;
+	unsigned int seed = 1;
+
+	while (*test_name) {
+		seed += *test_name++;
+		rand_r(&seed);
+	}
+
+	for (i = 0; i < size; i++)
+		data[i] = (i & 0xf) << 4 | (rand_r(&seed) & 0xf);
+}
+
+static size_t create_legacy(void *dst, struct spl_image_info *spl_image,
+			    size_t *data_offset)
+{
+	struct legacy_img_hdr *hdr = dst;
+	void *data = dst + sizeof(*hdr);
+
+	if (data_offset)
+		*data_offset = data - dst;
+
+	if (!dst)
+		goto out;
+
+	image_set_magic(hdr, IH_MAGIC);
+	image_set_time(hdr, 0);
+	image_set_size(hdr, spl_image->size);
+	image_set_load(hdr, spl_image->load_addr);
+	image_set_ep(hdr, spl_image->entry_point);
+	image_set_dcrc(hdr, crc32(0, data, spl_image->size));
+	image_set_os(hdr, spl_image->os);
+	image_set_arch(hdr, IH_ARCH_DEFAULT);
+	image_set_type(hdr, IH_TYPE_FIRMWARE);
+	image_set_comp(hdr, spl_image->flags & SPL_COMP_LZMA ? IH_COMP_LZMA :
+							       IH_COMP_NONE);
+	image_set_name(hdr, spl_image->name);
+	image_set_hcrc(hdr, crc32(0, (void *)hdr, sizeof(*hdr)));
+
+out:
+	return sizeof(*hdr) + spl_image->size;
+}
 
-static ulong read_fit_image(struct spl_load_info *load, ulong sector,
-			    ulong count, void *buf)
+static size_t create_imx8(void *dst, struct spl_image_info *spl_image,
+			  size_t *data_offset)
 {
-	struct text_ctx *text_ctx = load->priv;
-	off_t offset, ret;
-	ssize_t res;
+	struct container_hdr *hdr = dst;
+	struct boot_img_t *img = dst + sizeof(*hdr);
+	size_t length = sizeof(*hdr) + sizeof(*img);
+	/* Align to MMC block size for now */
+	void *data = dst + 512;
 
-	offset = sector * load->bl_len;
-	ret = os_lseek(text_ctx->fd, offset, OS_SEEK_SET);
-	if (ret != offset) {
-		printf("Failed to seek to %zx, got %zx (errno=%d)\n", offset,
-		       ret, errno);
+	if (data_offset)
+		*data_offset = data - dst;
+
+	if (!dst)
+		goto out;
+
+	hdr->version = CONTAINER_HDR_VERSION;
+	hdr->length_lsb = length & 0xff;
+	hdr->length_msb = length >> 8;
+	hdr->tag = CONTAINER_HDR_TAG;
+	hdr->num_images = 1;
+
+	/* spl_load_imx_container doesn't handle endianness; whoops! */
+	img->offset = data - dst;
+	img->size = spl_image->size;
+	img->dst = spl_image->load_addr;
+	img->entry = spl_image->entry_point;
+
+out:
+	return data - dst + spl_image->size;
+}
+
+#define ADDRESS_CELLS (sizeof(uintptr_t) / sizeof(u32))
+
+static inline int fdt_property_addr(void *fdt, const char *name, uintptr_t val)
+{
+	if (sizeof(uintptr_t) == sizeof(u32))
+		return fdt_property_u32(fdt, name, val);
+	return fdt_property_u64(fdt, name, val);
+}
+
+static size_t start_fit(void *dst, size_t fit_size, size_t data_size,
+			bool external)
+{
+	void *data;
+
+	if (fdt_create(dst, fit_size))
+		return 0;
+	if (fdt_finish_reservemap(dst))
+		return 0;
+	if (fdt_begin_node(dst, ""))
 		return 0;
+	if (fdt_property_u32(dst, FIT_TIMESTAMP_PROP, 0))
+		return 0;
+	if (fdt_property_u32(dst, "#address-cells", ADDRESS_CELLS))
+		return 0;
+	if (fdt_property_string(dst, FIT_DESC_PROP, ""))
+		return 0;
+
+	if (fdt_begin_node(dst, "images"))
+		return 0;
+	if (fdt_begin_node(dst, "u-boot"))
+		return 0;
+
+	if (external) {
+		if (fdt_property_u32(dst, FIT_DATA_OFFSET_PROP, 0))
+			return 0;
+		return fit_size;
 	}
 
-	res = os_read(text_ctx->fd, buf, count * load->bl_len);
-	if (res == -1) {
-		printf("Failed to read %lx bytes, got %ld (errno=%d)\n",
-		       count * load->bl_len, res, errno);
+	if (fdt_property_placeholder(dst, FIT_DATA_PROP, data_size, &data))
 		return 0;
+	return data - dst;
+}
+
+static size_t create_fit(void *dst, struct spl_image_info *spl_image,
+			 size_t *data_offset, bool external)
+{
+	size_t prop_size = 596, total_size = prop_size + spl_image->size;
+	size_t off, size;
+
+	if (external) {
+		size = prop_size;
+		off = size;
+	} else {
+		char tmp[256];
+
+		size = total_size;
+		off = start_fit(tmp, sizeof(tmp), 0, false);
+		if (!off)
+			return 0;
 	}
 
-	return count;
+	if (data_offset)
+		*data_offset = off;
+
+	if (!dst)
+		goto out;
+
+	if (start_fit(dst, size, spl_image->size, external) != off)
+		return 0;
+
+	if (fdt_property_string(dst, FIT_DESC_PROP, spl_image->name))
+		return 0;
+	if (fdt_property_string(dst, FIT_TYPE_PROP, "firmware"))
+		return 0;
+	if (fdt_property_string(dst, FIT_COMP_PROP, "none"))
+		return 0;
+	if (fdt_property_u32(dst, FIT_DATA_SIZE_PROP, spl_image->size))
+		return 0;
+	if (fdt_property_string(dst, FIT_OS_PROP,
+				genimg_get_os_short_name(spl_image->os)))
+		return 0;
+	if (fdt_property_string(dst, FIT_ARCH_PROP,
+				genimg_get_arch_short_name(IH_ARCH_DEFAULT)))
+		return 0;
+	if (fdt_property_addr(dst, FIT_ENTRY_PROP, spl_image->entry_point))
+		return 0;
+	if (fdt_property_addr(dst, FIT_LOAD_PROP, spl_image->load_addr))
+		return 0;
+	if (fdt_end_node(dst)) /* u-boot */
+		return 0;
+	if (fdt_end_node(dst)) /* images */
+		return 0;
+
+	if (fdt_begin_node(dst, "configurations"))
+		return 0;
+	if (fdt_property_string(dst, FIT_DEFAULT_PROP, "config-1"))
+		return 0;
+	if (fdt_begin_node(dst, "config-1"))
+		return 0;
+	if (fdt_property_string(dst, FIT_DESC_PROP, spl_image->name))
+		return 0;
+	if (fdt_property_string(dst, FIT_FIRMWARE_PROP, "u-boot"))
+		return 0;
+	if (fdt_end_node(dst)) /* configurations */
+		return 0;
+	if (fdt_end_node(dst)) /* config-1 */
+		return 0;
+
+	if (fdt_end_node(dst)) /* root */
+		return 0;
+	if (fdt_finish(dst))
+		return 0;
+
+	if (external) {
+		if (fdt_totalsize(dst) > size)
+			return 0;
+		fdt_set_totalsize(dst, size);
+	}
+
+out:
+	return total_size;
 }
 
-int board_fit_config_name_match(const char *name)
+size_t create_image(void *dst, enum spl_test_image type,
+		    struct spl_image_info *info, size_t *data_offset)
 {
+	bool external = false;
+
+	info->os = IH_OS_U_BOOT;
+	info->load_addr = CONFIG_TEXT_BASE;
+	info->entry_point = CONFIG_TEXT_BASE + 0x100;
+	info->flags = 0;
+
+	switch (type) {
+	case LEGACY_LZMA:
+		info->flags = SPL_COMP_LZMA;
+	case LEGACY:
+		return create_legacy(dst, info, data_offset);
+	case IMX8:
+		info->flags = SPL_IMX_CONTAINER;
+		return create_imx8(dst, info, data_offset);
+	case FIT_EXTERNAL:
+		/*
+		 * spl_fit_append_fdt will clobber external images with U-Boot's
+		 * FDT if the image doesn't have one. Just set the OS to
+		 * something which doesn't take a devicetree.
+		 */
+		if (!IS_ENABLED(CONFIG_LOAD_FIT_FULL))
+			info->os = IH_OS_TEE;
+		external = true;
+	case FIT_INTERNAL:
+		info->flags = SPL_FIT_FOUND;
+		return create_fit(dst, info, data_offset, external);
+	}
+
 	return 0;
 }
 
-struct legacy_img_hdr *spl_get_load_buffer(ssize_t offset, size_t size)
+int check_image_info(struct unit_test_state *uts, struct spl_image_info *info1,
+		     struct spl_image_info *info2)
 {
-	return map_sysmem(0x100000, 0);
+	if (info2->name) {
+		if (info1->flags & SPL_FIT_FOUND)
+			ut_asserteq_str(genimg_get_os_name(info1->os),
+					info2->name);
+		else
+			ut_asserteq_str(info1->name, info2->name);
+	}
+
+	if (info1->flags & SPL_IMX_CONTAINER)
+		ut_asserteq(IH_OS_INVALID, info2->os);
+	else
+		ut_asserteq(info1->os, info2->os);
+
+	ut_asserteq(info1->entry_point, info2->entry_point);
+	if (info1->flags & (SPL_FIT_FOUND | SPL_IMX_CONTAINER) ||
+	    info2->flags & SPL_COPY_PAYLOAD_ONLY) {
+		ut_asserteq(info1->load_addr, info2->load_addr);
+		if (info1->flags & SPL_IMX_CONTAINER)
+			ut_asserteq(0, info2->size);
+		else if (!(info1->flags & SPL_COMP_LZMA))
+			ut_asserteq(info1->size, info2->size);
+	} else {
+		ut_asserteq(info1->load_addr - sizeof(struct legacy_img_hdr),
+			    info2->load_addr);
+		ut_asserteq(info1->size + sizeof(struct legacy_img_hdr),
+			    info2->size);
+	}
+
+	return 0;
 }
 
-static int spl_test_load(struct unit_test_state *uts)
+static ulong spl_test_read(struct spl_load_info *load, ulong sector,
+			   ulong count, void *buf)
 {
-	struct spl_image_info image;
-	struct legacy_img_hdr *header;
-	struct text_ctx text_ctx;
-	struct spl_load_info load;
-	char fname[256];
-	int ret;
-	int fd;
+	memcpy(buf, load->priv + sector, count);
+	return count;
+}
 
-	memset(&load, '\0', sizeof(load));
-	load.bl_len = 512;
-	load.read = read_fit_image;
+static int spl_test_image(struct unit_test_state *uts, const char *test_name,
+			  enum spl_test_image type)
+{
+	size_t img_size, img_data, data_size = SPL_TEST_DATA_SIZE;
+	struct spl_image_info info_write = {
+		.name = test_name,
+		.size = data_size,
+	}, info_read = { };
+	char *data;
+	void *img;
+
+	img_size = create_image(NULL, type, &info_write, &img_data);
+	ut_assert(img_size);
+	img = calloc(img_size, 1);
+	ut_assertnonnull(img);
+
+	data = img + img_data;
+	generate_data(data, data_size, test_name);
+	ut_asserteq(img_size, create_image(img, type, &info_write, NULL));
+
+	if (type == LEGACY) {
+		ut_assertok(spl_parse_image_header(&info_read, NULL, img));
+		if (check_image_info(uts, &info_write, &info_read))
+			return CMD_RET_FAILURE;
+	} else {
+		struct spl_load_info load = {
+			.bl_len = 1,
+			.priv = img,
+			.read = spl_test_read,
+		};
 
-	ret = sandbox_find_next_phase(fname, sizeof(fname), true);
-	if (ret) {
-		printf("(%s not found, error %d)\n", fname, ret);
-		return ret;
+		if (type == IMX8)
+			ut_assertok(spl_load_imx_container(&info_read, &load,
+							   0));
+		else if (IS_ENABLED(CONFIG_SPL_FIT_FULL))
+			ut_assertok(spl_parse_image_header(&info_read, NULL,
+							   img));
+		else
+			ut_assertok(spl_load_simple_fit(&info_read, &load, 0,
+							img));
+		if (check_image_info(uts, &info_write, &info_read))
+			return CMD_RET_FAILURE;
+		ut_asserteq_mem(data, phys_to_virt(info_write.load_addr),
+				data_size);
 	}
-	load.filename = fname;
 
-	header = spl_get_load_buffer(-sizeof(*header), sizeof(*header));
+	free(img);
+	return 0;
+}
+SPL_IMG_TEST(spl_test_image, LEGACY, 0);
+SPL_IMG_TEST(spl_test_image, IMX8, 0);
+SPL_IMG_TEST(spl_test_image, FIT_INTERNAL, 0);
+SPL_IMG_TEST(spl_test_image, FIT_EXTERNAL, 0);
 
-	fd = os_open(fname, OS_O_RDONLY);
-	ut_assert(fd >= 0);
-	ut_asserteq(512, os_read(fd, header, 512));
-	text_ctx.fd = fd;
+/*
+ * LZMA is too complex to generate on the fly, so let's use some data I put in
+ * the oven^H^H^H^H compressed earlier
+ */
+static const char lzma_compressed[] = {
+	0x5d, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0x00, 0x02, 0x05, 0x55, 0x4e, 0x82, 0xbc, 0xc2, 0x42, 0xf6, 0x88,
+	0x6c, 0x99, 0xd6, 0x82, 0x48, 0xa6, 0x06, 0x67, 0xf8, 0x46, 0x7c, 0xe9,
+	0x41, 0x79, 0xfe, 0x90, 0x0b, 0x31, 0x7b, 0x79, 0x91, 0xb8, 0x5f, 0x33,
+	0x11, 0x04, 0xc3, 0x4f, 0xf5, 0x71, 0xd1, 0xfb, 0x94, 0x6b, 0x5f, 0x78,
+	0xe2, 0xfa, 0x6a, 0x21, 0xb6, 0x1d, 0x11, 0x0e, 0x5b, 0x56, 0x6a, 0x5b,
+	0xe9, 0x56, 0x5f, 0x8b, 0x87, 0x61, 0x96, 0x6d, 0xce, 0x66, 0xbb, 0xb6,
+	0xe7, 0x13, 0x5a, 0xd8, 0x84, 0x29, 0x60, 0xa0, 0x80, 0x43, 0xdd, 0x0f,
+	0x4b, 0x85, 0xb0, 0x04, 0x9d, 0x9f, 0x28, 0x97, 0x0a, 0x1e, 0x16, 0xb0,
+	0x45, 0x33, 0x5e, 0x79, 0x4f, 0xaa, 0xee, 0x79, 0x6e, 0xc3, 0x4e, 0x3d,
+	0xe8, 0x67, 0x7c, 0xe0, 0xd0, 0xcc, 0x05, 0x40, 0xae, 0x6b, 0x97, 0x82,
+	0x97, 0x02, 0x01, 0xe2, 0xe3, 0xbc, 0xe4, 0x9b, 0xb3, 0x28, 0xed, 0x5e,
+	0x0d, 0x68, 0x6e, 0xe5, 0x17, 0x0a, 0x86, 0x5a, 0xcd, 0x8d, 0x46, 0x2d,
+	0x06, 0x10, 0xa6, 0x90, 0x44, 0xa1, 0xfc, 0x66, 0x6d, 0x7c, 0x57, 0x57,
+	0x07, 0xbc, 0x95, 0xb2, 0x8d, 0xf0, 0x9f, 0x4d, 0x90, 0x04, 0xaf, 0x0c,
+	0x23, 0x51, 0x1b, 0x34, 0xd5, 0x5c, 0x5d, 0x87, 0x5e, 0x10, 0x2b, 0x71,
+	0xc2, 0xcf, 0xc5, 0x9d, 0x4b, 0x89, 0x01, 0xc4, 0x97, 0xf2, 0xea, 0x83,
+	0x97, 0xfa, 0xe0, 0x51, 0x96, 0x78, 0x4f, 0x44, 0xb8, 0xa8, 0x9d, 0x03,
+	0x1c, 0x6e, 0xb7, 0xc6, 0xd7, 0xc5, 0x3e, 0x32, 0x65, 0xa7, 0x06, 0xab,
+	0x86, 0xfb, 0xd2, 0x9b, 0xd7, 0x86, 0xa8, 0xfe, 0x46, 0x41, 0x2e, 0xc2,
+	0x4e, 0xed, 0xa2, 0x9b, 0x79, 0x36, 0x37, 0x49, 0x90, 0xfc, 0xa6, 0x14,
+	0x93, 0x17, 0x82, 0x62, 0x3f, 0x79, 0x6b, 0x86, 0xc2, 0xeb, 0x82, 0xfe,
+	0x87, 0x49, 0xa5, 0x7e, 0x41, 0xe3, 0x59, 0x60, 0x15, 0x61, 0x4e, 0x3b,
+	0x16, 0xcf, 0xdb, 0x49, 0x2c, 0x84, 0x92, 0x26, 0x40, 0x04, 0x78, 0xd3,
+	0xd6, 0xa6, 0xed, 0x6e, 0x63, 0x49, 0xcb, 0xea, 0xfe, 0x43, 0x85, 0x21,
+	0x1a, 0x28, 0x36, 0x0a, 0x3e, 0x2a, 0xad, 0xba, 0xfc, 0x8a, 0x37, 0x18,
+	0xb4, 0x80, 0xbe, 0x6a, 0x36, 0x14, 0x03, 0xdd, 0xa3, 0x37, 0xbd, 0xc1,
+	0x8a, 0xbb, 0x2d, 0xd4, 0x08, 0xd7, 0x4b, 0xc4, 0xe9, 0xb8, 0xb4, 0x65,
+	0xdd, 0xf6, 0xe8, 0x17, 0x2c, 0x2c, 0x9b, 0x1e, 0x92, 0x0b, 0xcb, 0x22,
+	0x7c, 0x1b, 0x74, 0x8d, 0x65, 0x11, 0x5f, 0xfe, 0xf5, 0x2a, 0xc2, 0xbe,
+	0xea, 0xa2, 0xf1, 0x7b, 0xe8, 0xaf, 0x32, 0x5a, 0x0a, 0x5b, 0xd2, 0x5a,
+	0x11, 0x22, 0x79, 0xfa, 0xae, 0x2d, 0xe8, 0xc6, 0x17, 0xba, 0x17, 0x81,
+	0x6a, 0x63, 0xb5, 0x26, 0xd7, 0x8d, 0xd0, 0x66, 0x0c, 0x4a, 0x0c, 0x22,
+	0x1b, 0x20, 0x9f, 0x3d, 0x0b, 0x1b, 0x59, 0x53, 0x89, 0x9b, 0x5e, 0xbd,
+	0x3d, 0xd1, 0xdd, 0xff, 0xca, 0xb2, 0xb7, 0x12, 0x8d, 0x03, 0xaa, 0xc3,
+	0x1d, 0x56, 0x76, 0x14, 0xf8, 0xee, 0xb3, 0xeb, 0x80, 0x38, 0xc1, 0xc1,
+	0x1a, 0xef, 0x4a, 0xd5, 0x16, 0x1f, 0x5e, 0x21, 0x5d, 0x46, 0x01, 0xb3,
+	0xa4, 0xf7, 0x99, 0x94, 0x05, 0xc6, 0xc8, 0x06, 0xd8, 0x1c, 0xac, 0x47,
+	0x13, 0x54, 0x13, 0x1b, 0x1f, 0xb6, 0x23, 0x9c, 0x73, 0x2b, 0x57, 0x32,
+	0x94, 0x92, 0xf1, 0x71, 0x44, 0x40, 0x02, 0xc3, 0x21, 0x4a, 0x2f, 0x36,
+	0x5e, 0x8a, 0xd0, 0x4b, 0x02, 0xc7, 0x6e, 0xcf, 0xed, 0xa2, 0xdb, 0xce,
+	0x0a, 0x0f, 0x66, 0x4f, 0xb2, 0x3d, 0xb6, 0xcc, 0x75, 0x45, 0x80, 0x0a,
+	0x49, 0x4a, 0xe7, 0xe7, 0x24, 0x62, 0x65, 0xc7, 0x02, 0x22, 0x13, 0xbe,
+	0x6c, 0xa9, 0x9a, 0x8b, 0xa9, 0x1b, 0x2b, 0x3a, 0xde, 0x5e, 0x37, 0xbd,
+	0x7f, 0x85, 0xd1, 0x32, 0x1d, 0xbf, 0x03, 0x8a, 0x3b, 0xe5, 0xb3, 0xfd,
+	0x01, 0xca, 0xde, 0x0d, 0x7a, 0x5b, 0x01, 0x05, 0x1d, 0x3c, 0x23, 0x00,
+	0x60, 0xb7, 0x50, 0xfd, 0x0d, 0xd7, 0x63, 0x92, 0xd6, 0xb0, 0x48, 0x3a,
+	0x2d, 0xa3, 0xf8, 0xf6, 0x44, 0xe1, 0xda, 0x3b, 0xf4, 0x39, 0x47, 0xc4,
+	0x4d, 0x8f, 0x54, 0x78, 0xec, 0x27, 0x7b, 0xc6, 0xe4, 0x81, 0x3a, 0x3f,
+	0xa5, 0x61, 0x9d, 0xcb, 0x71, 0x0b, 0x0d, 0x55, 0xea, 0x5b, 0xeb, 0x58,
+	0xa5, 0x49, 0xb5, 0x44, 0x1b, 0xb0, 0x0d, 0x1f, 0x58, 0xfb, 0x7a, 0xd4,
+	0x09, 0x1e, 0x9a, 0x7e, 0x21, 0xba, 0xb3, 0x36, 0xa6, 0x04, 0x74, 0xe1,
+	0xd0, 0xca, 0x02, 0x11, 0x84, 0x93, 0x8f, 0x86, 0x3d, 0x79, 0xbf, 0xa8,
+	0xec, 0x0a, 0x23, 0x5e, 0xde, 0xc4, 0xc6, 0xda, 0x45, 0xbd, 0x95, 0x74,
+	0x7b, 0xbf, 0xc1, 0x80, 0x48, 0x3f, 0x10, 0xb6, 0xb9, 0x5c, 0x31, 0x52,
+	0x06, 0x5a, 0xac, 0xec, 0x94, 0x21, 0x80, 0x51, 0xba, 0x64, 0xed, 0x9d,
+	0x27, 0x72, 0x8d, 0x17, 0x43, 0x5f, 0xf1, 0x60, 0xfa, 0xb5, 0x65, 0xd4,
+	0xb9, 0xf8, 0xfc, 0x48, 0x7b, 0xe3, 0xfe, 0xae, 0xe4, 0x71, 0x4a, 0x3d,
+	0x8c, 0xf5, 0x72, 0x8b, 0xbf, 0x60, 0xd8, 0x6a, 0x8f, 0x51, 0x82, 0xae,
+	0x98, 0xd0, 0x56, 0xf9, 0xa8, 0x3a, 0xad, 0x86, 0x26, 0xa8, 0x5a, 0xf8,
+	0x63, 0x87, 0x2c, 0x74, 0xbf, 0xf9, 0x7d, 0x00, 0xa0, 0x2f, 0x17, 0x23,
+	0xb7, 0x62, 0x94, 0x19, 0x47, 0x57, 0xf9, 0xa8, 0xe7, 0x4b, 0xe9, 0x2b,
+	0xe8, 0xb4, 0x03, 0xbf, 0x23, 0x75, 0xfe, 0xc3, 0x94, 0xc0, 0xa9, 0x5b,
+	0x07, 0xb5, 0x75, 0x87, 0xcc, 0xa5, 0xb5, 0x9b, 0x35, 0x29, 0xe4, 0xb1,
+	0xaa, 0x04, 0x57, 0xe9, 0xa3, 0xd0, 0xa3, 0xe4, 0x11, 0xe1, 0xaa, 0x3b,
+	0x67, 0x09, 0x60, 0x83, 0x23, 0x72, 0xa6, 0x7b, 0x73, 0x22, 0x5b, 0x4a,
+	0xe0, 0xf0, 0xa3, 0xeb, 0x9c, 0x91, 0xda, 0xba, 0x8b, 0xc1, 0x32, 0xa9,
+	0x24, 0x13, 0x51, 0xe4, 0x67, 0x49, 0x4a, 0xd9, 0x3d, 0xae, 0x80, 0xfd,
+	0x0a, 0x0d, 0x56, 0x98, 0x66, 0xa2, 0x6d, 0x92, 0x54, 0x7f, 0x82, 0xe5,
+	0x17, 0x39, 0xd3, 0xaa, 0xc4, 0x4e, 0x6f, 0xe1, 0x2e, 0xfe, 0x03, 0x44,
+	0x8a, 0xdd, 0xeb, 0xc0, 0x74, 0x79, 0x63, 0x33, 0x2b, 0x4b, 0xb5, 0x62,
+	0xdd, 0x47, 0xba, 0x6e, 0xfc, 0x91, 0x08, 0xa9, 0x17, 0x8c, 0x47, 0x61,
+	0xd9, 0x32, 0xe9, 0xa0, 0xb3, 0xa2, 0x82, 0xc9, 0xa6, 0x32, 0xa1, 0xca,
+	0x7c, 0x41, 0xa6, 0x5a, 0xe2, 0x46, 0xb6, 0x45, 0x53, 0x72, 0x55, 0x9e,
+	0xdf, 0xac, 0x96, 0x68, 0xe5, 0xdc, 0x4e, 0x2d, 0xa8, 0x1e, 0x7a, 0x8e,
+	0xff, 0x54, 0xe4, 0x0a, 0x33, 0x5d, 0x97, 0xdf, 0x4e, 0x36, 0x96, 0xba,
+	0x52, 0xd9, 0xa9, 0xec, 0x52, 0xe5, 0x1d, 0x94, 0xfe, 0x1c, 0x46, 0x54,
+	0xa6, 0x8e, 0x85, 0x47, 0xba, 0xeb, 0x4b, 0x8d, 0x57, 0xe4, 0x34, 0x24,
+	0x9e, 0x80, 0xb5, 0xc9, 0xa9, 0x94, 0x1d, 0xe4, 0x18, 0xb6, 0x07, 0x1e,
+	0xfa, 0xe0, 0x1c, 0x88, 0x06, 0x84, 0xaa, 0xcb, 0x5e, 0xfa, 0x15, 0x5a,
+	0xdd, 0x10, 0x43, 0x81, 0xf2, 0x50, 0x3e, 0x93, 0x26, 0x77, 0x1c, 0x77,
+	0xe9, 0x0c, 0xfc, 0x5f, 0xdd, 0x67, 0x31, 0x02, 0xc6, 0xdd, 0xf4, 0x30,
+	0x76, 0x51, 0xce, 0x56, 0xba, 0x7f, 0x44, 0xbd, 0x42, 0x9f, 0x10, 0x8c,
+	0x56, 0x49, 0x48, 0xa2, 0xcb, 0xc4, 0xdd, 0x29, 0xae, 0xf0, 0x33, 0x35,
+	0x46, 0x69, 0x1d, 0xae, 0xde, 0xde, 0x98, 0x82, 0x79, 0xa6, 0x50, 0x28,
+	0xb3, 0x5f, 0x10, 0x24, 0x63, 0xee, 0x9a, 0x22, 0xbe, 0xf8, 0x3a, 0xf4,
+	0xab, 0x98, 0xfe, 0xdf, 0x30, 0x03, 0xe8, 0x45, 0x8c, 0xf4, 0x85, 0xc6,
+	0x98, 0x7b, 0x35, 0xb8, 0x30, 0x9c, 0x15, 0xa6, 0x45, 0xbd, 0x39, 0x84,
+	0xe7, 0x43, 0x4b, 0x05, 0xa4, 0x8f, 0x52, 0x8e, 0x4a, 0xe4, 0x87, 0xc1,
+	0xdc, 0xdf, 0x25, 0x9c, 0x5c, 0x37, 0xd0, 0x66, 0x12, 0x41, 0x66, 0x8c,
+	0x28, 0xd0, 0x3f, 0x5c, 0x7f, 0x15, 0x9b, 0xcf, 0xa0, 0xae, 0x29, 0x33,
+	0xb0, 0xe4, 0xb7, 0x36, 0x2a, 0x45, 0x83, 0xff, 0x86, 0x75, 0xcf, 0xa7,
+	0x4d, 0x5c, 0xa8, 0xcf, 0x3f, 0xf2, 0xc8, 0xde, 0xdd, 0xad, 0x42, 0x8f,
+	0x0e, 0xd0, 0x11, 0x24, 0x42, 0x86, 0x51, 0x52, 0x76, 0x21, 0x68, 0xf1,
+	0xa7, 0x8f, 0xdb, 0x5b, 0x78, 0xfa, 0x44, 0x5f, 0xee, 0x31, 0xda, 0x62,
+	0x5f, 0xfe, 0x69, 0xae, 0x97, 0xc9, 0xb5, 0x04, 0x76, 0x79, 0x2e, 0xb9,
+	0xd9, 0x1b, 0xdd, 0xb7, 0xc4, 0x12, 0x78, 0xb2, 0x4d, 0xab, 0xd2, 0x29,
+	0x25, 0x8c, 0xd5, 0x52, 0x4a, 0xd7, 0x2e, 0x18, 0x9d, 0xa2, 0xee, 0x7b,
+	0xa5, 0xe5, 0x35, 0x3c, 0xb5, 0x54, 0x1c, 0x7f, 0x87, 0x4b, 0xc0, 0xbb,
+	0x1a, 0x85, 0x19, 0xc0, 0xa9, 0x2b, 0x4d, 0xed, 0x71, 0xc0, 0x15, 0xb3,
+	0x49, 0x2c, 0x46, 0xfc, 0x37, 0x40, 0xc0, 0x60, 0xd0, 0x00, 0x96, 0xfa,
+	0x7f, 0xbb, 0x30, 0x94, 0x6b, 0x81, 0x61, 0xc5, 0x13, 0x93, 0x95, 0xaa,
+	0xf3, 0x8d, 0x1d, 0xac, 0xdb, 0xbd, 0xc3, 0x90, 0xf3, 0xd2, 0x5f, 0x3a,
+	0x08, 0xb1, 0xc9, 0x3a, 0xe8, 0x25, 0x4d, 0x20, 0x2a, 0xe9, 0x4c, 0xaf,
+	0x9b, 0x54, 0x7b, 0xaf, 0x89, 0x44, 0x3a, 0x60, 0x23, 0xd3, 0x02, 0xb1,
+	0xb3, 0x9a, 0x3a, 0xb0, 0xa0, 0xdb, 0x61, 0x0b, 0xac, 0x55, 0xa1, 0x36,
+	0x55, 0x5b, 0xc4, 0xc5, 0xbd, 0x2a, 0x16, 0xe9, 0xe7, 0x86, 0x7f, 0xdb,
+	0xee, 0x90, 0xfa, 0xfd, 0x08, 0x7f, 0x1a, 0x43, 0xe0, 0xb8, 0x21, 0xb3,
+	0xe3, 0xdf, 0x27, 0x56, 0x61, 0xc4, 0xe8, 0xd5, 0x60, 0xe9, 0x6d, 0x49,
+	0xd9, 0xa8, 0xf5, 0xd9, 0xfc, 0x66, 0x82, 0xe9, 0x80, 0x5b, 0x85, 0x16,
+	0x55, 0x2b, 0xef, 0x50, 0x90, 0x6c, 0x5d, 0x81, 0x00, 0x00, 0x88, 0x9b,
+	0xb4, 0x62, 0x49, 0x46, 0x2e, 0x5d, 0x71, 0x95, 0xff, 0x63, 0xfb, 0x93,
+	0x23, 0xf8, 0x9f, 0xa2, 0x55, 0x56, 0xd4, 0xd5, 0xf7, 0xae, 0xaf, 0xd3,
+	0xf6, 0x82, 0xc8, 0xdd, 0x89, 0x0f, 0x7e, 0x89, 0x0d, 0x0d, 0x7f, 0x4f,
+	0x84, 0xa7, 0x16, 0xe8, 0xaf, 0xf2, 0x95, 0xd7, 0xc3, 0x66, 0xd6, 0x85,
+	0x5b, 0xa1, 0xbb, 0xea, 0x31, 0x02, 0xac, 0xa2, 0x7b, 0x50, 0xf4, 0x78,
+	0x29, 0x49, 0x59, 0xf6, 0x41, 0x42, 0x52, 0xa8, 0x19, 0xfb, 0x3d, 0xda,
+	0xa9, 0x8d, 0xac, 0xe1, 0x25, 0xd4, 0x12, 0x1e, 0x2b, 0x48, 0x44, 0xb0,
+	0xf6, 0x29, 0xd0, 0x55, 0x22, 0xb4, 0xe7, 0xbc, 0x22, 0x97, 0x1f, 0xe2,
+	0xe1, 0x73, 0x16, 0x13, 0x7a, 0x00, 0x62, 0x14, 0xcb, 0x25, 0x9b, 0x21,
+	0x98, 0x9d, 0xb8, 0xd8, 0xf4, 0x65, 0xf6, 0x8f, 0x39, 0xe4, 0x76, 0xf7,
+	0x30, 0xaf, 0xbc, 0x3a, 0xfe, 0x0e, 0xf1, 0x81, 0xa7, 0xff, 0x4d, 0xa7,
+	0xff, 0xbf, 0x15, 0x60, 0x0b, 0xcd, 0x69, 0xd5, 0x77, 0xba, 0xcb, 0x7b,
+	0x5a, 0xfb, 0x34, 0xc7, 0x5d, 0x13, 0x33, 0xd7, 0x86, 0x02, 0x43, 0x57,
+	0x52, 0x2c, 0x74, 0x61, 0x21, 0xa3, 0x34, 0xf5, 0x89, 0x51, 0x44, 0x89,
+	0xfc, 0xbb, 0x57, 0x5c, 0x6d, 0xb0, 0x2e, 0x8c, 0xff, 0x73, 0xe5, 0x09,
+	0x13, 0x3b, 0x45, 0x5b, 0x27, 0x88, 0xee, 0x9b, 0xab, 0x57, 0x7c, 0x9b,
+	0xb9, 0x78, 0x73, 0xd2, 0x2d, 0x98, 0x6f, 0xd2, 0x78, 0xb3, 0xeb, 0xaa,
+	0x18, 0x44, 0x87, 0x6d, 0x51, 0x1e, 0x9b, 0x73, 0xaa, 0x91, 0x1a, 0x4f,
+	0x69, 0x78, 0xef, 0x3f, 0xb1, 0x2d, 0x39, 0x3e, 0xda, 0x31, 0xfc, 0x99,
+	0xf6, 0xa2, 0x8c, 0xe5, 0xfd, 0x97, 0x95, 0x77, 0x37, 0xef, 0xf5, 0xd1,
+	0xc8, 0x74, 0x2c, 0x9a, 0x1f, 0x23, 0x8f, 0x72, 0x96, 0x3d, 0xb5, 0xad,
+	0x28, 0xa0, 0x6c, 0x66, 0xe8, 0xee, 0xaa, 0x9d, 0xc2, 0x8a, 0x56, 0x54,
+	0x89, 0x74, 0x56, 0xdc, 0x57, 0x49, 0xc3, 0x8e, 0xb9, 0x3a, 0x91, 0x34,
+	0xc4, 0x5e, 0x0b, 0x13, 0x63, 0x5e, 0xeb, 0xc5, 0xef, 0xc7, 0xe9, 0x7f,
+	0x27, 0xe8, 0xe7, 0xe5, 0x0d, 0x83, 0x95, 0x5f, 0x8a, 0xf2, 0xb2, 0x22,
+	0x03, 0x8d, 0x71, 0x4f, 0x62, 0xb7, 0xf1, 0x87, 0xf5, 0x3f, 0xc4, 0x23,
+	0x21, 0x40, 0x35, 0xcf, 0x79, 0x7a, 0x5b, 0x9d, 0x76, 0xb2, 0xdc, 0x6a,
+	0xb5, 0x1d, 0x8b, 0xb6, 0x9a, 0x19, 0xe4, 0x87, 0xf5, 0xce, 0x38, 0xf3,
+	0x70, 0xbf, 0x9e, 0x86, 0xa6, 0x07, 0x53, 0xdd, 0x5d, 0xc7, 0x72, 0x84,
+	0x47, 0x38, 0xd0, 0xe2, 0xeb, 0x64, 0x4c, 0x3a, 0x1e, 0xf6, 0x56, 0x79,
+	0x75, 0x75, 0x14, 0x5d, 0xe4, 0x1d, 0x9d, 0xbb, 0xe1, 0x35, 0x03, 0x5e,
+	0x4f, 0x8f, 0xea, 0x95, 0xde, 0x19, 0x57, 0x98, 0xe9, 0x2c, 0x42, 0x22,
+	0xcb, 0x0f, 0x15, 0x7a, 0x6b, 0x53, 0xc3, 0xec, 0xdc, 0xa0, 0x66, 0x26,
+	0x91, 0x04, 0x83, 0x75, 0x09, 0x0c, 0x22, 0x05, 0xec, 0x3a, 0x2d, 0x39,
+	0xea, 0x19, 0xf2, 0x1d, 0xdb, 0xba, 0x5c, 0x46, 0x47, 0xd4, 0x94, 0x6d,
+	0x51, 0xdb, 0x68, 0xde, 0x0c, 0xa0, 0x36, 0x8f, 0xbc, 0xfd, 0x9b, 0x8f,
+	0xfe, 0x04, 0x1f, 0xde, 0x1e, 0x77, 0xb5, 0x80, 0xb9, 0x9c, 0x1b, 0x24,
+	0x61, 0xfc, 0x2b, 0xc0, 0x42, 0x2b, 0xc5, 0x90, 0x58, 0xa2, 0xb1, 0x38,
+	0x58, 0xf2, 0x8b, 0x65, 0xbf, 0xe8, 0xe6, 0x79, 0xcf, 0x65, 0x35, 0xa5,
+	0xe1, 0xb7, 0x8b, 0x95, 0x54, 0xd7, 0x1d, 0xf0, 0x91, 0x18, 0xc0, 0x5d,
+	0x2c, 0xb5, 0xca, 0x1a, 0x7f, 0x8d, 0xfb, 0x9e, 0x57, 0x1c, 0x5c, 0xf0,
+	0x94, 0x36, 0x51, 0x95, 0x27, 0x62, 0xca, 0x92, 0x96, 0xe5, 0x00, 0x2e,
+	0xa4, 0x41, 0x97, 0xbf, 0x28, 0x3c, 0x6d, 0xc1, 0xb7, 0xe9, 0x1c, 0x2e,
+	0x3e, 0xe0, 0x5e, 0x89, 0x0c, 0x78, 0x88, 0x80, 0xb8, 0x30, 0xd2, 0x22,
+	0xf9, 0x71, 0xb4, 0xc8, 0xee, 0xe6, 0x80, 0x04, 0x04, 0x9a, 0xfb, 0x0c,
+	0x36, 0xcb, 0xea, 0x66, 0xf9, 0x52, 0x8c, 0x66, 0xbf, 0x4c, 0x0f, 0xf4,
+	0xf8, 0x1e, 0x7e, 0x39, 0x80, 0xe8, 0x82, 0x4b, 0x0e, 0x66, 0x1d, 0x51,
+	0x16, 0xa9, 0x8d, 0xd6, 0xea, 0x33, 0xb0, 0x2c, 0x36, 0x25, 0xf5, 0x01,
+	0x30, 0x7e, 0x03, 0x7f, 0xae, 0x8e, 0xd6, 0x25, 0x62, 0x6d, 0x99, 0x8c,
+	0x1f, 0xc1, 0x22, 0xf0, 0x94, 0x80, 0xbf, 0x82, 0x51, 0xea, 0xc2, 0x5a,
+	0x3c, 0x85, 0x2a, 0x5d, 0xbe, 0xae, 0xe1, 0xe3, 0x07, 0x92, 0xd2, 0x40,
+	0x47, 0xe8, 0x0f, 0x1a, 0xa5, 0x73, 0x64, 0x26, 0xc4, 0xac, 0xca, 0xc2,
+	0x83, 0x5a, 0x56, 0xbc, 0x81, 0x21, 0xcb, 0x72, 0xf3, 0xe7, 0x82, 0x1e,
+	0xc8, 0x54, 0x18, 0x42, 0xfe, 0xd6, 0xfc, 0x96, 0x0e, 0x03, 0x29, 0x98,
+	0x4f, 0xd1, 0xd2, 0x98, 0x7c, 0x9e, 0x4e, 0x1a, 0x0f, 0xd6, 0x4e, 0xa4,
+	0x52, 0x1b, 0xd1, 0xd8, 0x36, 0xf7, 0x47, 0x5f, 0xce, 0xcb, 0x87, 0x36,
+	0xc8, 0x9b, 0x44, 0xc6, 0x7a, 0xf3, 0x45, 0x28, 0xae, 0x96, 0x5a, 0x85,
+	0x62, 0x8b, 0x10, 0xc2, 0x7b, 0x39, 0x51, 0xdf, 0xf4, 0x21, 0xc2, 0x6b,
+	0x6f, 0x93, 0x27, 0xed, 0xf6, 0xea, 0xff, 0x2a, 0x21, 0x70, 0x84, 0x4e,
+	0x21, 0xac, 0xbc, 0x06, 0x41, 0xd3, 0x59, 0xa0, 0xa1, 0x50, 0xa6, 0x87,
+	0xa2, 0x48, 0xad, 0x94, 0x44, 0x8d, 0x2f, 0xa8, 0xc6, 0x10, 0xb5, 0xeb,
+	0x66, 0x82, 0x94, 0x5f, 0xae, 0x6a, 0x56, 0xb4, 0x8d, 0xf4, 0x62, 0x80,
+	0xe4, 0x42, 0xc4, 0xbc, 0xe7, 0xee, 0xa6, 0x96, 0x3b, 0xfd, 0xc0, 0x92,
+	0x7d, 0xcd, 0xe7, 0x0c, 0x99, 0x9a, 0xb6, 0x83, 0xcf, 0x45, 0xe5, 0x74,
+	0xb3, 0xbc, 0xc0, 0x40, 0xad, 0x4d, 0xfc, 0xa7, 0x92, 0x35, 0x13, 0x81,
+	0x5c, 0x9c, 0x21, 0x00, 0xa4, 0x37, 0x07, 0x1d, 0x19, 0xfc, 0x88, 0x4d,
+	0x71, 0x43, 0x7d, 0x94, 0xf7, 0x32, 0xb8, 0x4b, 0x8a, 0x54, 0xd6, 0xe4,
+	0x37, 0x4f, 0x27, 0x1f, 0xfd, 0x45, 0x83, 0xb9, 0x14, 0x5a, 0xf7, 0x36,
+	0xdc, 0x98, 0xad, 0x99, 0xb9, 0x38, 0x69, 0xac, 0x18, 0x7e, 0x47, 0xd0,
+	0x63, 0x27, 0xba, 0xe7, 0xd5, 0x1d, 0x7b, 0x6e, 0xde, 0x28, 0x7b, 0xf1,
+	0x84, 0x4d, 0x2d, 0x7c, 0x16, 0x38, 0x4b, 0x16, 0xa9, 0x10, 0x83, 0xfb,
+	0xe0, 0xe0, 0x6f, 0xdd, 0x03, 0x0a, 0xb8, 0x81, 0xf5, 0x8c, 0x98, 0xc3,
+	0xf4, 0xc8, 0x31, 0x3a, 0xed, 0x14, 0x83, 0x89, 0xc3, 0x0e, 0xf7, 0xba,
+	0x84, 0xb0, 0x49, 0xdf, 0xc6, 0x6b, 0xed, 0xbe, 0xd4, 0xa3, 0x83, 0x3a,
+	0xe6, 0x6d, 0xa3, 0x83, 0x17, 0x43, 0x5e, 0x3a, 0x83, 0xda, 0x81, 0xe3,
+	0x26, 0x95, 0x6b, 0xe5, 0x30, 0x28, 0x6d, 0xec, 0xd7, 0xd7, 0x35, 0xfa,
+	0x1a, 0xad, 0x86, 0x04, 0x05, 0x2c, 0x76, 0x3f, 0xb2, 0x83, 0x92, 0x4e,
+	0xef, 0x05, 0xde, 0x13, 0x26, 0x68, 0x80, 0x57, 0xee, 0x92, 0x80, 0xa3,
+	0x99, 0xb4, 0xac, 0x98, 0x31, 0xd4, 0xf3, 0xe2, 0x60, 0xd9, 0xb9, 0x8d,
+	0x20, 0xf7, 0x97, 0x70, 0x10, 0xd6, 0xba, 0x86, 0xb8, 0x9c, 0xb8, 0xf8,
+	0x49, 0x71, 0x28, 0x9d, 0x05, 0x38, 0x1f, 0x63, 0xba, 0xf7, 0x15, 0x60,
+	0x96, 0x61, 0x84, 0x68, 0xeb, 0x5d, 0x28, 0x51, 0xe3, 0x51, 0xdd, 0x69,
+	0x8a, 0xdd, 0xba, 0xec, 0xbd, 0xd3, 0xa1, 0x42, 0x83, 0x59, 0x77, 0x11,
+	0x12, 0x86, 0x5b, 0x8d, 0x30, 0xcf, 0xdf, 0x6f, 0xea, 0x9d, 0x31, 0xa2,
+	0x65, 0xa5, 0x61, 0xc0, 0xde, 0x52, 0x6c, 0x72, 0x71, 0x0b, 0x4c, 0x7a,
+	0x4c, 0x9f, 0x75, 0x74, 0x38, 0xc8, 0xdd, 0x12, 0xba, 0x21, 0x57, 0x1b,
+	0x45, 0xb3, 0x02, 0x1d, 0x67, 0x22, 0x66, 0x53, 0x18, 0x48, 0xed, 0x60,
+	0x40, 0x55, 0xd1, 0x25, 0x3b, 0xbc, 0x08, 0x7b, 0x19, 0x8a, 0x30, 0x5b,
+	0x02, 0x4f, 0x65, 0x42, 0xff, 0xce, 0x87, 0xe8, 0x97, 0x2b, 0xbb, 0xfe,
+	0x52, 0x52, 0x72, 0xe8, 0xb5, 0x77, 0xb7, 0x8e, 0x94, 0x34, 0xbc, 0x46,
+	0xf1, 0xe1, 0x94, 0x98, 0x19, 0xbe, 0x7c, 0x3f, 0xf6, 0x0e, 0xe4, 0xbb,
+	0x88, 0x32, 0x07, 0x83, 0x64, 0xad, 0xd7, 0xd1, 0xe8, 0x35, 0x8d, 0x5d,
+	0x70, 0x16, 0xc8, 0x11, 0x94, 0x39, 0xc9, 0xac, 0xd6, 0xed, 0x6b, 0xdf,
+	0xc8, 0xf3, 0x1d, 0x5e, 0x37, 0xd8, 0xb5, 0x86, 0x9b, 0xc2, 0xdc, 0x3c,
+	0x5c, 0x04, 0x52, 0x5c, 0x11, 0x88, 0x0a, 0x2b, 0x78, 0x48, 0x9e, 0x5e,
+	0x98, 0x57, 0x5a, 0xd1, 0x77, 0x1c, 0x7d, 0x5f, 0x60, 0xbb, 0x61, 0x7e,
+	0x7e, 0x2a, 0xaf, 0x44, 0x14, 0x88, 0xfc, 0xa5, 0x31, 0xb7, 0xd4, 0x44,
+	0x48, 0xda, 0xb5, 0x71, 0xa8, 0xd8, 0x4f, 0x79, 0xcd, 0xe4, 0xbe, 0xb6,
+	0x1a, 0x61, 0x74, 0x4b, 0xd8, 0xec, 0xd7, 0xbf, 0xad, 0x57, 0x00, 0x42,
+	0x04, 0xe8, 0xb3, 0xec, 0x47, 0x1d, 0x2a, 0x0a, 0xde, 0x7c, 0x6e, 0x5e,
+	0xf8, 0xaa, 0x44, 0x05, 0x10, 0xab, 0xe9, 0x4e, 0xd7, 0x44, 0x0b, 0x97,
+	0x6f, 0x1a, 0xc1, 0x59, 0x2b, 0xe4, 0xe1, 0x8a, 0x13, 0x82, 0x65, 0xd8,
+	0xae, 0x5f, 0x2b, 0xbc, 0xa6, 0x14, 0x39, 0xaf, 0x38, 0x41, 0x26, 0x74,
+	0xdb, 0x55, 0x6b, 0xe2, 0x21, 0x80, 0x5d, 0x20, 0xc3, 0xf5, 0x82, 0xee,
+	0xcc, 0x3c, 0xc9, 0xb4, 0xeb, 0x52, 0xe9, 0x13, 0x8a, 0xea, 0xc6, 0x19,
+	0x70, 0x37, 0x1b, 0xb8, 0x2e, 0x86, 0xa2, 0xe9, 0x9d, 0xb6, 0xd5, 0xd6,
+	0xf3, 0xa8, 0x31, 0xf3, 0x02, 0xaa, 0x10, 0x33, 0x3f, 0xba, 0xf8, 0xf9,
+	0x46, 0x5b, 0xe1, 0xd7, 0x34, 0x9f, 0x94, 0xcb, 0xfb, 0xb1, 0x3d, 0x60,
+	0x77, 0x85, 0x14, 0xd4, 0xcf, 0x55, 0x60, 0x5d, 0x47, 0x6c, 0x07, 0xb4,
+	0xc7, 0x73, 0xbd, 0x49, 0xbd, 0xa5, 0x31, 0xa1, 0xfa, 0x34, 0x3a, 0x8b,
+	0x77, 0x1b, 0xaa, 0xaf, 0xa5, 0x87, 0x12, 0x4e, 0x36, 0x06, 0x14, 0xe7,
+	0xb3, 0xb8, 0x87, 0x6c, 0x4b, 0x50, 0xc9, 0x52, 0x1b, 0x19, 0x48, 0x69,
+	0x5b, 0x7f, 0xd8, 0xc9, 0x14, 0xb8, 0x11, 0xa0, 0x51, 0x09, 0xbd, 0x42,
+	0x5a, 0x50, 0x32, 0x57, 0x69, 0x39, 0x30, 0xdb, 0xbf, 0x8b, 0x93, 0x54,
+	0x43, 0x80, 0x4e, 0xd0, 0xc6, 0xf2, 0x81, 0x15, 0x6d, 0xef, 0x5a, 0xb6,
+	0x4d, 0x70, 0x93, 0x88, 0x8d, 0xce, 0x0d, 0xb8, 0xe9, 0xac, 0xa2, 0xcd,
+	0xc7, 0x18, 0xa5, 0x95, 0xb7, 0xf6, 0x0c, 0x6f, 0xe1, 0x10, 0x7b, 0x22,
+	0xf8, 0x81, 0x18, 0x42, 0x6a, 0x09, 0x75, 0x20, 0xb4, 0x2f, 0x67, 0x7a,
+	0xda, 0x55, 0x28, 0xc3, 0x81, 0xf7, 0xc1, 0xf0, 0xe6, 0x1b, 0x29, 0x9c,
+	0x72, 0x87, 0xe5, 0x4c, 0xa9, 0x5b, 0x5b, 0x62, 0xb5, 0xb7, 0x1e, 0x82,
+	0xc3, 0x7b, 0xaf, 0xe9, 0x6f, 0x37, 0x31, 0x9f, 0x79, 0xe7, 0x4f, 0x06,
+	0x1e, 0xff, 0xff, 0x80, 0x8e, 0x00, 0x00
+};
+
+int do_spl_test_load(struct unit_test_state *uts, const char *test_name,
+		     enum spl_test_image type, struct spl_image_loader *loader,
+		     int (*write_image)(struct unit_test_state *, void *, size_t))
+{
+	size_t img_size, img_data, plain_size = SPL_TEST_DATA_SIZE;
+	struct spl_image_info info_write = {
+		.name = test_name,
+		.size = type == LEGACY_LZMA ? sizeof(lzma_compressed) :
+					      plain_size,
+	}, info_read = { };
+	struct spl_boot_device bootdev = {
+		.boot_device = loader->boot_device,
+	};
+	char *data, *plain;
+	void *img;
+
+	img_size = create_image(NULL, type, &info_write, &img_data);
+	ut_assert(img_size);
+	img = calloc(img_size, 1);
+	ut_assertnonnull(img);
+
+	data = img + img_data;
+	if (type == LEGACY_LZMA) {
+		plain = malloc(plain_size);
+		ut_assertnonnull(plain);
+		generate_data(plain, plain_size, "lzma");
+		memcpy(data, lzma_compressed, sizeof(lzma_compressed));
+	} else {
+		plain = data;
+		generate_data(plain, plain_size, test_name);
+	}
+	ut_asserteq(img_size, create_image(img, type, &info_write, NULL));
 
-	load.priv = &text_ctx;
+	if (write_image(uts, img, img_size))
+		return CMD_RET_FAILURE;
 
-	ut_assertok(spl_load_simple_fit(&image, &load, 0, header));
+	ut_assertok(loader->load_image(&info_read, &bootdev));
+	if (check_image_info(uts, &info_write, &info_read))
+		return CMD_RET_FAILURE;
+	if (type == LEGACY_LZMA)
+		ut_asserteq(plain_size, info_read.size);
+	ut_asserteq_mem(plain, phys_to_virt(info_write.load_addr), plain_size);
 
+	if (type == LEGACY_LZMA)
+		free(plain);
+	free(img);
 	return 0;
 }
-SPL_TEST(spl_test_load, 0);
diff --git a/test/image/spl_load_fs.c b/test/image/spl_load_fs.c
new file mode 100644
index 0000000..297ab08
--- /dev/null
+++ b/test/image/spl_load_fs.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
+ */
+
+#include <common.h>
+#include <blk.h>
+#include <ext_common.h>
+#include <ext4fs.h>
+#include <fat.h>
+#include <fs.h>
+#include <memalign.h>
+#include <spl.h>
+#include <asm/io.h>
+#include <linux/stat.h>
+#include <test/spl.h>
+#include <test/ut.h>
+
+/**
+ * create_ext2() - Create an "ext2" filesystem with a single file
+ * @dst: The location of the new filesystem; MUST be zeroed
+ * @size: The size of the file
+ * @filename: The name of the file
+ * @data_offset: Filled with the offset of the file data from @dst
+ *
+ * Budget mke2fs. We use 1k blocks (to reduce overhead) with a single block
+ * group, which limits us to 8M of data. Almost every feature which increases
+ * complexity (checksums, hash tree directories, etc.) is disabled. We do cheat
+ * a little and use extents from ext4 to save having to deal with indirects, but
+ * U-Boot doesn't care.
+ *
+ * If @dst is %NULL, nothing is copied.
+ *
+ * Return: The size of the filesystem in bytes
+ */
+static size_t create_ext2(void *dst, size_t size, const char *filename,
+			  size_t *data_offset)
+{
+	u32 super_block = 1;
+	u32 group_block = 2;
+	u32 block_bitmap_block = 3;
+	u32 inode_bitmap_block = 4;
+	u32 inode_table_block = 5;
+	u32 root_block = 6;
+	u32 file_block = 7;
+
+	u32 root_ino = EXT2_ROOT_INO;
+	u32 file_ino = EXT2_BOOT_LOADER_INO;
+
+	u32 block_size = EXT2_MIN_BLOCK_SIZE;
+	u32 inode_size = sizeof(struct ext2_inode);
+
+	u32 file_blocks = (size + block_size - 1) / block_size;
+	u32 blocks = file_block + file_blocks;
+	u32 inodes = block_size / inode_size;
+	u32 filename_len = strlen(filename);
+	u32 dirent_len = ALIGN(filename_len, sizeof(struct ext2_dirent)) +
+			    sizeof(struct ext2_dirent);
+
+	struct ext2_sblock *sblock = dst + super_block * block_size;
+	struct ext2_block_group *bg = dst + group_block * block_size;
+	struct ext2_inode *inode_table = dst + inode_table_block * block_size;
+	struct ext2_inode *root_inode = &inode_table[root_ino - 1];
+	struct ext2_inode *file_inode = &inode_table[file_ino - 1];
+	struct ext4_extent_header *ext_block = (void *)&file_inode->b;
+	struct ext4_extent *extent = (void *)(ext_block + 1);
+	struct ext2_dirent *dot = dst + root_block * block_size;
+	struct ext2_dirent *dotdot = dot + 2;
+	struct ext2_dirent *dirent = dotdot + 2;
+	struct ext2_dirent *last = ((void *)dirent) + dirent_len;
+
+	/* Make sure we fit in one block group */
+	if (blocks > block_size * 8)
+		return 0;
+
+	if (filename_len > EXT2_NAME_LEN)
+		return 0;
+
+	if (data_offset)
+		*data_offset = file_block * block_size;
+
+	if (!dst)
+		goto out;
+
+	sblock->total_inodes = cpu_to_le32(inodes);
+	sblock->total_blocks = cpu_to_le32(blocks);
+	sblock->first_data_block = cpu_to_le32(super_block);
+	sblock->blocks_per_group = cpu_to_le32(blocks);
+	sblock->fragments_per_group = cpu_to_le32(blocks);
+	sblock->inodes_per_group = cpu_to_le32(inodes);
+	sblock->magic = cpu_to_le16(EXT2_MAGIC);
+	/* Done mostly so we can pretend to be (in)compatible */
+	sblock->revision_level = cpu_to_le32(EXT2_DYNAMIC_REV);
+	/* Not really accurate but it doesn't matter */
+	sblock->first_inode = cpu_to_le32(EXT2_GOOD_OLD_FIRST_INO);
+	sblock->inode_size = cpu_to_le32(inode_size);
+	sblock->feature_incompat = cpu_to_le32(EXT4_FEATURE_INCOMPAT_EXTENTS);
+
+	bg->block_id = cpu_to_le32(block_bitmap_block);
+	bg->inode_id = cpu_to_le32(inode_bitmap_block);
+	bg->inode_table_id = cpu_to_le32(inode_table_block);
+
+	/*
+	 * All blocks/inodes are in-use. I don't want to have to deal with
+	 * endianness, so just fill everything in.
+	 */
+	memset(dst + block_bitmap_block * block_size, 0xff, block_size * 2);
+
+	root_inode->mode = cpu_to_le16(S_IFDIR | 0755);
+	root_inode->size = cpu_to_le32(block_size);
+	root_inode->nlinks = cpu_to_le16(3);
+	root_inode->blockcnt = cpu_to_le32(1);
+	root_inode->flags = cpu_to_le32(EXT4_TOPDIR_FL);
+	root_inode->b.blocks.dir_blocks[0] = root_block;
+
+	file_inode->mode = cpu_to_le16(S_IFREG | 0644);
+	file_inode->size = cpu_to_le32(size);
+	file_inode->nlinks = cpu_to_le16(1);
+	file_inode->blockcnt = cpu_to_le32(file_blocks);
+	file_inode->flags = cpu_to_le32(EXT4_EXTENTS_FL);
+	ext_block->eh_magic = cpu_to_le16(EXT4_EXT_MAGIC);
+	ext_block->eh_entries = cpu_to_le16(1);
+	ext_block->eh_max = cpu_to_le16(sizeof(file_inode->b) /
+					sizeof(*ext_block) - 1);
+	extent->ee_len = cpu_to_le16(file_blocks);
+	extent->ee_start_lo = cpu_to_le16(file_block);
+
+	/* I'm not sure we need these, but it can't hurt */
+	dot->inode = cpu_to_le32(root_ino);
+	dot->direntlen = cpu_to_le16(2 * sizeof(*dot));
+	dot->namelen = 1;
+	dot->filetype = FILETYPE_DIRECTORY;
+	memcpy(dot + 1, ".", dot->namelen);
+
+	dotdot->inode = cpu_to_le32(root_ino);
+	dotdot->direntlen = cpu_to_le16(2 * sizeof(*dotdot));
+	dotdot->namelen = 2;
+	dotdot->filetype = FILETYPE_DIRECTORY;
+	memcpy(dotdot + 1, "..", dotdot->namelen);
+
+	dirent->inode = cpu_to_le32(file_ino);
+	dirent->direntlen = cpu_to_le16(dirent_len);
+	dirent->namelen = filename_len;
+	dirent->filetype = FILETYPE_REG;
+	memcpy(dirent + 1, filename, filename_len);
+
+	last->direntlen = block_size - dirent_len;
+
+out:
+	return (size_t)blocks * block_size;
+}
+
+/**
+ * create_fat() - Create a FAT32 filesystem with a single file
+ * @dst: The location of the new filesystem; MUST be zeroed
+ * @size: The size of the file
+ * @filename: The name of the file
+ * @data_offset: Filled with the offset of the file data from @dst
+ *
+ * Budget mkfs.fat. We use FAT32 (so I don't have to deal with FAT12) with no
+ * info sector, and a single one-sector FAT. This limits us to 64k of data
+ * (enough for anyone). The filename must fit in 8.3.
+ *
+ * If @dst is %NULL, nothing is copied.
+ *
+ * Return: The size of the filesystem in bytes
+ */
+static size_t create_fat(void *dst, size_t size, const char *filename,
+			 size_t *data_offset)
+{
+	u16 boot_sector = 0;
+	u16 fat_sector = 1;
+	u32 root_sector = 2;
+	u32 file_sector = 3;
+
+	u16 sector_size = 512;
+	u32 file_sectors = (size + sector_size - 1) / sector_size;
+	u32 sectors = file_sector + file_sectors;
+
+	char *ext;
+	size_t filename_len, ext_len;
+	int i;
+
+	struct boot_sector *bs = dst + boot_sector * sector_size;
+	struct volume_info *vi = (void *)(bs + 1);
+	__le32 *fat = dst + fat_sector * sector_size;
+	struct dir_entry *dirent = dst + root_sector * sector_size;
+
+	/* Make sure we fit in the FAT */
+	if (sectors > sector_size / sizeof(u32))
+		return 0;
+
+	ext = strchr(filename, '.');
+	if (ext) {
+		filename_len = ext - filename;
+		ext++;
+		ext_len = strlen(ext);
+	} else {
+		filename_len = strlen(filename);
+		ext_len = 0;
+	}
+
+	if (filename_len > 8 || ext_len > 3)
+		return 0;
+
+	if (data_offset)
+		*data_offset = file_sector * sector_size;
+
+	if (!dst)
+		goto out;
+
+	bs->sector_size[0] = sector_size & 0xff;
+	bs->sector_size[1] = sector_size >> 8;
+	bs->cluster_size = 1;
+	bs->reserved = cpu_to_le16(fat_sector);
+	bs->fats = 1;
+	bs->media = 0xf8;
+	bs->total_sect = cpu_to_le32(sectors);
+	bs->fat32_length = cpu_to_le32(1);
+	bs->root_cluster = cpu_to_le32(root_sector);
+
+	vi->ext_boot_sign = 0x29;
+	memcpy(vi->fs_type, FAT32_SIGN, sizeof(vi->fs_type));
+
+	memcpy(dst + 0x1fe, "\x55\xAA", 2);
+
+	fat[0] = cpu_to_le32(0x0ffffff8);
+	fat[1] = cpu_to_le32(0x0fffffff);
+	fat[2] = cpu_to_le32(0x0ffffff8);
+	for (i = file_sector; file_sectors > 1; file_sectors--, i++)
+		fat[i] = cpu_to_le32(i + 1);
+	fat[i] = cpu_to_le32(0x0ffffff8);
+
+	for (i = 0; i < sizeof(dirent->nameext.name); i++) {
+		if (i < filename_len)
+			dirent->nameext.name[i] = toupper(filename[i]);
+		else
+			dirent->nameext.name[i] = ' ';
+	}
+
+	for (i = 0; i < sizeof(dirent->nameext.ext); i++) {
+		if (i < ext_len)
+			dirent->nameext.ext[i] = toupper(ext[i]);
+		else
+			dirent->nameext.ext[i] = ' ';
+	}
+
+	dirent->start = cpu_to_le16(file_sector);
+	dirent->size = cpu_to_le32(size);
+
+out:
+	return sectors * sector_size;
+}
+
+typedef size_t (*create_fs_t)(void *, size_t, const char *, size_t *);
+
+static int spl_test_fs(struct unit_test_state *uts, const char *test_name,
+		       create_fs_t create)
+{
+	const char *filename = CONFIG_SPL_FS_LOAD_PAYLOAD_NAME;
+	struct blk_desc *dev_desc;
+	char *data_write, *data_read;
+	void *fs;
+	size_t fs_size, fs_data, fs_blocks, data_size = SPL_TEST_DATA_SIZE;
+	loff_t actread;
+
+	fs_size = create(NULL, data_size, filename, &fs_data);
+	ut_assert(fs_size);
+	fs = calloc(fs_size, 1);
+	ut_assertnonnull(fs);
+
+	data_write = fs + fs_data;
+	generate_data(data_write, data_size, test_name);
+	ut_asserteq(fs_size, create(fs, data_size, filename, NULL));
+
+	dev_desc = blk_get_devnum_by_uclass_id(UCLASS_MMC, 0);
+	ut_assertnonnull(dev_desc);
+	ut_asserteq(512, dev_desc->blksz);
+	fs_blocks = fs_size / dev_desc->blksz;
+	ut_asserteq(fs_blocks, blk_dwrite(dev_desc, 0, fs_blocks, fs));
+
+	/* We have to use malloc so we can call virt_to_phys */
+	data_read = malloc_cache_aligned(data_size);
+	ut_assertnonnull(data_read);
+	ut_assertok(fs_set_blk_dev_with_part(dev_desc, 0));
+	ut_assertok(fs_read("/" CONFIG_SPL_FS_LOAD_PAYLOAD_NAME,
+			    virt_to_phys(data_read), 0, data_size, &actread));
+	ut_asserteq(data_size, actread);
+	ut_asserteq_mem(data_write, data_read, data_size);
+
+	free(data_read);
+	free(fs);
+	return 0;
+}
+
+static int spl_test_ext(struct unit_test_state *uts)
+{
+	return spl_test_fs(uts, __func__, create_ext2);
+}
+SPL_TEST(spl_test_ext, DM_FLAGS);
+
+static int spl_test_fat(struct unit_test_state *uts)
+{
+	spl_fat_force_reregister();
+	return spl_test_fs(uts, __func__, create_fat);
+}
+SPL_TEST(spl_test_fat, DM_FLAGS);
+
+static bool spl_mmc_raw;
+
+u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
+{
+	return spl_mmc_raw ? MMCSD_MODE_RAW : MMCSD_MODE_FS;
+}
+
+static int spl_test_mmc_fs(struct unit_test_state *uts, const char *test_name,
+			   enum spl_test_image type, create_fs_t create_fs,
+			   bool blk_mode)
+{
+	const char *filename = CONFIG_SPL_FS_LOAD_PAYLOAD_NAME;
+	struct blk_desc *dev_desc;
+	size_t fs_size, fs_data, img_size, img_data,
+	       data_size = SPL_TEST_DATA_SIZE;
+	struct spl_image_info info_write = {
+		.name = test_name,
+		.size = data_size,
+	}, info_read = { };
+	struct disk_partition part = {
+		.start = 1,
+		.sys_ind = 0x83,
+	};
+	struct spl_image_loader *loader =
+		SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_MMC1, spl_mmc_load_image);
+	struct spl_boot_device bootdev = {
+		.boot_device = loader->boot_device,
+	};
+	void *fs;
+	char *data;
+
+	img_size = create_image(NULL, type, &info_write, &img_data);
+	ut_assert(img_size);
+	fs_size = create_fs(NULL, img_size, filename, &fs_data);
+	ut_assert(fs_size);
+	fs = calloc(fs_size, 1);
+	ut_assertnonnull(fs);
+
+	data = fs + fs_data + img_data;
+	generate_data(data, data_size, test_name);
+	ut_asserteq(img_size, create_image(fs + fs_data, type, &info_write,
+					   NULL));
+	ut_asserteq(fs_size, create_fs(fs, img_size, filename, NULL));
+
+	dev_desc = blk_get_devnum_by_uclass_id(UCLASS_MMC, 0);
+	ut_assertnonnull(dev_desc);
+
+	ut_asserteq(512, dev_desc->blksz);
+	part.size = fs_size / dev_desc->blksz;
+	ut_assertok(write_mbr_partitions(dev_desc, &part, 1, 0));
+	ut_asserteq(part.size, blk_dwrite(dev_desc, part.start, part.size, fs));
+
+	spl_mmc_raw = false;
+	if (blk_mode)
+		ut_assertok(spl_blk_load_image(&info_read, &bootdev, UCLASS_MMC,
+					       0, 1));
+	else
+		ut_assertok(loader->load_image(&info_read, &bootdev));
+	if (check_image_info(uts, &info_write, &info_read))
+		return CMD_RET_FAILURE;
+	ut_asserteq_mem(data, phys_to_virt(info_write.load_addr), data_size);
+
+	free(fs);
+	return 0;
+}
+
+static int spl_test_blk(struct unit_test_state *uts, const char *test_name,
+			enum spl_test_image type)
+{
+	spl_fat_force_reregister();
+	if (spl_test_mmc_fs(uts, test_name, type, create_fat, true))
+		return CMD_RET_FAILURE;
+
+	return spl_test_mmc_fs(uts, test_name, type, create_ext2, true);
+}
+SPL_IMG_TEST(spl_test_blk, LEGACY, DM_FLAGS);
+SPL_IMG_TEST(spl_test_blk, FIT_EXTERNAL, DM_FLAGS);
+SPL_IMG_TEST(spl_test_blk, FIT_INTERNAL, DM_FLAGS);
+
+static int spl_test_mmc_write_image(struct unit_test_state *uts, void *img,
+				    size_t img_size)
+{
+	struct blk_desc *dev_desc;
+	size_t img_blocks;
+
+	dev_desc = blk_get_devnum_by_uclass_id(UCLASS_MMC, 0);
+	ut_assertnonnull(dev_desc);
+
+	img_blocks = DIV_ROUND_UP(img_size, dev_desc->blksz);
+	ut_asserteq(img_blocks, blk_dwrite(dev_desc,
+					   CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR,
+					   img_blocks, img));
+
+	spl_mmc_raw = true;
+	return 0;
+}
+
+static int spl_test_mmc(struct unit_test_state *uts, const char *test_name,
+			enum spl_test_image type)
+{
+	spl_mmc_clear_cache();
+	spl_fat_force_reregister();
+
+	if (type == LEGACY &&
+	    spl_test_mmc_fs(uts, test_name, type, create_ext2, false))
+		return CMD_RET_FAILURE;
+
+	if (type != IMX8 &&
+	    spl_test_mmc_fs(uts, test_name, type, create_fat, false))
+		return CMD_RET_FAILURE;
+
+	return do_spl_test_load(uts, test_name, type,
+				SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_MMC1,
+						   spl_mmc_load_image),
+				spl_test_mmc_write_image);
+}
+SPL_IMG_TEST(spl_test_mmc, LEGACY, DM_FLAGS);
+SPL_IMG_TEST(spl_test_mmc, IMX8, DM_FLAGS);
+SPL_IMG_TEST(spl_test_mmc, FIT_EXTERNAL, DM_FLAGS);
+SPL_IMG_TEST(spl_test_mmc, FIT_INTERNAL, DM_FLAGS);
diff --git a/test/image/spl_load_net.c b/test/image/spl_load_net.c
new file mode 100644
index 0000000..f570cef
--- /dev/null
+++ b/test/image/spl_load_net.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <spl.h>
+#include <test/spl.h>
+#include <asm/eth.h>
+#include <test/ut.h>
+#include "../../net/bootp.h"
+
+/*
+ * sandbox_eth_bootp_req_to_reply()
+ *
+ * Check if a BOOTP request was sent. If so, inject a reply
+ *
+ * returns 0 if injected, -EAGAIN if not
+ */
+static int sandbox_eth_bootp_req_to_reply(struct udevice *dev, void *packet,
+					  unsigned int len)
+{
+	struct eth_sandbox_priv *priv = dev_get_priv(dev);
+	struct ethernet_hdr *eth = packet;
+	struct ip_udp_hdr *ip;
+	struct bootp_hdr *bp;
+	struct ethernet_hdr *eth_recv;
+	struct ip_udp_hdr *ipr;
+	struct bootp_hdr *bpr;
+
+	if (ntohs(eth->et_protlen) != PROT_IP)
+		return -EAGAIN;
+
+	ip = packet + ETHER_HDR_SIZE;
+	if (ip->ip_p != IPPROTO_UDP)
+		return -EAGAIN;
+
+	if (ntohs(ip->udp_dst) != PORT_BOOTPS)
+		return -EAGAIN;
+
+	bp = (void *)ip + IP_UDP_HDR_SIZE;
+	if (bp->bp_op != OP_BOOTREQUEST)
+		return -EAGAIN;
+
+	/* Don't allow the buffer to overrun */
+	if (priv->recv_packets >= PKTBUFSRX)
+		return 0;
+
+	/* reply to the request */
+	eth_recv = (void *)priv->recv_packet_buffer[priv->recv_packets];
+	memcpy(eth_recv, packet, len);
+	ipr = (void *)eth_recv + ETHER_HDR_SIZE;
+	bpr = (void *)ipr + IP_UDP_HDR_SIZE;
+	memcpy(eth_recv->et_dest, eth->et_src, ARP_HLEN);
+	memcpy(eth_recv->et_src, priv->fake_host_hwaddr, ARP_HLEN);
+	ipr->ip_sum = 0;
+	ipr->ip_off = 0;
+	net_write_ip(&ipr->ip_dst, net_ip);
+	net_write_ip(&ipr->ip_src, priv->fake_host_ipaddr);
+	ipr->ip_sum = compute_ip_checksum(ipr, IP_HDR_SIZE);
+	ipr->udp_src = ip->udp_dst;
+	ipr->udp_dst = ip->udp_src;
+
+	bpr->bp_op = OP_BOOTREPLY;
+	net_write_ip(&bpr->bp_yiaddr, net_ip);
+	net_write_ip(&bpr->bp_siaddr, priv->fake_host_ipaddr);
+	copy_filename(bpr->bp_file, CONFIG_BOOTFILE, sizeof(CONFIG_BOOTFILE));
+	memset(&bpr->bp_vend, 0, sizeof(bpr->bp_vend));
+
+	priv->recv_packet_length[priv->recv_packets] = len;
+	++priv->recv_packets;
+
+	return 0;
+}
+
+struct spl_test_net_priv {
+	struct unit_test_state *uts;
+	void *img;
+	size_t img_size;
+	u16 port;
+};
+
+/* Well known TFTP port # */
+#define TFTP_PORT	69
+/* Transaction ID, chosen at random */
+#define	TFTP_TID	21313
+
+/*
+ *	TFTP operations.
+ */
+#define TFTP_RRQ	1
+#define TFTP_DATA	3
+#define TFTP_ACK	4
+
+/* default TFTP block size */
+#define TFTP_BLOCK_SIZE		512
+
+struct tftp_hdr {
+	u16 opcode;
+	u16 block;
+};
+
+#define TFTP_HDR_SIZE sizeof(struct tftp_hdr)
+
+/*
+ * sandbox_eth_tftp_req_to_reply()
+ *
+ * Check if a TFTP request was sent. If so, inject a reply. We don't support
+ * options, and we don't check for rollover, so we are limited files of less
+ * than 32M.
+ *
+ * returns 0 if injected, -EAGAIN if not
+ */
+static int sandbox_eth_tftp_req_to_reply(struct udevice *dev, void *packet,
+					 unsigned int len)
+{
+	struct eth_sandbox_priv *priv = dev_get_priv(dev);
+	struct spl_test_net_priv *test_priv = priv->priv;
+	struct ethernet_hdr *eth = packet;
+	struct ip_udp_hdr *ip;
+	struct tftp_hdr *tftp;
+	struct ethernet_hdr *eth_recv;
+	struct ip_udp_hdr *ipr;
+	struct tftp_hdr *tftpr;
+	size_t size;
+	u16 block;
+
+	if (ntohs(eth->et_protlen) != PROT_IP)
+		return -EAGAIN;
+
+	ip = packet + ETHER_HDR_SIZE;
+	if (ip->ip_p != IPPROTO_UDP)
+		return -EAGAIN;
+
+	if (ntohs(ip->udp_dst) == TFTP_PORT) {
+		tftp = (void *)ip + IP_UDP_HDR_SIZE;
+		if (htons(tftp->opcode) != TFTP_RRQ)
+			return -EAGAIN;
+
+		block = 0;
+	} else if (ntohs(ip->udp_dst) == TFTP_TID) {
+		tftp = (void *)ip + IP_UDP_HDR_SIZE;
+		if (htons(tftp->opcode) != TFTP_ACK)
+			return -EAGAIN;
+
+		block = htons(tftp->block);
+	} else {
+		return -EAGAIN;
+	}
+
+	if (block * TFTP_BLOCK_SIZE > test_priv->img_size)
+		return 0;
+
+	size = min(test_priv->img_size - block * TFTP_BLOCK_SIZE,
+		   (size_t)TFTP_BLOCK_SIZE);
+
+	/* Don't allow the buffer to overrun */
+	if (priv->recv_packets >= PKTBUFSRX)
+		return 0;
+
+	/* reply to the request */
+	eth_recv = (void *)priv->recv_packet_buffer[priv->recv_packets];
+	memcpy(eth_recv->et_dest, eth->et_src, ARP_HLEN);
+	memcpy(eth_recv->et_src, priv->fake_host_hwaddr, ARP_HLEN);
+	eth_recv->et_protlen = htons(PROT_IP);
+
+	ipr = (void *)eth_recv + ETHER_HDR_SIZE;
+	ipr->ip_hl_v = 0x45;
+	ipr->ip_len = htons(IP_UDP_HDR_SIZE + TFTP_HDR_SIZE + size);
+	ipr->ip_off = htons(IP_FLAGS_DFRAG);
+	ipr->ip_ttl = 255;
+	ipr->ip_p = IPPROTO_UDP;
+	ipr->ip_sum = 0;
+	net_copy_ip(&ipr->ip_dst, &ip->ip_src);
+	net_copy_ip(&ipr->ip_src, &ip->ip_dst);
+	ipr->ip_sum = compute_ip_checksum(ipr, IP_HDR_SIZE);
+
+	ipr->udp_src = htons(TFTP_TID);
+	ipr->udp_dst = ip->udp_src;
+	ipr->udp_len = htons(UDP_HDR_SIZE + TFTP_HDR_SIZE + size);
+	ipr->udp_xsum = 0;
+
+	tftpr = (void *)ipr + IP_UDP_HDR_SIZE;
+	tftpr->opcode = htons(TFTP_DATA);
+	tftpr->block = htons(block + 1);
+	memcpy((void *)tftpr + TFTP_HDR_SIZE,
+	       test_priv->img + block * TFTP_BLOCK_SIZE, size);
+
+	priv->recv_packet_length[priv->recv_packets] =
+		ETHER_HDR_SIZE + IP_UDP_HDR_SIZE + TFTP_HDR_SIZE + size;
+	++priv->recv_packets;
+
+	return 0;
+}
+
+static int spl_net_handler(struct udevice *dev, void *packet,
+			   unsigned int len)
+{
+	struct eth_sandbox_priv *priv = dev_get_priv(dev);
+	int old_packets = priv->recv_packets;
+
+	priv->fake_host_ipaddr = string_to_ip("1.1.2.4");
+	net_ip = string_to_ip("1.1.2.2");
+
+	sandbox_eth_arp_req_to_reply(dev, packet, len);
+	sandbox_eth_bootp_req_to_reply(dev, packet, len);
+	sandbox_eth_tftp_req_to_reply(dev, packet, len);
+
+	if (old_packets == priv->recv_packets)
+		return 0;
+
+	return 0;
+}
+
+static int spl_test_net_write_image(struct unit_test_state *uts, void *img,
+				    size_t img_size)
+{
+	struct spl_test_net_priv *test_priv = malloc(sizeof(*test_priv));
+
+	ut_assertnonnull(test_priv);
+	test_priv->uts = uts;
+	test_priv->img = img;
+	test_priv->img_size = img_size;
+
+	sandbox_eth_set_tx_handler(0, spl_net_handler);
+	sandbox_eth_set_priv(0, test_priv);
+	return 0;
+}
+
+static int spl_test_net(struct unit_test_state *uts, const char *test_name,
+			enum spl_test_image type)
+{
+	struct eth_sandbox_priv *priv;
+	struct udevice *dev;
+	int ret;
+
+	net_server_ip = string_to_ip("1.1.2.4");
+	ret = do_spl_test_load(uts, test_name, type,
+			       SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_CPGMAC,
+						  spl_net_load_image_cpgmac),
+			       spl_test_net_write_image);
+
+	sandbox_eth_set_tx_handler(0, NULL);
+	ut_assertok(uclass_get_device(UCLASS_ETH, 0, &dev));
+	priv = dev_get_priv(dev);
+	free(priv->priv);
+	return ret;
+}
+SPL_IMG_TEST(spl_test_net, LEGACY, DM_FLAGS);
+SPL_IMG_TEST(spl_test_net, FIT_INTERNAL, DM_FLAGS);
+SPL_IMG_TEST(spl_test_net, FIT_EXTERNAL, DM_FLAGS);
diff --git a/test/image/spl_load_nor.c b/test/image/spl_load_nor.c
new file mode 100644
index 0000000..a62bb60
--- /dev/null
+++ b/test/image/spl_load_nor.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <spl.h>
+#include <asm/io.h>
+#include <test/spl.h>
+#include <test/ut.h>
+
+static void *spl_test_nor_base;
+
+unsigned long spl_nor_get_uboot_base(void)
+{
+	return virt_to_phys(spl_test_nor_base);
+}
+
+static int spl_test_nor_write_image(struct unit_test_state *uts, void *img,
+				    size_t img_size)
+{
+	spl_test_nor_base = img;
+	return 0;
+}
+
+static int spl_test_nor(struct unit_test_state *uts, const char *test_name,
+			enum spl_test_image type)
+{
+	return do_spl_test_load(uts, test_name, type,
+				SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_NOR,
+						   spl_nor_load_image),
+				spl_test_nor_write_image);
+}
+SPL_IMG_TEST(spl_test_nor, LEGACY, 0);
+SPL_IMG_TEST(spl_test_nor, LEGACY_LZMA, 0);
+SPL_IMG_TEST(spl_test_nor, IMX8, 0);
+SPL_IMG_TEST(spl_test_nor, FIT_INTERNAL, 0);
+SPL_IMG_TEST(spl_test_nor, FIT_EXTERNAL, 0);
diff --git a/test/image/spl_load_os.c b/test/image/spl_load_os.c
new file mode 100644
index 0000000..49edf15
--- /dev/null
+++ b/test/image/spl_load_os.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <image.h>
+#include <os.h>
+#include <spl.h>
+#include <test/spl.h>
+#include <test/ut.h>
+
+/* Context used for this test */
+struct text_ctx {
+	int fd;
+};
+
+static ulong read_fit_image(struct spl_load_info *load, ulong sector,
+			    ulong count, void *buf)
+{
+	struct text_ctx *text_ctx = load->priv;
+	off_t offset, ret;
+	ssize_t res;
+
+	offset = sector * load->bl_len;
+	ret = os_lseek(text_ctx->fd, offset, OS_SEEK_SET);
+	if (ret != offset) {
+		printf("Failed to seek to %zx, got %zx (errno=%d)\n", offset,
+		       ret, errno);
+		return 0;
+	}
+
+	res = os_read(text_ctx->fd, buf, count * load->bl_len);
+	if (res == -1) {
+		printf("Failed to read %lx bytes, got %ld (errno=%d)\n",
+		       count * load->bl_len, res, errno);
+		return 0;
+	}
+
+	return count;
+}
+
+static int spl_test_load(struct unit_test_state *uts)
+{
+	struct spl_image_info image;
+	struct legacy_img_hdr *header;
+	struct text_ctx text_ctx;
+	struct spl_load_info load;
+	char fname[256];
+	int ret;
+	int fd;
+
+	memset(&load, '\0', sizeof(load));
+	load.bl_len = 512;
+	load.read = read_fit_image;
+
+	ret = sandbox_find_next_phase(fname, sizeof(fname), true);
+	if (ret)
+		ut_assertf(0, "%s not found, error %d\n", fname, ret);
+	load.filename = fname;
+
+	header = spl_get_load_buffer(-sizeof(*header), sizeof(*header));
+
+	fd = os_open(fname, OS_O_RDONLY);
+	ut_assert(fd >= 0);
+	ut_asserteq(512, os_read(fd, header, 512));
+	text_ctx.fd = fd;
+
+	load.priv = &text_ctx;
+
+	ut_assertok(spl_load_simple_fit(&image, &load, 0, header));
+
+	return 0;
+}
+SPL_TEST(spl_test_load, 0);
diff --git a/test/image/spl_load_spi.c b/test/image/spl_load_spi.c
new file mode 100644
index 0000000..8f9b6e0
--- /dev/null
+++ b/test/image/spl_load_spi.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <spi_flash.h>
+#include <spl.h>
+#include <test/spl.h>
+#include <test/ut.h>
+
+static int spl_test_spi_write_image(struct unit_test_state *uts, void *img,
+				    size_t img_size)
+{
+	struct spi_flash *flash;
+
+	flash = spi_flash_probe(spl_spi_boot_bus(), spl_spi_boot_cs(),
+				CONFIG_SF_DEFAULT_SPEED,
+				CONFIG_SF_DEFAULT_MODE);
+	ut_assertnonnull(flash);
+	ut_assertok(spi_flash_write(flash, spl_spi_get_uboot_offs(flash),
+				    img_size, img));
+
+	return 0;
+}
+
+static int spl_test_spi(struct unit_test_state *uts, const char *test_name,
+			enum spl_test_image type)
+{
+	return do_spl_test_load(uts, test_name, type,
+				SPL_LOAD_IMAGE_GET(1, BOOT_DEVICE_SPI,
+						   spl_spi_load_image),
+				spl_test_spi_write_image);
+}
+SPL_IMG_TEST(spl_test_spi, LEGACY, DM_FLAGS);
+SPL_IMG_TEST(spl_test_spi, IMX8, DM_FLAGS);
+SPL_IMG_TEST(spl_test_spi, FIT_INTERNAL, DM_FLAGS);
+#if !IS_ENABLED(CONFIG_SPL_LOAD_FIT_FULL)
+SPL_IMG_TEST(spl_test_spi, FIT_EXTERNAL, DM_FLAGS);
+#endif
diff --git a/test/py/tests/test_spl.py b/test/py/tests/test_spl.py
index bd273da..42e4c43 100644
--- a/test/py/tests/test_spl.py
+++ b/test/py/tests/test_spl.py
@@ -5,6 +5,16 @@
 import os.path
 import pytest
 
+@pytest.mark.buildconfigspec('spl_unit_test')
+def test_ut_spl_init(u_boot_console):
+    """Initialize data for ut spl tests."""
+
+    fn = u_boot_console.config.source_dir + '/spi.bin'
+    if not os.path.exists(fn):
+        data = b'\x00' * (2 * 1024 * 1024)
+        with open(fn, 'wb') as fh:
+            fh.write(data)
+
 def test_spl(u_boot_console, ut_spl_subtest):
     """Execute a "ut" subtest.
 
diff --git a/test/test-main.c b/test/test-main.c
index edb20bc..b7015d9 100644
--- a/test/test-main.c
+++ b/test/test-main.c
@@ -303,7 +303,7 @@
 	if (test->flags & UT_TESTF_PROBE_TEST)
 		ut_assertok(do_autoprobe(uts));
 
-	if (!CONFIG_IS_ENABLED(OF_PLATDATA) &&
+	if (CONFIG_IS_ENABLED(OF_REAL) &&
 	    (test->flags & UT_TESTF_SCAN_FDT)) {
 		/*
 		 * only set this if we know the ethernet uclass will be created