| // 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) { | |
| /* update to match the mapped device */ | |
| bd->blksz = lbd->blksz; | |
| bd->log2blksz = LOG2(bd->blksz); | |
| } | |
| 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, | |
| }; | |
| static 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), DEFAULT_BLKSZ, 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; | |
| } | |
| static 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", | |
| }; |