[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