[RFC PATCH 03/20] mtd: add mtd_read_skip_bad() helper
Daniel Golle
daniel at makrotopia.org
Mon Feb 16 22:21:40 CET 2026
Add a generic mtd_read_skip_bad() helper to the MTD core that reads
data from an MTD device while transparently skipping bad erase blocks
on NAND.
The function takes a physical byte offset, reads in erase-block-sized
chunks, and automatically skips any block where mtd_block_isbad()
returns true. Non-block-aligned start offsets are handled correctly.
For NOR and other device types without bad-block support, it falls
through to a plain mtd_read().
Refactor the plain-data read path in cmd/mtd.c ("mtd read", without
.raw or .oob suffixes) to use the new helper instead of open-coding
the bad-block skip loop. The write path and OOB/raw read paths
retain their existing page-at-a-time mtd_read_oob() loop.
This helper will also be used by the image_loader MTD backend in a
subsequent patch. It could also serve as a replacement for
nand_read_skip_bad() in drivers/mtd/nand/raw/nand_util.c in the
future.
Signed-off-by: Daniel Golle <daniel at makrotopia.org>
---
cmd/mtd.c | 65 +++++++++++++++++++++++------------------
drivers/mtd/mtdcore.c | 45 ++++++++++++++++++++++++++++
include/linux/mtd/mtd.h | 24 +++++++++++++++
3 files changed, 106 insertions(+), 28 deletions(-)
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 7f25144098b..30e4845b26d 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -551,12 +551,6 @@ static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc,
printf("%s %lld byte(s) at offset 0x%08llx\n",
read ? "Reading" : "Writing", len, start_off);
- io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB;
- io_op.len = has_pages ? mtd->writesize : len;
- io_op.ooblen = woob ? mtd->oobsize : 0;
- io_op.datbuf = buf;
- io_op.oobbuf = woob ? &buf[len] : NULL;
-
/* Search for the first good block after the given offset */
off = start_off;
while (mtd_block_isbad(mtd, off))
@@ -567,31 +561,46 @@ static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc,
if (benchmark)
bench_start = timer_get_us();
- /* Loop over the pages to do the actual read/write */
- while (remaining) {
- /* Skip the block if it is bad */
- if (mtd_is_aligned_with_block_size(mtd, off) &&
- mtd_block_isbad(mtd, off)) {
- off += mtd->erasesize;
- continue;
- }
+ if (read && !raw && !woob) {
+ /* Plain data read — use the skip-bad-block helper */
+ size_t rdlen;
- if (read)
- ret = mtd_read_oob(mtd, off, &io_op);
- else
- ret = mtd_special_write_oob(mtd, off, &io_op,
- write_empty_pages, woob);
+ ret = mtd_read_skip_bad(mtd, off, remaining, &rdlen, buf);
+ remaining -= rdlen;
+ } else {
+ io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB;
+ io_op.len = has_pages ? mtd->writesize : len;
+ io_op.ooblen = woob ? mtd->oobsize : 0;
+ io_op.datbuf = buf;
+ io_op.oobbuf = woob ? &buf[len] : NULL;
+
+ /* Loop over the pages to do the actual read/write */
+ while (remaining) {
+ /* Skip the block if it is bad */
+ if (mtd_is_aligned_with_block_size(mtd, off) &&
+ mtd_block_isbad(mtd, off)) {
+ off += mtd->erasesize;
+ continue;
+ }
- if (ret) {
- printf("Failure while %s at offset 0x%llx\n",
- read ? "reading" : "writing", off);
- break;
- }
+ if (read)
+ ret = mtd_read_oob(mtd, off, &io_op);
+ else
+ ret = mtd_special_write_oob(mtd, off, &io_op,
+ write_empty_pages,
+ woob);
+
+ if (ret) {
+ printf("Failure while %s at offset 0x%llx\n",
+ read ? "reading" : "writing", off);
+ break;
+ }
- off += io_op.retlen;
- remaining -= io_op.retlen;
- io_op.datbuf += io_op.retlen;
- io_op.oobbuf += io_op.oobretlen;
+ off += io_op.retlen;
+ remaining -= io_op.retlen;
+ io_op.datbuf += io_op.retlen;
+ io_op.oobbuf += io_op.oobretlen;
+ }
}
if (benchmark && bench_start) {
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 3bfa5aebbc6..eb36743af1f 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -1684,6 +1684,51 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
}
EXPORT_SYMBOL_GPL(mtd_block_markbad);
+int mtd_read_skip_bad(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ size_t remaining = len;
+ u_char *p = buf;
+ int ret;
+
+ *retlen = 0;
+
+ if (!mtd_can_have_bb(mtd)) {
+ ret = mtd_read(mtd, from, len, retlen, buf);
+ if (ret == -EUCLEAN)
+ ret = 0;
+ return ret;
+ }
+
+ while (remaining) {
+ loff_t block_start = from & ~(loff_t)(mtd->erasesize - 1);
+ size_t block_off = from - block_start;
+ size_t chunk, rdlen;
+
+ if (from >= mtd->size)
+ return -EINVAL;
+
+ if (mtd_block_isbad(mtd, block_start)) {
+ /* Skip to start of next erase block */
+ from = block_start + mtd->erasesize;
+ continue;
+ }
+
+ chunk = min(remaining, (size_t)mtd->erasesize - block_off);
+
+ ret = mtd_read(mtd, from, chunk, &rdlen, p);
+ if (ret && ret != -EUCLEAN)
+ return ret;
+
+ p += rdlen;
+ from += rdlen;
+ remaining -= rdlen;
+ *retlen += rdlen;
+ }
+
+ return 0;
+}
+
#ifndef __UBOOT__
/*
* default_mtd_writev - the default writev method
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 1f97bc4fe11..ea3fa513c58 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -471,6 +471,30 @@ int mtd_block_isreserved(struct mtd_info *mtd, loff_t ofs);
int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs);
int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs);
+/**
+ * mtd_read_skip_bad() - Read from an MTD device, skipping bad blocks
+ *
+ * Read @len bytes starting at physical offset @from into @buf. On NAND
+ * devices, erase blocks that are marked bad are transparently skipped
+ * so the caller always receives a contiguous stream of good data.
+ *
+ * If @from is not erase-block-aligned, reading starts at the correct
+ * position within the first good block.
+ *
+ * For NOR and other device types without bad-block support, this is
+ * equivalent to a plain mtd_read().
+ *
+ * @mtd: MTD device
+ * @from: Start offset (physical, byte address)
+ * @len: Number of bytes to read
+ * @retlen: Actual number of bytes read (output)
+ * @buf: Destination buffer
+ * Return: 0 on success, negative errno on failure. -EUCLEAN is
+ * treated as success (ECC corrected).
+ */
+int mtd_read_skip_bad(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf);
+
#ifndef __UBOOT__
static inline int mtd_suspend(struct mtd_info *mtd)
{
--
2.53.0
More information about the U-Boot
mailing list