cmd: mtd: add nand_read_test command support

This patch implements read-only test of nand flash devices.

Test reads blocks of NAND flash in normal and raw modes and compares
results. The following statuses can be returned for a block:
 * non-ecc reading failed,
 * ecc reading failed,
 * block is bad,
 * bitflips is above maximum,
 * actual number of biflips above reported one,
 * bitflips reached it maximum value,
 * bitflips above threshold,
 * block is ok.

Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
This commit is contained in:
Mikhail Kshevetskiy
2025-09-24 07:20:33 +03:00
committed by Michael Trimarchi
parent b89b0f3c47
commit 642f150bcd
2 changed files with 218 additions and 0 deletions
+16
View File
@@ -1533,6 +1533,22 @@ config CMD_MTD_NAND_WRITE_TEST
WARNING: This test will destroy any data on blocks being tested.
config CMD_MTD_NAND_READ_TEST
bool "mtd nand_read_test"
depends on CMD_MTD
help
MTD nand_read_test command support.
Reads blocks of NAND flash in normal and raw modes and compares results.
The following statuses can be returned for a block:
* non-ecc reading failed,
* ecc reading failed,
* block is bad,
* bitflips is above maximum,
* actual number of biflips above reported one,
* bitflips reached it maximum value,
* block is ok.
config CMD_MUX
bool "mux"
depends on MULTIPLEXER
+202
View File
@@ -951,6 +951,200 @@ out_put_mtd:
}
#endif
#ifdef CONFIG_CMD_MTD_NAND_READ_TEST
enum nand_read_status {
NAND_READ_STATUS_UNKNOWN = 0,
NAND_READ_STATUS_NONECC_READ_FAIL,
NAND_READ_STATUS_ECC_READ_FAIL,
NAND_READ_STATUS_BAD_BLOCK,
NAND_READ_STATUS_BITFLIP_ABOVE_MAX,
NAND_READ_STATUS_BITFLIP_MISMATCH,
NAND_READ_STATUS_BITFLIP_MAX,
NAND_READ_STATUS_UNRELIABLE,
NAND_READ_STATUS_OK,
};
/* test_buf MUST be not smaller than 2 * blocksize bytes */
static enum nand_read_status nand_read_block_check(struct mtd_info *mtd,
loff_t off,
size_t blocksize,
u_char *test_buf)
{
struct mtd_oob_ops ops = {
.mode = MTD_OPS_RAW,
.len = blocksize,
.datbuf = test_buf,
};
int i, d, ret, len, pos, cnt, max;
if (blocksize % mtd->writesize != 0) {
printf("\r block at 0x%llx: bad block size\n", off);
return NAND_READ_STATUS_UNKNOWN;
}
ret = mtd->_read_oob(mtd, off, &ops);
if (ret < 0) {
printf("\r block at 0x%llx: non-ecc reading error %d\n",
off, ret);
return NAND_READ_STATUS_NONECC_READ_FAIL;
}
ops.mode = MTD_OPS_PLACE_OOB;
ops.datbuf = test_buf + blocksize;
ret = mtd->_read_oob(mtd, off, &ops);
if (ret == -EBADMSG) {
printf("\r block at 0x%llx: bad block\n", off);
return NAND_READ_STATUS_BAD_BLOCK;
}
if (ret < 0) {
printf("\r block at 0x%llx: ecc reading error %d\n", off, ret);
return NAND_READ_STATUS_ECC_READ_FAIL;
}
if (mtd->ecc_strength == 0)
return NAND_READ_STATUS_OK;
if (ret > mtd->ecc_strength) {
printf("\r block at 0x%llx: returned bit-flips value %d "
"is above maximum value %d\n",
off, ret, mtd->ecc_strength);
return NAND_READ_STATUS_BITFLIP_ABOVE_MAX;
}
max = 0;
pos = 0;
len = blocksize;
while (len > 0) {
cnt = 0;
for (i = 0; i < mtd->ecc_step_size; i++) {
d = test_buf[pos + i] ^ test_buf[blocksize + pos + i];
if (d == 0)
continue;
while (d > 0) {
d &= (d - 1);
cnt++;
}
}
if (cnt > max)
max = cnt;
len -= mtd->ecc_step_size;
pos += mtd->ecc_step_size;
}
if (max > ret) {
printf("\r block at 0x%llx: bitflip mismatch, "
"read %d but actual %d\n", off, ret, max);
return NAND_READ_STATUS_BITFLIP_MISMATCH;
}
if (ret == mtd->ecc_strength) {
printf("\r block at 0x%llx: max bitflip reached, "
"block is unreliable\n", off);
return NAND_READ_STATUS_BITFLIP_MAX;
}
if (ret >= mtd->bitflip_threshold) {
printf("\r block at 0x%llx: bitflip threshold reached, "
"block is unreliable\n", off);
return NAND_READ_STATUS_UNRELIABLE;
}
return NAND_READ_STATUS_OK;
}
static int do_mtd_nand_read_test(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct mtd_info *mtd;
u64 off, blocks;
int stat[NAND_READ_STATUS_OK + 1];
enum nand_read_status ret;
u_char *buf;
if (argc < 2)
return CMD_RET_USAGE;
mtd = get_mtd_by_name(argv[1]);
if (IS_ERR_OR_NULL(mtd))
return CMD_RET_FAILURE;
if (!mtd_can_have_bb(mtd)) {
printf("Only NAND-based devices can be checked\n");
goto test_error;
}
if (!mtd->_read_oob) {
printf("RAW reading is not supported\n");
goto test_error;
}
buf = malloc_cache_aligned(2 * mtd->erasesize);
if (!buf) {
printf("Can't allocate memory for the test\n");
goto test_error;
}
blocks = mtd->size;
do_div(blocks, mtd->erasesize);
printf("ECC strength: %d\n", mtd->ecc_strength);
printf("ECC theshold: %d\n", mtd->bitflip_threshold);
printf("ECC step size: %d\n", mtd->ecc_step_size);
printf("Erase block size: 0x%x\n", mtd->erasesize);
printf("Total blocks: %lld\n", blocks);
printf("\nworking...\n");
memset(stat, 0, sizeof(stat));
for (off = 0; off < mtd->size; off += mtd->erasesize) {
ret = nand_read_block_check(mtd, off, mtd->erasesize, buf);
stat[ret]++;
switch (ret) {
case NAND_READ_STATUS_BAD_BLOCK:
case NAND_READ_STATUS_BITFLIP_MAX:
case NAND_READ_STATUS_UNRELIABLE:
if (!mtd_block_isbad(mtd, off))
printf("\r block at 0x%llx: should be marked "
"as BAD\n", off);
break;
case NAND_READ_STATUS_OK:
if (mtd_block_isbad(mtd, off))
printf("\r block at 0x%llx: marked as BAD, but "
"probably is GOOD\n", off);
break;
default:
break;
}
}
free(buf);
put_mtd_device(mtd);
printf("\n");
printf("results:\n");
printf(" Good blocks: %d\n", stat[NAND_READ_STATUS_OK]);
printf(" Physically bad blocks: %d\n", stat[NAND_READ_STATUS_BAD_BLOCK]);
printf(" Unreliable blocks: %d\n", stat[NAND_READ_STATUS_BITFLIP_MAX] +
stat[NAND_READ_STATUS_UNRELIABLE]);
printf(" Non checked blocks: %d\n", stat[NAND_READ_STATUS_UNKNOWN]);
printf(" Failed to check blocks: %d\n", stat[NAND_READ_STATUS_NONECC_READ_FAIL] +
stat[NAND_READ_STATUS_ECC_READ_FAIL]);
printf(" Suspictious blocks: %d\n", stat[NAND_READ_STATUS_BITFLIP_ABOVE_MAX] +
stat[NAND_READ_STATUS_BITFLIP_MISMATCH]);
return CMD_RET_SUCCESS;
test_error:
put_mtd_device(mtd);
return CMD_RET_FAILURE;
}
#endif
static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
@@ -1039,6 +1233,9 @@ U_BOOT_LONGHELP(mtd,
#endif
#if CONFIG_IS_ENABLED(CMD_MTD_NAND_WRITE_TEST)
"mtd nand_write_test <name> [<off> [<size>]]\n"
#endif
#if CONFIG_IS_ENABLED(CMD_MTD_NAND_READ_TEST)
"mtd nand_read_test <name>\n"
#endif
"\n"
"With:\n"
@@ -1081,6 +1278,11 @@ U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text,
U_BOOT_SUBCMD_MKENT_COMPLETE(nand_write_test, 4, 0,
do_nand_write_test,
mtd_name_complete),
#endif
#if CONFIG_IS_ENABLED(CMD_MTD_NAND_READ_TEST)
U_BOOT_SUBCMD_MKENT_COMPLETE(nand_read_test, 2, 0,
do_mtd_nand_read_test,
mtd_name_complete),
#endif
U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad,
mtd_name_complete));