Merge branch '2023-04-05-blkmap-composable-virtual-block-devices'

To quote the author:
Block maps are a way of looking at various sources of data through the
lens of a regular block device. It lets you treat devices that are not
block devices, like RAM, as if they were. It also lets you export a
slice of an existing block device, which does not have to correspond to
a partition boundary, as a new block device.

This is primarily useful because U-Boot's filesystem drivers only
operate on block devices, so a block map lets you access filesystems
wherever they might be located.

The implementation is loosely modeled on Linux's "Device Mapper"
subsystem, see the kernel documentation [1] for more information.

The primary use-cases are to access filesystem images stored in RAM, and
within FIT images stored on disk. See doc/usage/blkmap.rst for more
details.

The architecture is pluggable, so adding other types of mappings should
be quite easy.

[1]: https://docs.kernel.org/admin-guide/device-mapper/index.html
diff --git a/MAINTAINERS b/MAINTAINERS
index d2e245e..4c17c6c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -793,6 +793,15 @@
 S:	Maintained
 F:	tools/binman/
 
+BLKMAP
+M:	Tobias Waldekranz <tobias@waldekranz.com>
+S:	Maintained
+F:	cmd/blkmap.c
+F:	doc/usage/blkmap.rst
+F:	drivers/block/blkmap.c
+F:	include/blkmap.h
+F:	test/dm/blkmap.c
+
 BOOTDEVICE
 M:	Simon Glass <sjg@chromium.org>
 S:	Maintained
diff --git a/boot/image-board.c b/boot/image-board.c
index 7dd0c32..c602832 100644
--- a/boot/image-board.c
+++ b/boot/image-board.c
@@ -1126,7 +1126,8 @@
 			}
 
 			/* get script subimage data address and length */
-			if (fit_image_get_data(fit_hdr, noffset, &fit_data, &fit_len)) {
+			if (fit_image_get_data_and_size(fit_hdr, noffset,
+							&fit_data, &fit_len)) {
 				puts("Could not find script subimage data\n");
 				return 1;
 			}
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 8c9b430..bab35fc 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1980,6 +1980,25 @@
 	  during development, but also allows the cache to be disabled when
 	  it might hurt performance (e.g. when using the ums command).
 
+config CMD_BLKMAP
+	bool "blkmap - Composable virtual block devices"
+	depends on BLKMAP
+	default y if BLKMAP
+	help
+ 	  Create virtual block devices that are backed by various sources,
+ 	  e.g. RAM, or parts of an existing block device. Though much more
+ 	  rudimentary, it borrows a lot of ideas from Linux's device mapper
+ 	  subsystem.
+
+	  Example use-cases:
+	  - Treat a region of RAM as a block device, i.e. a RAM disk. This let's
+            you extract files from filesystem images stored in RAM (perhaps as a
+            result of a TFTP transfer).
+	  - Create a virtual partition on an existing device. This let's you
+            access filesystems that aren't stored at an exact partition
+            boundary. A common example is a filesystem image embedded in an FIT
+            image.
+
 config CMD_BUTTON
 	bool "button"
 	depends on BUTTON
diff --git a/cmd/Makefile b/cmd/Makefile
index e032091..054ef42 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -27,6 +27,7 @@
 obj-$(CONFIG_CMD_BDI) += bdinfo.o
 obj-$(CONFIG_CMD_BIND) += bind.o
 obj-$(CONFIG_CMD_BINOP) += binop.o
+obj-$(CONFIG_CMD_BLKMAP) += blkmap.o
 obj-$(CONFIG_CMD_BLOBLIST) += bloblist.o
 obj-$(CONFIG_CMD_BLOCK_CACHE) += blkcache.o
 obj-$(CONFIG_CMD_BMP) += bmp.o
diff --git a/cmd/blk_common.c b/cmd/blk_common.c
index 75a072c..9f9d432 100644
--- a/cmd/blk_common.c
+++ b/cmd/blk_common.c
@@ -11,6 +11,7 @@
 #include <common.h>
 #include <blk.h>
 #include <command.h>
+#include <mapmem.h>
 
 int blk_common_cmd(int argc, char *const argv[], enum uclass_id uclass_id,
 		   int *cur_devnump)
@@ -63,31 +64,37 @@
 
 	default: /* at least 4 args */
 		if (strcmp(argv[1], "read") == 0) {
-			ulong addr = hextoul(argv[2], NULL);
+			phys_addr_t paddr = hextoul(argv[2], NULL);
 			lbaint_t blk = hextoul(argv[3], NULL);
 			ulong cnt = hextoul(argv[4], NULL);
+			void *vaddr;
 			ulong n;
 
 			printf("\n%s read: device %d block # "LBAFU", count %lu ... ",
 			       if_name, *cur_devnump, blk, cnt);
 
+			vaddr = map_sysmem(paddr, 512 * cnt);
 			n = blk_read_devnum(uclass_id, *cur_devnump, blk, cnt,
-					    (ulong *)addr);
+					    vaddr);
+			unmap_sysmem(vaddr);
 
 			printf("%ld blocks read: %s\n", n,
 			       n == cnt ? "OK" : "ERROR");
 			return n == cnt ? 0 : 1;
 		} else if (strcmp(argv[1], "write") == 0) {
-			ulong addr = hextoul(argv[2], NULL);
+			phys_addr_t paddr = hextoul(argv[2], NULL);
 			lbaint_t blk = hextoul(argv[3], NULL);
 			ulong cnt = hextoul(argv[4], NULL);
+			void *vaddr;
 			ulong n;
 
 			printf("\n%s write: device %d block # "LBAFU", count %lu ... ",
 			       if_name, *cur_devnump, blk, cnt);
 
+			vaddr = map_sysmem(paddr, 512 * cnt);
 			n = blk_write_devnum(uclass_id, *cur_devnump, blk, cnt,
-					     (ulong *)addr);
+					     vaddr);
+			unmap_sysmem(vaddr);
 
 			printf("%ld blocks written: %s\n", n,
 			       n == cnt ? "OK" : "ERROR");
diff --git a/cmd/blkmap.c b/cmd/blkmap.c
new file mode 100644
index 0000000..b34c013
--- /dev/null
+++ b/cmd/blkmap.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Addiva Elektronik
+ * Author: Tobias Waldekranz <tobias@waldekranz.com>
+ */
+
+#include <blk.h>
+#include <blkmap.h>
+#include <common.h>
+#include <command.h>
+#include <malloc.h>
+#include <dm/device.h>
+
+static int blkmap_curr_dev;
+
+struct map_ctx {
+	struct udevice *dev;
+	lbaint_t blknr, blkcnt;
+};
+
+typedef int (*map_parser_fn)(struct map_ctx *ctx, int argc, char *const argv[]);
+
+struct map_handler {
+	const char *name;
+	map_parser_fn fn;
+};
+
+int do_blkmap_map_linear(struct map_ctx *ctx, int argc, char *const argv[])
+{
+	struct blk_desc *lbd;
+	int err, ldevnum;
+	lbaint_t lblknr;
+
+	if (argc < 4)
+		return CMD_RET_USAGE;
+
+	ldevnum = dectoul(argv[2], NULL);
+	lblknr = dectoul(argv[3], NULL);
+
+	lbd = blk_get_devnum_by_uclass_idname(argv[1], ldevnum);
+	if (!lbd) {
+		printf("Found no device matching \"%s %d\"\n",
+		       argv[1], ldevnum);
+		return CMD_RET_FAILURE;
+	}
+
+	err = blkmap_map_linear(ctx->dev, ctx->blknr, ctx->blkcnt,
+				lbd->bdev, lblknr);
+	if (err) {
+		printf("Unable to map \"%s %d\" at block 0x" LBAF ": %d\n",
+		       argv[1], ldevnum, ctx->blknr, err);
+
+		return CMD_RET_FAILURE;
+	}
+
+	printf("Block 0x" LBAF "+0x" LBAF " mapped to block 0x" LBAF " of \"%s %d\"\n",
+	       ctx->blknr, ctx->blkcnt, lblknr, argv[1], ldevnum);
+	return CMD_RET_SUCCESS;
+}
+
+int do_blkmap_map_mem(struct map_ctx *ctx, int argc, char *const argv[])
+{
+	phys_addr_t addr;
+	int err;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	addr = hextoul(argv[1], NULL);
+
+	err = blkmap_map_pmem(ctx->dev, ctx->blknr, ctx->blkcnt, addr);
+	if (err) {
+		printf("Unable to map %#llx at block 0x" LBAF ": %d\n",
+		       (unsigned long long)addr, ctx->blknr, err);
+		return CMD_RET_FAILURE;
+	}
+
+	printf("Block 0x" LBAF "+0x" LBAF " mapped to %#llx\n",
+	       ctx->blknr, ctx->blkcnt, (unsigned long long)addr);
+	return CMD_RET_SUCCESS;
+}
+
+struct map_handler map_handlers[] = {
+	{ .name = "linear", .fn = do_blkmap_map_linear },
+	{ .name = "mem", .fn = do_blkmap_map_mem },
+
+	{ .name = NULL }
+};
+
+static int do_blkmap_map(struct cmd_tbl *cmdtp, int flag,
+			 int argc, char *const argv[])
+{
+	struct map_handler *handler;
+	struct map_ctx ctx;
+
+	if (argc < 5)
+		return CMD_RET_USAGE;
+
+	ctx.dev = blkmap_from_label(argv[1]);
+	if (!ctx.dev) {
+		printf("\"%s\" is not the name of any known blkmap\n", argv[1]);
+		return CMD_RET_FAILURE;
+	}
+
+	ctx.blknr = hextoul(argv[2], NULL);
+	ctx.blkcnt = hextoul(argv[3], NULL);
+	argc -= 4;
+	argv += 4;
+
+	for (handler = map_handlers; handler->name; handler++) {
+		if (!strcmp(handler->name, argv[0]))
+			return handler->fn(&ctx, argc, argv);
+	}
+
+	printf("Unknown map type \"%s\"\n", argv[0]);
+	return CMD_RET_USAGE;
+}
+
+static int do_blkmap_create(struct cmd_tbl *cmdtp, int flag,
+			    int argc, char *const argv[])
+{
+	const char *label;
+	int err;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	label = argv[1];
+
+	err = blkmap_create(label, NULL);
+	if (err) {
+		printf("Unable to create \"%s\": %d\n", label, err);
+		return CMD_RET_FAILURE;
+	}
+
+	printf("Created \"%s\"\n", label);
+	return CMD_RET_SUCCESS;
+}
+
+static int do_blkmap_destroy(struct cmd_tbl *cmdtp, int flag,
+			     int argc, char *const argv[])
+{
+	struct udevice *dev;
+	const char *label;
+	int err;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	label = argv[1];
+
+	dev = blkmap_from_label(label);
+	if (!dev) {
+		printf("\"%s\" is not the name of any known blkmap\n", label);
+		return CMD_RET_FAILURE;
+	}
+
+	err = blkmap_destroy(dev);
+	if (err) {
+		printf("Unable to destroy \"%s\": %d\n", label, err);
+		return CMD_RET_FAILURE;
+	}
+
+	printf("Destroyed \"%s\"\n", label);
+	return CMD_RET_SUCCESS;
+}
+
+static int do_blkmap_get(struct cmd_tbl *cmdtp, int flag,
+			 int argc, char *const argv[])
+{
+	struct udevice *dev;
+	const char *label;
+	int err;
+
+	if (argc < 3)
+		return CMD_RET_USAGE;
+
+	label = argv[1];
+
+	dev = blkmap_from_label(label);
+	if (!dev) {
+		printf("\"%s\" is not the name of any known blkmap\n", label);
+		return CMD_RET_FAILURE;
+	}
+
+	if (!strcmp(argv[2], "dev")) {
+		if (argc == 3) {
+			printf("%d\n", dev_seq(dev));
+		} else {
+			err = env_set_hex(argv[3], dev_seq(dev));
+			if (err)
+				return CMD_RET_FAILURE;
+		}
+	} else {
+		return CMD_RET_USAGE;
+	}
+
+	return CMD_RET_SUCCESS;
+}
+
+static int do_blkmap_common(struct cmd_tbl *cmdtp, int flag,
+			    int argc, char *const argv[])
+{
+	/* The subcommand parsing pops the original argv[0] ("blkmap")
+	 * which blk_common_cmd expects. Push it back again.
+	 */
+	argc++;
+	argv--;
+
+	return blk_common_cmd(argc, argv, UCLASS_BLKMAP, &blkmap_curr_dev);
+}
+
+U_BOOT_CMD_WITH_SUBCMDS(
+	blkmap, "Composeable virtual block devices",
+	"info - list configured devices\n"
+	"blkmap part - list available partitions on current blkmap device\n"
+	"blkmap dev [<dev>] - show or set current blkmap device\n"
+	"blkmap read <addr> <blk#> <cnt>\n"
+	"blkmap write <addr> <blk#> <cnt>\n"
+	"blkmap get <label> dev [<var>] - store device number in variable\n"
+	"blkmap create <label> - create device\n"
+	"blkmap destroy <label> - destroy device\n"
+	"blkmap map <label> <blk#> <cnt> linear <interface> <dev> <blk#> - device mapping\n"
+	"blkmap map <label> <blk#> <cnt> mem <addr> - memory mapping\n",
+	U_BOOT_SUBCMD_MKENT(info, 2, 1, do_blkmap_common),
+	U_BOOT_SUBCMD_MKENT(part, 2, 1, do_blkmap_common),
+	U_BOOT_SUBCMD_MKENT(dev, 4, 1, do_blkmap_common),
+	U_BOOT_SUBCMD_MKENT(read, 5, 1, do_blkmap_common),
+	U_BOOT_SUBCMD_MKENT(write, 5, 1, do_blkmap_common),
+	U_BOOT_SUBCMD_MKENT(get, 5, 1, do_blkmap_get),
+	U_BOOT_SUBCMD_MKENT(create, 2, 1, do_blkmap_create),
+	U_BOOT_SUBCMD_MKENT(destroy, 2, 1, do_blkmap_destroy),
+	U_BOOT_SUBCMD_MKENT(map, 32, 1, do_blkmap_map));
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index cbace25..3a1f14c 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -147,6 +147,7 @@
 CONFIG_ADC_SANDBOX=y
 CONFIG_AXI=y
 CONFIG_AXI_SANDBOX=y
+CONFIG_BLKMAP=y
 CONFIG_SYS_IDE_MAXBUS=1
 CONFIG_SYS_ATA_BASE_ADDR=0x100
 CONFIG_SYS_ATA_STRIDE=4
diff --git a/disk/part.c b/disk/part.c
index d449635..35300df5 100644
--- a/disk/part.c
+++ b/disk/part.c
@@ -140,6 +140,7 @@
 	case UCLASS_NVME:
 	case UCLASS_PVBLOCK:
 	case UCLASS_HOST:
+	case UCLASS_BLKMAP:
 		printf ("Vendor: %s Rev: %s Prod: %s\n",
 			dev_desc->vendor,
 			dev_desc->revision,
diff --git a/doc/usage/blkmap.rst b/doc/usage/blkmap.rst
new file mode 100644
index 0000000..dbfa8e5
--- /dev/null
+++ b/doc/usage/blkmap.rst
@@ -0,0 +1,111 @@
+.. SPDX-License-Identifier: GPL-2.0+
+..
+.. Copyright (c) 2023 Addiva Elektronik
+.. Author: Tobias Waldekranz <tobias@waldekranz.com>
+
+Block Maps (blkmap)
+===================
+
+Block maps are a way of looking at various sources of data through the
+lens of a regular block device. It lets you treat devices that are not
+block devices, like RAM, as if they were. It also lets you export a
+slice of an existing block device, which does not have to correspond
+to a partition boundary, as a new block device.
+
+This is primarily useful because U-Boot's filesystem drivers only
+operate on block devices, so a block map lets you access filesystems
+wherever they might be located.
+
+The implementation is loosely modeled on Linux's "Device Mapper"
+subsystem, see `kernel documentation`_ for more information.
+
+.. _kernel documentation: https://docs.kernel.org/admin-guide/device-mapper/index.html
+
+
+Example: Netbooting an Ext4 Image
+---------------------------------
+
+Say that our system is using an Ext4 filesystem as its rootfs, where
+the kernel is stored in ``/boot``. This image is then typically stored
+in an eMMC partition. In this configuration, we can use something like
+``load mmc 0 ${kernel_addr_r} /boot/Image`` to load the kernel image
+into the expected location, and then boot the system. No problems.
+
+Now imagine that during development, or as a recovery mechanism, we
+want to boot the same type of image by downloading it over the
+network. Getting the image to the target is easy enough:
+
+::
+
+   dhcp ${ramdisk_addr_r} rootfs.ext4
+
+But now we are faced with a predicament: how to we extract the kernel
+image? Block maps to the rescue!
+
+We start by creating a new device:
+
+::
+
+   blkmap create netboot
+
+Before setting up the mapping, we figure out the size of the
+downloaded file, in blocks:
+
+::
+
+   setexpr fileblks ${filesize} + 0x1ff
+   setexpr fileblks ${filesize} / 0x200
+
+Then we can add a mapping to the start of our device, backed by the
+memory at `${loadaddr}`:
+
+::
+
+   blkmap map netboot 0 ${fileblks} mem ${fileaddr}
+
+Now we can access the filesystem via the virtual device:
+
+::
+
+   blkmap get netboot dev devnum
+   load blkmap ${devnum} ${kernel_addr_r} /boot/Image
+
+
+Example: Accessing a filesystem inside an FIT image
+---------------------------------------------------
+
+In this example, an FIT image is stored in an eMMC partition. We would
+like to read the file ``/etc/version``, stored inside a Squashfs image
+in the FIT. Since the Squashfs image is not stored on a partition
+boundary, there is no way of accessing it via ``load mmc ...``.
+
+What we can to instead is to first figure out the offset and size of
+the filesystem:
+
+::
+
+   mmc dev 0
+   mmc read ${loadaddr} 0 0x100
+
+   fdt addr ${loadaddr}
+   fdt get value squashaddr /images/ramdisk data-position
+   fdt get value squashsize /images/ramdisk data-size
+
+   setexpr squashblk  ${squashaddr} / 0x200
+   setexpr squashsize ${squashsize} + 0x1ff
+   setexpr squashsize ${squashsize} / 0x200
+
+Then we can create a block map that maps to that slice of the full
+partition:
+
+::
+
+   blkmap create sq
+   blkmap map sq 0 ${squashsize} linear mmc 0 ${squashblk}
+
+Now we can access the filesystem:
+
+::
+
+   blkmap get sq dev devnum
+   load blkmap ${devnum} ${loadaddr} /etc/version
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index bc85e1d..729541b 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -4,6 +4,7 @@
 .. toctree::
    :maxdepth: 1
 
+   blkmap
    dfu
    environment
    fdt_overlays
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index e95da48..5a1aeb3d 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -67,6 +67,24 @@
 	  it will prevent repeated reads from directory structures and other
 	  filesystem data structures.
 
+config BLKMAP
+	bool "Composable virtual block devices (blkmap)"
+	depends on BLK
+	help
+ 	  Create virtual block devices that are backed by various sources,
+ 	  e.g. RAM, or parts of an existing block device. Though much more
+ 	  rudimentary, it borrows a lot of ideas from Linux's device mapper
+ 	  subsystem.
+
+	  Example use-cases:
+	  - Treat a region of RAM as a block device, i.e. a RAM disk. This let's
+            you extract files from filesystem images stored in RAM (perhaps as a
+            result of a TFTP transfer).
+	  - Create a virtual partition on an existing device. This let's you
+            access filesystems that aren't stored at an exact partition
+            boundary. A common example is a filesystem image embedded in an FIT
+            image.
+
 config SPL_BLOCK_CACHE
 	bool "Use block device cache in SPL"
 	depends on SPL_BLK
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index f12447d..a161d14 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -14,6 +14,7 @@
 endif
 obj-$(CONFIG_SANDBOX) += sandbox.o host-uclass.o host_dev.o
 obj-$(CONFIG_$(SPL_TPL_)BLOCK_CACHE) += blkcache.o
+obj-$(CONFIG_BLKMAP) += blkmap.o
 
 obj-$(CONFIG_EFI_MEDIA) += efi-media-uclass.o
 obj-$(CONFIG_EFI_MEDIA_SANDBOX) += sb_efi_media.o
diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c
index c69fc4d..cb73faa 100644
--- a/drivers/block/blk-uclass.c
+++ b/drivers/block/blk-uclass.c
@@ -32,6 +32,7 @@
 	{ UCLASS_EFI_LOADER, "efiloader" },
 	{ UCLASS_VIRTIO, "virtio" },
 	{ UCLASS_PVBLOCK, "pvblock" },
+	{ UCLASS_BLKMAP, "blkmap" },
 };
 
 static enum uclass_id uclass_name_to_iftype(const char *uclass_idname)
diff --git a/drivers/block/blkmap.c b/drivers/block/blkmap.c
new file mode 100644
index 0000000..2bb0acc
--- /dev/null
+++ b/drivers/block/blkmap.c
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Addiva Elektronik
+ * Author: Tobias Waldekranz <tobias@waldekranz.com>
+ */
+
+#include <common.h>
+#include <blk.h>
+#include <blkmap.h>
+#include <dm.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <part.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/root.h>
+
+struct blkmap;
+
+/**
+ * struct blkmap_slice - Region mapped to a blkmap
+ *
+ * Common data for a region mapped to a blkmap, specialized by each
+ * map type.
+ *
+ * @node: List node used to associate this slice with a blkmap
+ * @blknr: Start block number of the mapping
+ * @blkcnt: Number of blocks covered by this mapping
+ */
+struct blkmap_slice {
+	struct list_head node;
+
+	lbaint_t blknr;
+	lbaint_t blkcnt;
+
+	/**
+	 * @read: - Read from slice
+	 *
+	 * @read.bm: Blkmap to which this slice belongs
+	 * @read.bms: This slice
+	 * @read.blknr: Start block number to read from
+	 * @read.blkcnt: Number of blocks to read
+	 * @read.buffer: Buffer to store read data to
+	 */
+	ulong (*read)(struct blkmap *bm, struct blkmap_slice *bms,
+		      lbaint_t blknr, lbaint_t blkcnt, void *buffer);
+
+	/**
+	 * @write: - Write to slice
+	 *
+	 * @write.bm: Blkmap to which this slice belongs
+	 * @write.bms: This slice
+	 * @write.blknr: Start block number to write to
+	 * @write.blkcnt: Number of blocks to write
+	 * @write.buffer: Data to be written
+	 */
+	ulong (*write)(struct blkmap *bm, struct blkmap_slice *bms,
+		       lbaint_t blknr, lbaint_t blkcnt, const void *buffer);
+
+	/**
+	 * @destroy: - Tear down slice
+	 *
+	 * @read.bm: Blkmap to which this slice belongs
+	 * @read.bms: This slice
+	 */
+	void (*destroy)(struct blkmap *bm, struct blkmap_slice *bms);
+};
+
+/**
+ * struct blkmap - Block map
+ *
+ * Data associated with a blkmap.
+ *
+ * @label: Human readable name of this blkmap
+ * @blk: Underlying block device
+ * @slices: List of slices associated with this blkmap
+ */
+struct blkmap {
+	char *label;
+	struct udevice *blk;
+	struct list_head slices;
+};
+
+static bool blkmap_slice_contains(struct blkmap_slice *bms, lbaint_t blknr)
+{
+	return (blknr >= bms->blknr) && (blknr < (bms->blknr + bms->blkcnt));
+}
+
+static bool blkmap_slice_available(struct blkmap *bm, struct blkmap_slice *new)
+{
+	struct blkmap_slice *bms;
+	lbaint_t first, last;
+
+	first = new->blknr;
+	last = new->blknr + new->blkcnt - 1;
+
+	list_for_each_entry(bms, &bm->slices, node) {
+		if (blkmap_slice_contains(bms, first) ||
+		    blkmap_slice_contains(bms, last) ||
+		    blkmap_slice_contains(new, bms->blknr) ||
+		    blkmap_slice_contains(new, bms->blknr + bms->blkcnt - 1))
+			return false;
+	}
+
+	return true;
+}
+
+static int blkmap_slice_add(struct blkmap *bm, struct blkmap_slice *new)
+{
+	struct blk_desc *bd = dev_get_uclass_plat(bm->blk);
+	struct list_head *insert = &bm->slices;
+	struct blkmap_slice *bms;
+
+	if (!blkmap_slice_available(bm, new))
+		return -EBUSY;
+
+	list_for_each_entry(bms, &bm->slices, node) {
+		if (bms->blknr < new->blknr)
+			continue;
+
+		insert = &bms->node;
+		break;
+	}
+
+	list_add_tail(&new->node, insert);
+
+	/* Disk might have grown, update the size */
+	bms = list_last_entry(&bm->slices, struct blkmap_slice, node);
+	bd->lba = bms->blknr + bms->blkcnt;
+	return 0;
+}
+
+/**
+ * struct blkmap_linear - Linear mapping to other block device
+ *
+ * @slice: Common map data
+ * @blk: Target block device of this mapping
+ * @blknr: Start block number of the target device
+ */
+struct blkmap_linear {
+	struct blkmap_slice slice;
+
+	struct udevice *blk;
+	lbaint_t blknr;
+};
+
+static ulong blkmap_linear_read(struct blkmap *bm, struct blkmap_slice *bms,
+				lbaint_t blknr, lbaint_t blkcnt, void *buffer)
+{
+	struct blkmap_linear *bml = container_of(bms, struct blkmap_linear, slice);
+
+	return blk_read(bml->blk, bml->blknr + blknr, blkcnt, buffer);
+}
+
+static ulong blkmap_linear_write(struct blkmap *bm, struct blkmap_slice *bms,
+				 lbaint_t blknr, lbaint_t blkcnt,
+				 const void *buffer)
+{
+	struct blkmap_linear *bml = container_of(bms, struct blkmap_linear, slice);
+
+	return blk_write(bml->blk, bml->blknr + blknr, blkcnt, buffer);
+}
+
+int blkmap_map_linear(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt,
+		      struct udevice *lblk, lbaint_t lblknr)
+{
+	struct blkmap *bm = dev_get_plat(dev);
+	struct blkmap_linear *linear;
+	struct blk_desc *bd, *lbd;
+	int err;
+
+	bd = dev_get_uclass_plat(bm->blk);
+	lbd = dev_get_uclass_plat(lblk);
+	if (lbd->blksz != bd->blksz)
+		/* We could support block size translation, but we
+		 * don't yet.
+		 */
+		return -EINVAL;
+
+	linear = malloc(sizeof(*linear));
+	if (!linear)
+		return -ENOMEM;
+
+	*linear = (struct blkmap_linear) {
+		.slice = {
+			.blknr = blknr,
+			.blkcnt = blkcnt,
+
+			.read = blkmap_linear_read,
+			.write = blkmap_linear_write,
+		},
+
+		.blk = lblk,
+		.blknr = lblknr,
+	};
+
+	err = blkmap_slice_add(bm, &linear->slice);
+	if (err)
+		free(linear);
+
+	return err;
+}
+
+/**
+ * struct blkmap_mem - Memory mapping
+ *
+ * @slice: Common map data
+ * @addr: Target memory region of this mapping
+ * @remapped: True if @addr is backed by a physical to virtual memory
+ * mapping that must be torn down at the end of this mapping's
+ * lifetime.
+ */
+struct blkmap_mem {
+	struct blkmap_slice slice;
+	void *addr;
+	bool remapped;
+};
+
+static ulong blkmap_mem_read(struct blkmap *bm, struct blkmap_slice *bms,
+			     lbaint_t blknr, lbaint_t blkcnt, void *buffer)
+{
+	struct blkmap_mem *bmm = container_of(bms, struct blkmap_mem, slice);
+	struct blk_desc *bd = dev_get_uclass_plat(bm->blk);
+	char *src;
+
+	src = bmm->addr + (blknr << bd->log2blksz);
+	memcpy(buffer, src, blkcnt << bd->log2blksz);
+	return blkcnt;
+}
+
+static ulong blkmap_mem_write(struct blkmap *bm, struct blkmap_slice *bms,
+			      lbaint_t blknr, lbaint_t blkcnt,
+			      const void *buffer)
+{
+	struct blkmap_mem *bmm = container_of(bms, struct blkmap_mem, slice);
+	struct blk_desc *bd = dev_get_uclass_plat(bm->blk);
+	char *dst;
+
+	dst = bmm->addr + (blknr << bd->log2blksz);
+	memcpy(dst, buffer, blkcnt << bd->log2blksz);
+	return blkcnt;
+}
+
+static void blkmap_mem_destroy(struct blkmap *bm, struct blkmap_slice *bms)
+{
+	struct blkmap_mem *bmm = container_of(bms, struct blkmap_mem, slice);
+
+	if (bmm->remapped)
+		unmap_sysmem(bmm->addr);
+}
+
+int __blkmap_map_mem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt,
+		     void *addr, bool remapped)
+{
+	struct blkmap *bm = dev_get_plat(dev);
+	struct blkmap_mem *bmm;
+	int err;
+
+	bmm = malloc(sizeof(*bmm));
+	if (!bmm)
+		return -ENOMEM;
+
+	*bmm = (struct blkmap_mem) {
+		.slice = {
+			.blknr = blknr,
+			.blkcnt = blkcnt,
+
+			.read = blkmap_mem_read,
+			.write = blkmap_mem_write,
+			.destroy = blkmap_mem_destroy,
+		},
+
+		.addr = addr,
+		.remapped = remapped,
+	};
+
+	err = blkmap_slice_add(bm, &bmm->slice);
+	if (err)
+		free(bmm);
+
+	return err;
+}
+
+int blkmap_map_mem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt,
+		   void *addr)
+{
+	return __blkmap_map_mem(dev, blknr, blkcnt, addr, false);
+}
+
+int blkmap_map_pmem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt,
+		    phys_addr_t paddr)
+{
+	struct blkmap *bm = dev_get_plat(dev);
+	struct blk_desc *bd = dev_get_uclass_plat(bm->blk);
+	void *addr;
+	int err;
+
+	addr = map_sysmem(paddr, blkcnt << bd->log2blksz);
+	if (!addr)
+		return -ENOMEM;
+
+	err = __blkmap_map_mem(dev, blknr, blkcnt, addr, true);
+	if (err)
+		unmap_sysmem(addr);
+
+	return err;
+}
+
+static ulong blkmap_blk_read_slice(struct blkmap *bm, struct blkmap_slice *bms,
+				   lbaint_t blknr, lbaint_t blkcnt,
+				   void *buffer)
+{
+	lbaint_t nr, cnt;
+
+	nr = blknr - bms->blknr;
+	cnt = (blkcnt < bms->blkcnt) ? blkcnt : bms->blkcnt;
+	return bms->read(bm, bms, nr, cnt, buffer);
+}
+
+static ulong blkmap_blk_read(struct udevice *dev, lbaint_t blknr,
+			     lbaint_t blkcnt, void *buffer)
+{
+	struct blk_desc *bd = dev_get_uclass_plat(dev);
+	struct blkmap *bm = dev_get_plat(dev->parent);
+	struct blkmap_slice *bms;
+	lbaint_t cnt, total = 0;
+
+	list_for_each_entry(bms, &bm->slices, node) {
+		if (!blkmap_slice_contains(bms, blknr))
+			continue;
+
+		cnt = blkmap_blk_read_slice(bm, bms, blknr, blkcnt, buffer);
+		blknr += cnt;
+		blkcnt -= cnt;
+		buffer += cnt << bd->log2blksz;
+		total += cnt;
+	}
+
+	return total;
+}
+
+static ulong blkmap_blk_write_slice(struct blkmap *bm, struct blkmap_slice *bms,
+				    lbaint_t blknr, lbaint_t blkcnt,
+				    const void *buffer)
+{
+	lbaint_t nr, cnt;
+
+	nr = blknr - bms->blknr;
+	cnt = (blkcnt < bms->blkcnt) ? blkcnt : bms->blkcnt;
+	return bms->write(bm, bms, nr, cnt, buffer);
+}
+
+static ulong blkmap_blk_write(struct udevice *dev, lbaint_t blknr,
+			      lbaint_t blkcnt, const void *buffer)
+{
+	struct blk_desc *bd = dev_get_uclass_plat(dev);
+	struct blkmap *bm = dev_get_plat(dev->parent);
+	struct blkmap_slice *bms;
+	lbaint_t cnt, total = 0;
+
+	list_for_each_entry(bms, &bm->slices, node) {
+		if (!blkmap_slice_contains(bms, blknr))
+			continue;
+
+		cnt = blkmap_blk_write_slice(bm, bms, blknr, blkcnt, buffer);
+		blknr += cnt;
+		blkcnt -= cnt;
+		buffer += cnt << bd->log2blksz;
+		total += cnt;
+	}
+
+	return total;
+}
+
+static const struct blk_ops blkmap_blk_ops = {
+	.read	= blkmap_blk_read,
+	.write	= blkmap_blk_write,
+};
+
+U_BOOT_DRIVER(blkmap_blk) = {
+	.name		= "blkmap_blk",
+	.id		= UCLASS_BLK,
+	.ops		= &blkmap_blk_ops,
+};
+
+int blkmap_dev_bind(struct udevice *dev)
+{
+	struct blkmap *bm = dev_get_plat(dev);
+	struct blk_desc *bd;
+	int err;
+
+	err = blk_create_devicef(dev, "blkmap_blk", "blk", UCLASS_BLKMAP,
+				 dev_seq(dev), 512, 0, &bm->blk);
+	if (err)
+		return log_msg_ret("blk", err);
+
+	INIT_LIST_HEAD(&bm->slices);
+
+	bd = dev_get_uclass_plat(bm->blk);
+	snprintf(bd->vendor, BLK_VEN_SIZE, "U-Boot");
+	snprintf(bd->product, BLK_PRD_SIZE, "blkmap");
+	snprintf(bd->revision, BLK_REV_SIZE, "1.0");
+
+	/* EFI core isn't keen on zero-sized disks, so we lie. This is
+	 * updated with the correct size once the user adds a
+	 * mapping.
+	 */
+	bd->lba = 1;
+
+	return 0;
+}
+
+int blkmap_dev_unbind(struct udevice *dev)
+{
+	struct blkmap *bm = dev_get_plat(dev);
+	struct blkmap_slice *bms, *tmp;
+	int err;
+
+	list_for_each_entry_safe(bms, tmp, &bm->slices, node) {
+		list_del(&bms->node);
+		free(bms);
+	}
+
+	err = device_remove(bm->blk, DM_REMOVE_NORMAL);
+	if (err)
+		return err;
+
+	return device_unbind(bm->blk);
+}
+
+U_BOOT_DRIVER(blkmap_root) = {
+	.name		= "blkmap_dev",
+	.id		= UCLASS_BLKMAP,
+	.bind		= blkmap_dev_bind,
+	.unbind		= blkmap_dev_unbind,
+	.plat_auto	= sizeof(struct blkmap),
+};
+
+struct udevice *blkmap_from_label(const char *label)
+{
+	struct udevice *dev;
+	struct uclass *uc;
+	struct blkmap *bm;
+
+	uclass_id_foreach_dev(UCLASS_BLKMAP, dev, uc) {
+		bm = dev_get_plat(dev);
+		if (bm->label && !strcmp(label, bm->label))
+			return dev;
+	}
+
+	return NULL;
+}
+
+int blkmap_create(const char *label, struct udevice **devp)
+{
+	char *hname, *hlabel;
+	struct udevice *dev;
+	struct blkmap *bm;
+	size_t namelen;
+	int err;
+
+	dev = blkmap_from_label(label);
+	if (dev) {
+		err = -EBUSY;
+		goto err;
+	}
+
+	hlabel = strdup(label);
+	if (!hlabel) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	namelen = strlen("blkmap-") + strlen(label) + 1;
+	hname = malloc(namelen);
+	if (!hname) {
+		err = -ENOMEM;
+		goto err_free_hlabel;
+	}
+
+	strlcpy(hname, "blkmap-", namelen);
+	strlcat(hname, label, namelen);
+
+	err = device_bind_driver(dm_root(), "blkmap_dev", hname, &dev);
+	if (err)
+		goto err_free_hname;
+
+	device_set_name_alloced(dev);
+	bm = dev_get_plat(dev);
+	bm->label = hlabel;
+
+	if (devp)
+		*devp = dev;
+
+	return 0;
+
+err_free_hname:
+	free(hname);
+err_free_hlabel:
+	free(hlabel);
+err:
+	return err;
+}
+
+int blkmap_destroy(struct udevice *dev)
+{
+	int err;
+
+	err = device_remove(dev, DM_REMOVE_NORMAL);
+	if (err)
+		return err;
+
+	return device_unbind(dev);
+}
+
+UCLASS_DRIVER(blkmap) = {
+	.id		= UCLASS_BLKMAP,
+	.name		= "blkmap",
+};
diff --git a/include/blkmap.h b/include/blkmap.h
new file mode 100644
index 0000000..af54583
--- /dev/null
+++ b/include/blkmap.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2023 Addiva Elektronik
+ * Author: Tobias Waldekranz <tobias@waldekranz.com>
+ */
+
+#ifndef _BLKMAP_H
+#define _BLKMAP_H
+
+/**
+ * blkmap_map_linear() - Map region of other block device
+ *
+ * @dev: Blkmap to create the mapping on
+ * @blknr: Start block number of the mapping
+ * @blkcnt: Number of blocks to map
+ * @lblk: The target block device of the mapping
+ * @lblknr: The start block number of the target device
+ * Returns: 0 on success, negative error code on failure
+ */
+int blkmap_map_linear(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt,
+		      struct udevice *lblk, lbaint_t lblknr);
+
+/**
+ * blkmap_map_mem() - Map region of memory
+ *
+ * @dev: Blkmap to create the mapping on
+ * @blknr: Start block number of the mapping
+ * @blkcnt: Number of blocks to map
+ * @addr: The target memory address of the mapping
+ * Returns: 0 on success, negative error code on failure
+ */
+int blkmap_map_mem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt,
+		   void *addr);
+
+/**
+ * blkmap_map_pmem() - Map region of physical memory
+ *
+ * Ensures that a valid physical to virtual memory mapping for the
+ * requested region is valid for the lifetime of the mapping, on
+ * architectures that require it (sandbox).
+ *
+ * @dev: Blkmap to create the mapping on
+ * @blknr: Start block number of the mapping
+ * @blkcnt: Number of blocks to map
+ * @paddr: The target physical memory address of the mapping
+ * Returns: 0 on success, negative error code on failure
+ */
+int blkmap_map_pmem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt,
+		    phys_addr_t paddr);
+
+
+/**
+ * blkmap_from_label() - Find blkmap from label
+ *
+ * @label: Label of the requested blkmap
+ * Returns: A pointer to the blkmap on success, NULL on failure
+ */
+struct udevice *blkmap_from_label(const char *label);
+
+/**
+ * blkmap_create() - Create new blkmap
+ *
+ * @label: Label of the new blkmap
+ * @devp: If not NULL, updated with the address of the resulting device
+ * Returns: 0 on success, negative error code on failure
+ */
+int blkmap_create(const char *label, struct udevice **devp);
+
+/**
+ * blkmap_destroy() - Destroy blkmap
+ *
+ * @dev: The blkmap to be destroyed
+ * Returns: 0 on success, negative error code on failure
+ */
+int blkmap_destroy(struct udevice *dev);
+
+#endif	/* _BLKMAP_H */
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 33e43c2..576237b 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -37,6 +37,7 @@
 	UCLASS_AUDIO_CODEC,	/* Audio codec with control and data path */
 	UCLASS_AXI,		/* AXI bus */
 	UCLASS_BLK,		/* Block device */
+	UCLASS_BLKMAP,		/* Composable virtual block device */
 	UCLASS_BOOTCOUNT,       /* Bootcount backing store */
 	UCLASS_BOOTDEV,		/* Boot device for locating an OS to boot */
 	UCLASS_BOOTMETH,	/* Bootmethod for booting an OS */
diff --git a/include/efi_loader.h b/include/efi_loader.h
index cee04cb..4b89b981 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -134,6 +134,10 @@
 #define U_BOOT_GUID \
 	EFI_GUID(0xe61d73b9, 0xa384, 0x4acc, \
 		 0xae, 0xab, 0x82, 0xe8, 0x28, 0xf3, 0x62, 0x8b)
+/* GUID used as root for blkmap devices */
+#define U_BOOT_BLKMAP_DEV_GUID \
+	EFI_GUID(0x4cad859d, 0xd644, 0x42ff,	\
+		 0x87, 0x0b, 0xc0, 0x2e, 0xac, 0x05, 0x58, 0x63)
 /* GUID used as host device on sandbox */
 #define U_BOOT_HOST_DEV_GUID \
 	EFI_GUID(0xbbe4e671, 0x5773, 0x4ea1, \
diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c
index ea09783..e2e98a3 100644
--- a/lib/efi_loader/efi_device_path.c
+++ b/lib/efi_loader/efi_device_path.c
@@ -21,6 +21,9 @@
 #include <asm-generic/unaligned.h>
 #include <linux/compat.h> /* U16_MAX */
 
+#ifdef CONFIG_BLKMAP
+const efi_guid_t efi_guid_blkmap_dev = U_BOOT_BLKMAP_DEV_GUID;
+#endif
 #ifdef CONFIG_SANDBOX
 const efi_guid_t efi_guid_host_dev = U_BOOT_HOST_DEV_GUID;
 #endif
@@ -556,6 +559,16 @@
 			return dp_size(dev->parent)
 				+ sizeof(struct efi_device_path_vendor) + 1;
 #endif
+#ifdef CONFIG_BLKMAP
+		case UCLASS_BLKMAP:
+			 /*
+			  * blkmap devices will be represented as a vendor
+			  * device node with an extra byte for the device
+			  * number.
+			  */
+			return dp_size(dev->parent)
+				+ sizeof(struct efi_device_path_vendor) + 1;
+#endif
 		default:
 			return dp_size(dev->parent);
 		}
@@ -613,6 +626,23 @@
 #endif
 	case UCLASS_BLK:
 		switch (dev->parent->uclass->uc_drv->id) {
+#ifdef CONFIG_BLKMAP
+		case UCLASS_BLKMAP: {
+			struct efi_device_path_vendor *dp;
+			struct blk_desc *desc = dev_get_uclass_plat(dev);
+
+			dp_fill(buf, dev->parent);
+			dp = buf;
+			++dp;
+			dp->dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE;
+			dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_VENDOR;
+			dp->dp.length = sizeof(*dp) + 1;
+			memcpy(&dp->guid, &efi_guid_blkmap_dev,
+			       sizeof(efi_guid_t));
+			dp->vendor_data[0] = desc->devnum;
+			return &dp->vendor_data[1];
+			}
+#endif
 #ifdef CONFIG_SANDBOX
 		case UCLASS_HOST: {
 			/* stop traversing parents at this point: */
diff --git a/test/dm/Makefile b/test/dm/Makefile
index 7a79b6e..e15bdbf 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -29,6 +29,7 @@
 obj-$(CONFIG_SOUND) += audio.o
 obj-$(CONFIG_AXI) += axi.o
 obj-$(CONFIG_BLK) += blk.o
+obj-$(CONFIG_BLKMAP) += blkmap.o
 obj-$(CONFIG_BUTTON) += button.o
 obj-$(CONFIG_DM_BOOTCOUNT) += bootcount.o
 obj-$(CONFIG_DM_REBOOT_MODE) += reboot-mode.o
diff --git a/test/dm/blkmap.c b/test/dm/blkmap.c
new file mode 100644
index 0000000..7a163d6
--- /dev/null
+++ b/test/dm/blkmap.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Addiva Elektronik
+ * Author: Tobias Waldekranz <tobias@waldekranz.com>
+ */
+
+#include <common.h>
+#include <blk.h>
+#include <blkmap.h>
+#include <dm.h>
+#include <asm/test.h>
+#include <dm/test.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+#define BLKSZ 0x200
+
+struct mapping {
+	int src;
+	int cnt;
+	int dst;
+};
+
+const struct mapping unordered_mapping[] = {
+	{ 0, 1, 3 },
+	{ 1, 3, 0 },
+	{ 4, 2, 6 },
+	{ 6, 2, 4 },
+
+	{ 0, 0, 0 }
+};
+
+const struct mapping identity_mapping[] = {
+	{ 0, 8, 0 },
+
+	{ 0, 0, 0 }
+};
+
+static char identity[8 * BLKSZ];
+static char unordered[8 * BLKSZ];
+static char buffer[8 * BLKSZ];
+
+static void mkblob(void *base, const struct mapping *m)
+{
+	int nr;
+
+	for (; m->cnt; m++) {
+		for (nr = 0; nr < m->cnt; nr++) {
+			memset(base + (m->dst + nr) * BLKSZ,
+			       m->src + nr, BLKSZ);
+		}
+	}
+}
+
+static int dm_test_blkmap_read(struct unit_test_state *uts)
+{
+	struct udevice *dev, *blk;
+	const struct mapping *m;
+
+	ut_assertok(blkmap_create("rdtest", &dev));
+	ut_assertok(blk_get_from_parent(dev, &blk));
+
+	/* Generate an ordered and an unordered pattern in memory */
+	mkblob(unordered, unordered_mapping);
+	mkblob(identity, identity_mapping);
+
+	/* Create a blkmap that cancels out the disorder */
+	for (m = unordered_mapping; m->cnt; m++) {
+		ut_assertok(blkmap_map_mem(dev, m->src, m->cnt,
+					   unordered + m->dst * BLKSZ));
+	}
+
+	/* Read out the data via the blkmap device to another area,
+	 * and verify that it matches the ordered pattern.
+	 */
+	ut_asserteq(8, blk_read(blk, 0, 8, buffer));
+	ut_assertok(memcmp(buffer, identity, sizeof(buffer)));
+
+	ut_assertok(blkmap_destroy(dev));
+	return 0;
+}
+DM_TEST(dm_test_blkmap_read, 0);
+
+static int dm_test_blkmap_write(struct unit_test_state *uts)
+{
+	struct udevice *dev, *blk;
+	const struct mapping *m;
+
+	ut_assertok(blkmap_create("wrtest", &dev));
+	ut_assertok(blk_get_from_parent(dev, &blk));
+
+	/* Generate an ordered and an unordered pattern in memory */
+	mkblob(unordered, unordered_mapping);
+	mkblob(identity, identity_mapping);
+
+	/* Create a blkmap that mimics the disorder */
+	for (m = unordered_mapping; m->cnt; m++) {
+		ut_assertok(blkmap_map_mem(dev, m->src, m->cnt,
+					   buffer + m->dst * BLKSZ));
+	}
+
+	/* Write the ordered data via the blkmap device to another
+	 * area, and verify that the result matches the unordered
+	 * pattern.
+	 */
+	ut_asserteq(8, blk_write(blk, 0, 8, identity));
+	ut_assertok(memcmp(buffer, unordered, sizeof(buffer)));
+
+	ut_assertok(blkmap_destroy(dev));
+	return 0;
+}
+DM_TEST(dm_test_blkmap_write, 0);
+
+static int dm_test_blkmap_slicing(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+
+	ut_assertok(blkmap_create("slicetest", &dev));
+
+	ut_assertok(blkmap_map_mem(dev, 8, 8, NULL));
+
+	/* Can't overlap on the low end */
+	ut_asserteq(-EBUSY, blkmap_map_mem(dev,  4, 5, NULL));
+	/* Can't be inside */
+	ut_asserteq(-EBUSY, blkmap_map_mem(dev, 10, 2, NULL));
+	/* Can't overlap on the high end */
+	ut_asserteq(-EBUSY, blkmap_map_mem(dev, 15, 4, NULL));
+
+	/* But we should be able to add slices right before and
+	 * after
+	 */
+	ut_assertok(blkmap_map_mem(dev,  4, 4, NULL));
+	ut_assertok(blkmap_map_mem(dev, 16, 4, NULL));
+
+	ut_assertok(blkmap_destroy(dev));
+	return 0;
+}
+DM_TEST(dm_test_blkmap_slicing, 0);
+
+static int dm_test_blkmap_creation(struct unit_test_state *uts)
+{
+	struct udevice *first, *second;
+
+	ut_assertok(blkmap_create("first", &first));
+
+	/* Can't have two "first"s */
+	ut_asserteq(-EBUSY, blkmap_create("first", &second));
+
+	/* But "second" should be fine */
+	ut_assertok(blkmap_create("second", &second));
+
+	/* Once "first" is destroyed, we should be able to create it
+	 * again
+	 */
+	ut_assertok(blkmap_destroy(first));
+	ut_assertok(blkmap_create("first", &first));
+
+	ut_assertok(blkmap_destroy(first));
+	ut_assertok(blkmap_destroy(second));
+	return 0;
+}
+DM_TEST(dm_test_blkmap_creation, 0);
+
+static int dm_test_cmd_blkmap(struct unit_test_state *uts)
+{
+	ulong loadaddr = env_get_hex("loadaddr", 0);
+	struct udevice *dev;
+
+	console_record_reset();
+
+	ut_assertok(run_command("blkmap info", 0));
+	ut_assert_console_end();
+
+	ut_assertok(run_command("blkmap create ramdisk", 0));
+	ut_assert_nextline("Created \"ramdisk\"");
+	ut_assert_console_end();
+
+	ut_assertnonnull((dev = blkmap_from_label("ramdisk")));
+
+	ut_assertok(run_commandf("blkmap map ramdisk 0 800 mem 0x%lx", loadaddr));
+	ut_assert_nextline("Block 0x0+0x800 mapped to 0x%lx", loadaddr);
+	ut_assert_console_end();
+
+	ut_assertok(run_command("blkmap info", 0));
+	ut_assert_nextline("Device 0: Vendor: U-Boot Rev: 1.0 Prod: blkmap");
+	ut_assert_nextline("            Type: Hard Disk");
+	ut_assert_nextline("            Capacity: 1.0 MB = 0.0 GB (2048 x 512)");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("blkmap get ramdisk dev devnum", 0));
+	ut_asserteq(dev_seq(dev), env_get_hex("devnum", 0xdeadbeef));
+
+	ut_assertok(run_command("blkmap destroy ramdisk", 0));
+	ut_assert_nextline("Destroyed \"ramdisk\"");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("blkmap info", 0));
+	ut_assert_console_end();
+	return 0;
+}
+DM_TEST(dm_test_cmd_blkmap, 0);