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:
commit
487e42f7bc
20 changed files with 1242 additions and 5 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
19
cmd/Kconfig
19
cmd/Kconfig
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
233
cmd/blkmap.c
Normal 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));
|
|
@ -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
|
||||
|
|
|
@ -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
111
doc/usage/blkmap.rst
Normal 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
|
|
@ -4,6 +4,7 @@ Use U-Boot
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
blkmap
|
||||
dfu
|
||||
environment
|
||||
fdt_overlays
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
519
drivers/block/blkmap.c
Normal 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
77
include/blkmap.h
Normal 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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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, \
|
||||
|
|
|
@ -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: */
|
||||
|
|
|
@ -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
201
test/dm/blkmap.c
Normal 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);
|
Loading…
Reference in a new issue