[PATCH 3/3] cmd: mtd: add nand_read_test command support

Michael Nazzareno Trimarchi michael at amarulasolutions.com
Sat Sep 6 00:27:04 CEST 2025


Hi

On Thu, Sep 4, 2025 at 4:50 PM Mikhail Kshevetskiy
<mikhail.kshevetskiy at iopsys.eu> wrote:
>
> 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,
>  * block is ok.
>
> Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
> ---
>  cmd/Kconfig |  16 +++++
>  cmd/mtd.c   | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 213 insertions(+)
>
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index cd1a664f34f..21d6db57628 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -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
> diff --git a/cmd/mtd.c b/cmd/mtd.c
> index 4d69c7b4915..cee3a7e43ef 100644
> --- a/cmd/mtd.c
> +++ b/cmd/mtd.c
> @@ -951,6 +951,195 @@ 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_OK,
> +};
> +
> +static enum nand_read_status nand_read_block_check(struct mtd_info *mtd,
> +                                                  loff_t off, size_t blocksize)
> +{
> +       struct mtd_oob_ops      ops = {};
> +       u_char                  *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;
> +       }
> +
> +       buf = malloc_cache_aligned(2 * blocksize);
> +       if (buf == NULL) {
> +               printf("\r  block at 0x%llx: can't allocate memory\n", off);
> +               return NAND_READ_STATUS_UNKNOWN;
> +       }
> +

I think that you can allocate those and check outside the function
only one time. Then you
can use standard return ENOMEM.

> +       ops.mode = MTD_OPS_RAW;
> +       ops.len = blocksize;
> +       ops.datbuf = buf;
> +       ops.ooblen = 0;
> +       ops.oobbuf = NULL;
> +

You don't need to set to 0 and null and if you drop the allocation and
the check then
you can initialize in the struct definition.

> +       if (mtd->_read_oob)
> +               ret = mtd->_read_oob(mtd, off, &ops);
> +       else
> +               ret = mtd->_read(mtd, off, ops.len, &ops.retlen, ops.datbuf);
> +
> +       if (ret != 0) {
> +               free(buf);
> +               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_AUTO_OOB;
> +       ops.datbuf = buf + blocksize;
> +
> +       if (mtd->_read_oob)
> +               ret = mtd->_read_oob(mtd, off, &ops);
> +       else
> +               ret = mtd->_read(mtd, off, ops.len, &ops.retlen, ops.datbuf);
> +
> +       if (ret == -EBADMSG) {
> +               free(buf);
> +               printf("\r  block at 0x%llx: bad block\n", off);
> +               return NAND_READ_STATUS_BAD_BLOCK;
> +       }
> +
> +       if (ret < 0) {
> +               free(buf);
> +               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) {
> +               free(buf);
> +               return NAND_READ_STATUS_OK;
> +       }
> +
> +       if (ret > mtd->ecc_strength) {
> +               free(buf);
> +               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 = buf[pos + i] ^ 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;
> +       }
> +
> +       free(buf);
> +
> +       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;
> +       }
> +
> +       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;
> +
> +       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 out_put_mtd;
> +       }
> +
> +       blocks = mtd->size;
> +       do_div(blocks, mtd->erasesize);
> +
> +       printf("ECC strength:     %d\n",   mtd->ecc_strength);
> +       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);
> +               stat[ret]++;
> +
> +               switch (ret) {
> +               case NAND_READ_STATUS_BAD_BLOCK:
> +               case NAND_READ_STATUS_BITFLIP_MAX:
> +                       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;
> +               }
> +       }
> +
> +out_put_mtd:
> +       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]);
> +       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;
> +}
> +#endif
> +
>  static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc,
>                       char *const argv[])
>  {
> @@ -1039,6 +1228,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 +1273,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));
> --
> 2.50.1
>

Otherwise:

Reviewed-by: Michael Trimarchi <michael at amarulasolutions.com>

-- 
Michael Nazzareno Trimarchi
Co-Founder & Chief Executive Officer
M. +39 347 913 2170
michael at amarulasolutions.com
__________________________________

Amarula Solutions BV
Joop Geesinkweg 125, 1114 AB, Amsterdam, NL
T. +31 (0)85 111 9172
info at amarulasolutions.com
www.amarulasolutions.com


More information about the U-Boot mailing list