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
This commit is contained in:
Tom Rini 2023-04-05 18:59:47 -04:00
commit 487e42f7bc
20 changed files with 1242 additions and 5 deletions

View 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

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

View 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

View 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

View 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");

233
cmd/blkmap.c Normal file
View file

@ -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));

View 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

View 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,

111
doc/usage/blkmap.rst Normal file
View file

@ -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

View file

@ -4,6 +4,7 @@ Use U-Boot
.. toctree::
:maxdepth: 1
blkmap
dfu
environment
fdt_overlays

View 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

View 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

View 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)

519
drivers/block/blkmap.c Normal file
View file

@ -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",
};

77
include/blkmap.h Normal file
View file

@ -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 */

View 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 */

View 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, \

View 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
@ -555,6 +558,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: */

View 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

201
test/dm/blkmap.c Normal file
View file

@ -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);