diff options
Diffstat (limited to 'drivers/block')
-rw-r--r-- | drivers/block/Kconfig | 18 | ||||
-rw-r--r-- | drivers/block/Makefile | 1 | ||||
-rw-r--r-- | drivers/block/blk-uclass.c | 1 | ||||
-rw-r--r-- | drivers/block/blkmap.c | 343 |
4 files changed, 363 insertions, 0 deletions
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index e95da48bdc0..5a1aeb3d2b4 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -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 diff --git a/drivers/block/Makefile b/drivers/block/Makefile index f12447d78d8..a161d145fd3 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -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 diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c index c69fc4d5182..cb73faaedaf 100644 --- a/drivers/block/blk-uclass.c +++ b/drivers/block/blk-uclass.c @@ -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 index 00000000000..acfc002ceba --- /dev/null +++ b/drivers/block/blkmap.c @@ -0,0 +1,343 @@ +// 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; +} + +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", +}; |