diff --git a/cmd/Kconfig b/cmd/Kconfig index 4936a70f3ef..93efeaec6f4 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1497,6 +1497,11 @@ config CMD_NAND_TORTURE help NAND torture support. +config CMD_NAND_WATCH + bool "nand watch" + help + NAND watch bitflip support. + endif # CMD_NAND config CMD_NVME diff --git a/cmd/nand.c b/cmd/nand.c index 5a328e0acdd..2f785deeb7f 100644 --- a/cmd/nand.c +++ b/cmd/nand.c @@ -231,6 +231,54 @@ free_dat: return ret; } +#ifdef CONFIG_CMD_NAND_WATCH +static int nand_watch_bf(struct mtd_info *mtd, ulong off, ulong size, bool quiet) +{ + unsigned int max_bf = 0, pages_wbf = 0; + unsigned int first_page, pages, i; + struct mtd_oob_ops ops = {}; + u_char *buf; + int ret; + + buf = memalign(ARCH_DMA_MINALIGN, mtd->writesize); + if (!buf) { + puts("No memory for page buffer\n"); + return 1; + } + + first_page = off / mtd->writesize; + pages = size / mtd->writesize; + + ops.datbuf = buf; + ops.len = mtd->writesize; + for (i = first_page; i < first_page + pages; i++) { + ulong addr = mtd->writesize * i; + ret = mtd_read_oob_bf(mtd, addr, &ops); + if (ret < 0) { + if (quiet) + continue; + + printf("Page %7d (0x%08lx) -> error %d\n", + i, addr, ret); + } else if (ret) { + max_bf = max(max_bf, (unsigned int)ret); + pages_wbf++; + if (quiet) + continue; + printf("Page %7d (0x%08lx) -> up to %2d bf/chunk\n", + i, addr, ret); + } + } + + printf("Maximum number of bitflips: %u\n", max_bf); + printf("Pages with bitflips: %u/%u\n", pages_wbf, pages); + + free(buf); + + return 0; +} +#endif + /* ------------------------------------------------------------------------- */ static int set_dev(int dev) @@ -781,6 +829,55 @@ static int do_nand(struct cmd_tbl *cmdtp, int flag, int argc, return ret == 0 ? 0 : 1; } +#ifdef CONFIG_CMD_NAND_WATCH + if (strncmp(cmd, "watch", 5) == 0) { + int args = 2; + + if (cmd[5]) { + if (!strncmp(&cmd[5], ".part", 5)) { + args = 1; + } else if (!strncmp(&cmd[5], ".chip", 5)) { + args = 0; + } else { + goto usage; + } + } + + if (cmd[10]) + if (!strncmp(&cmd[10], ".quiet", 6)) + quiet = true; + + if (argc != 2 + args) + goto usage; + + ret = mtd_arg_off_size(argc - 2, argv + 2, &dev, &off, &size, + &maxsize, MTD_DEV_TYPE_NAND, mtd->size); + if (ret) + return ret; + + /* size is unspecified */ + if (argc < 4) + adjust_size_for_badblocks(&size, off, dev); + + if ((off & (mtd->writesize - 1)) || + (size & (mtd->writesize - 1))) { + printf("Attempt to read non page-aligned data\n"); + return -EINVAL; + } + + ret = set_dev(dev); + if (ret) + return ret; + + mtd = get_nand_dev_by_index(dev); + + printf("\nNAND watch for bitflips in area 0x%llx-0x%llx:\n", + off, off + size); + + return nand_watch_bf(mtd, off, size, quiet); + } +#endif + #ifdef CONFIG_CMD_NAND_TORTURE if (strcmp(cmd, "torture") == 0) { loff_t endoff; @@ -946,6 +1043,12 @@ U_BOOT_LONGHELP(nand, "nand erase.chip [clean] - erase entire chip'\n" "nand bad - show bad blocks\n" "nand dump[.oob] off - dump page\n" +#ifdef CONFIG_CMD_NAND_WATCH + "nand watch - check an area for bitflips\n" + "nand watch.part - check a partition for bitflips\n" + "nand watch.chip - check the whole device for bitflips\n" + "\t\t.quiet - Query only the summary, not the details\n" +#endif #ifdef CONFIG_CMD_NAND_TORTURE "nand torture off - torture one block at offset\n" "nand torture off [size] - torture blocks from off to off+size\n" diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 5bd64bd6ad4..3bfa5aebbc6 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1124,6 +1124,28 @@ int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) } EXPORT_SYMBOL_GPL(mtd_read_oob); +/* This is a bare copy of mtd_read_oob returning the actual number of bitflips */ +int mtd_read_oob_bf(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) +{ + int ret_code; + ops->retlen = ops->oobretlen = 0; + if (!mtd->_read_oob) + return -EOPNOTSUPP; + /* + * In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics + * similar to mtd->_read(), returning a non-negative integer + * representing max bitflips. In other cases, mtd->_read_oob() may + * return -EUCLEAN. In all cases, perform similar logic to mtd_read(). + */ + ret_code = mtd->_read_oob(mtd, from, ops); + if (unlikely(ret_code < 0)) + return ret_code; + if (mtd->ecc_strength == 0) + return 0; /* device lacks ecc */ + return ret_code; +} +EXPORT_SYMBOL_GPL(mtd_read_oob_bf); + int mtd_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) {