Merge branch '2023-04-05-blkmap-composable-virtual-block-devices'
authorTom Rini <trini@konsulko.com>
Wed, 5 Apr 2023 22:59:47 +0000 (18:59 -0400)
committerTom Rini <trini@konsulko.com>
Wed, 5 Apr 2023 22:59:47 +0000 (18:59 -0400)
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

20 files changed:
MAINTAINERS
boot/image-board.c
cmd/Kconfig
cmd/Makefile
cmd/blk_common.c
cmd/blkmap.c [new file with mode: 0644]
configs/sandbox_defconfig
disk/part.c
doc/usage/blkmap.rst [new file with mode: 0644]
doc/usage/index.rst
drivers/block/Kconfig
drivers/block/Makefile
drivers/block/blk-uclass.c
drivers/block/blkmap.c [new file with mode: 0644]
include/blkmap.h [new file with mode: 0644]
include/dm/uclass-id.h
include/efi_loader.h
lib/efi_loader/efi_device_path.c
test/dm/Makefile
test/dm/blkmap.c [new file with mode: 0644]

index d2e245e..4c17c6c 100644 (file)
@@ -793,6 +793,15 @@ M: Alper Nebi Yasak <alpernebiyasak@gmail.com>
 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
index 7dd0c32..c602832 100644 (file)
@@ -1126,7 +1126,8 @@ fallback:
                        }
 
                        /* 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;
                        }
index 8c9b430..bab35fc 100644 (file)
@@ -1980,6 +1980,25 @@ config CMD_BLOCK_CACHE
          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
index e032091..054ef42 100644 (file)
@@ -27,6 +27,7 @@ obj-$(CONFIG_CMD_BCB) += bcb.o
 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
index 75a072c..9f9d432 100644 (file)
@@ -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 @@ int blk_common_cmd(int argc, char *const argv[], enum uclass_id uclass_id,
 
        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 (file)
index 0000000..b34c013
--- /dev/null
@@ -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));
index cbace25..3a1f14c 100644 (file)
@@ -147,6 +147,7 @@ CONFIG_ADC=y
 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
index d449635..35300df 100644 (file)
@@ -140,6 +140,7 @@ void dev_print(struct blk_desc *dev_desc)
        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 (file)
index 0000000..dbfa8e5
--- /dev/null
@@ -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
index bc85e1d..729541b 100644 (file)
@@ -4,6 +4,7 @@ Use U-Boot
 .. toctree::
    :maxdepth: 1
 
+   blkmap
    dfu
    environment
    fdt_overlays
index e95da48..5a1aeb3 100644 (file)
@@ -67,6 +67,24 @@ config BLOCK_CACHE
          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
index f12447d..a161d14 100644 (file)
@@ -14,6 +14,7 @@ obj-$(CONFIG_IDE) += ide.o
 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
index c69fc4d..cb73faa 100644 (file)
@@ -32,6 +32,7 @@ static struct {
        { 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 (file)
index 0000000..2bb0acc
--- /dev/null
@@ -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 (file)
index 0000000..af54583
--- /dev/null
@@ -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 */
index 33e43c2..576237b 100644 (file)
@@ -37,6 +37,7 @@ enum uclass_id {
        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 */
index cee04cb..4b89b98 100644 (file)
@@ -134,6 +134,10 @@ static inline efi_status_t efi_launch_capsules(void)
 #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, \
index ea09783..e2e98a3 100644 (file)
@@ -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 @@ __maybe_unused static unsigned int dp_size(struct udevice *dev)
                        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 @@ __maybe_unused static void *dp_fill(void *buf, struct udevice *dev)
 #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: */
index 7a79b6e..e15bdbf 100644 (file)
@@ -29,6 +29,7 @@ obj-$(CONFIG_ADC) += adc.o
 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 (file)
index 0000000..7a163d6
--- /dev/null
@@ -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);