From e108d10d8c89aa65c8b41db5c43407fd0d4a09fe Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 18 Jul 2024 08:46:05 +0300 Subject: drivers: introduce mtdblock abstraction MTD block - abstraction over MTD subsystem, allowing to read and write in blocks using BLK UCLASS. Signed-off-by: Alexey Romanov Signed-off-by: Michael Trimarchi --- drivers/mtd/Kconfig | 8 ++ drivers/mtd/Makefile | 1 + drivers/mtd/mtdblock.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 drivers/mtd/mtdblock.c (limited to 'drivers/mtd') diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index e4e29e0a3c..ce74ef31d7 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -32,6 +32,14 @@ config MTD_CONCAT into a single logical device. The larger logical device can then be partitioned. +config MTD_BLOCK + bool "Enable block device access to MTD devices" + depends on BLK + default n + help + Enable support for block device access to MTD devices + using blk_ops abstraction. + config SYS_MTDPARTS_RUNTIME bool "Allow MTDPARTS to be configured at runtime" help diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index c2fc80b10f..10d575e9f9 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -26,6 +26,7 @@ obj-y += onenand/ obj-y += spi/ obj-$(CONFIG_MTD_UBI) += ubi/ obj-$(CONFIG_NVMXIP) += nvmxip/ +obj-$(CONFIG_MTD_BLOCK) += mtdblock.o #SPL/TPL build else diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c new file mode 100644 index 0000000000..66a79b8c56 --- /dev/null +++ b/drivers/mtd/mtdblock.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MTD block - abstraction over MTD subsystem, allowing + * to read and write in blocks using BLK UCLASS. + * + * - Read algorithm: + * + * 1. Convert start block number to start address. + * 2. Read block_dev->blksz bytes using mtd_read() and + * add to start address pointer block_dev->blksz bytes, + * until the requested number of blocks have been read. + * + * - Write algorithm: + * + * 1. Convert start block number to start address. + * 2. Round this address down by mtd->erasesize. + * + * Erase addr Start addr + * | | + * v v + * +----------------+----------------+----------------+ + * | blksz | blksz | blksz | + * +----------------+----------------+----------------+ + * + * 3. Calculate offset between this two addresses. + * 4. Read mtd->erasesize bytes using mtd_read() into + * temporary buffer from erase address. + * + * Erase addr Start addr + * | | + * v v + * +----------------+----------------+----------------+ + * | blksz | blksz | blksz | + * +----------------+----------------+----------------+ + * ^ + * | + * | + * mtd_read() + * from here + * + * 5. Copy data from user buffer to temporary buffer with offset, + * calculated at step 3. + * 6. Erase and write mtd->erasesize bytes at erase address + * pointer using mtd_erase/mtd_write(). + * 7. Add to erase address pointer mtd->erasesize bytes. + * 8. goto 1 until the requested number of blocks have + * been written. + * + * (C) Copyright 2024 SaluteDevices, Inc. + * + * Author: Alexey Romanov + */ + +#include +#include +#include +#include +#include + +int mtd_bind(struct udevice *dev, struct mtd_info **mtd) +{ + struct blk_desc *bdesc; + struct udevice *bdev; + int ret; + + ret = blk_create_devicef(dev, "mtd_blk", "blk", UCLASS_MTD, + -1, 512, 0, &bdev); + if (ret) { + pr_err("Cannot create block device\n"); + return ret; + } + + bdesc = dev_get_uclass_plat(bdev); + dev_set_priv(bdev, mtd); + bdesc->bdev = bdev; + bdesc->part_type = PART_TYPE_MTD; + + return 0; +} + +static ulong mtd_blk_read(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, + void *dst) +{ + struct blk_desc *block_dev = dev_get_uclass_plat(dev); + struct mtd_info *mtd = blk_desc_to_mtd(block_dev); + unsigned int sect_size = block_dev->blksz; + lbaint_t cur = start; + ulong read_cnt = 0; + + while (read_cnt < blkcnt) { + int ret; + loff_t sect_start = cur * sect_size; + size_t retlen; + + ret = mtd_read(mtd, sect_start, sect_size, &retlen, dst); + if (ret) + return ret; + + if (retlen != sect_size) { + pr_err("mtdblock: failed to read block 0x" LBAF "\n", cur); + return -EIO; + } + + cur++; + dst += sect_size; + read_cnt++; + } + + return read_cnt; +} + +static int mtd_erase_write(struct mtd_info *mtd, uint64_t start, const void *src) +{ + int ret; + size_t retlen; + struct erase_info erase = { 0 }; + + erase.mtd = mtd; + erase.addr = start; + erase.len = mtd->erasesize; + + ret = mtd_erase(mtd, &erase); + if (ret) + return ret; + + ret = mtd_write(mtd, start, mtd->erasesize, &retlen, src); + if (ret) + return ret; + + if (retlen != mtd->erasesize) { + pr_err("mtdblock: failed to read block at 0x%llx\n", start); + return -EIO; + } + + return 0; +} + +static ulong mtd_blk_write(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, + const void *src) +{ + struct blk_desc *block_dev = dev_get_uclass_plat(dev); + struct mtd_info *mtd = blk_desc_to_mtd(block_dev); + unsigned int sect_size = block_dev->blksz; + lbaint_t cur = start, blocks_todo = blkcnt; + ulong write_cnt = 0; + u8 *buf; + int ret = 0; + + buf = malloc(mtd->erasesize); + if (!buf) + return -ENOMEM; + + while (blocks_todo > 0) { + loff_t sect_start = cur * sect_size; + loff_t erase_start = ALIGN_DOWN(sect_start, mtd->erasesize); + u32 offset = sect_start - erase_start; + size_t cur_size = min_t(size_t, mtd->erasesize - offset, + blocks_todo * sect_size); + size_t retlen; + lbaint_t written; + + ret = mtd_read(mtd, erase_start, mtd->erasesize, &retlen, buf); + if (ret) + goto out; + + if (retlen != mtd->erasesize) { + pr_err("mtdblock: failed to read block 0x" LBAF "\n", cur); + ret = -EIO; + goto out; + } + + memcpy(buf + offset, src, cur_size); + + ret = mtd_erase_write(mtd, erase_start, buf); + if (ret) + goto out; + + written = cur_size / sect_size; + + blocks_todo -= written; + cur += written; + src += cur_size; + write_cnt += written; + } + +out: + free(buf); + + if (ret) + return ret; + + return write_cnt; +} + +static int mtd_blk_probe(struct udevice *dev) +{ + struct blk_desc *bdesc; + struct mtd_info *mtd; + int ret; + + ret = device_probe(dev); + if (ret) { + pr_err("Probing %s failed (err=%d)\n", dev->name, ret); + return ret; + } + + bdesc = dev_get_uclass_plat(dev); + mtd = blk_desc_to_mtd(bdesc); + + if (mtd_type_is_nand(mtd)) + pr_warn("MTD device '%s' is NAND, please use UBI devices instead\n", + mtd->name); + + return 0; +} + +static const struct blk_ops mtd_blk_ops = { + .read = mtd_blk_read, + .write = mtd_blk_write, +}; + +U_BOOT_DRIVER(mtd_blk) = { + .name = "mtd_blk", + .id = UCLASS_BLK, + .ops = &mtd_blk_ops, + .probe = mtd_blk_probe, +}; -- cgit v1.2.3