// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2023 Addiva Elektronik * Author: Tobias Waldekranz */ #include #include #include #include #include #include #include #include #include #include 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", };