From 12fa9c50243be05a989d30d5e6b02f19d3197ada Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Mon, 12 Dec 2011 15:12:08 +0100 Subject: nandtest: add nandtest command Add nandtest command to test nand devices and display ecc stats at the end of test. Signed-off-by: Alexander Aring Signed-off-by: Sascha Hauer --- commands/nandtest.c | 364 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 commands/nandtest.c (limited to 'commands/nandtest.c') diff --git a/commands/nandtest.c b/commands/nandtest.c new file mode 100644 index 0000000000..3a955e93fc --- /dev/null +++ b/commands/nandtest.c @@ -0,0 +1,364 @@ +/* + * Based on nandtest.c source in mtd-utils package. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Max ECC Bits that can be corrected */ +#define MAX_ECC_BITS 8 + +/* + * Structures for flash memory information. + */ +static struct region_info_user memregion; +static struct mtd_info_user meminfo; +static struct mtd_ecc_stats oldstats, newstats; + +static int fd, seed; +/* Markbad option flag */ +static int markbad; + +/* ECC-Calculation stats */ +static unsigned int ecc_stats[MAX_ECC_BITS]; +static unsigned int ecc_stats_over; +static unsigned int ecc_failed_cnt; + +/* + * Implementation of pread with lseek and read. + */ +static ssize_t pread(int fd, void *buf, size_t count, off_t offset) +{ + int ret; + + /* Seek to offset */ + ret = lseek(fd, offset, SEEK_SET); + if (ret < 0) + perror("lseek"); + + /* Read from flash and put it into buf */ + ret = read(fd, buf, count); + if (ret < 0) + perror("read"); + + return 0; +} + +/* + * Implementation of pwrite with lseek and write. + */ +static ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + int ret; + + ret = lseek(fd, offset, SEEK_SET); + if (ret < 0) + perror("lseek"); + + /* Write buf to flash */ + ret = write(fd, buf, count); + if (ret < 0) { + perror("write"); + if (markbad) { + printf("Mark block bad at 0x%08x\n", + (unsigned)(offset + memregion.offset)); + ioctl(fd, MEMSETBADBLOCK, &offset); + } + } + + flush(fd); + return 0; +} + +/* + * Erase and write function. + * Param ofs: offset on flash_device. + * Param data: data to write on flash. + * Param rbuf: pointer to allocated buffer to copy readed data. + */ +static int erase_and_write(off_t ofs, unsigned char *data, unsigned char *rbuf) +{ + struct erase_info_user er; + int i, ret; + + printf("\r0x%08x: erasing... ", (unsigned)(ofs + memregion.offset)); + + er.start = ofs; + er.length = meminfo.erasesize; + + ret = erase(fd, er.length, er.start); + if (ret < 0) { + perror("erase"); + printf("Could't not erase flash at 0x%08x length 0x%08x.\n", + er.start, er.length); + return ret; + } + + printf("\r0x%08x: writing...", (unsigned)(ofs + memregion.offset)); + + /* Write data to given offset */ + pwrite(fd, data, meminfo.erasesize, ofs); + + printf("\r0x%08x: reading...", (unsigned)(ofs + memregion.offset)); + + /* Read data from offset */ + pread(fd, rbuf, meminfo.erasesize, ofs); + + ret = ioctl(fd, ECCGETSTATS, &newstats); + if (ret < 0) { + perror("ECCGETSTATS"); + return ret; + } + + if (newstats.corrected > oldstats.corrected) { + printf("\n %d bit(s) ECC corrected at 0x%08x\n", + newstats.corrected - oldstats.corrected, + (unsigned)(ofs + memregion.offset)); + if ((newstats.corrected-oldstats.corrected) >= MAX_ECC_BITS) { + /* Increment ECC stats that are over MAX_ECC_BITS */ + ecc_stats_over++; + } else { + /* Increment ECC stat value */ + ecc_stats[(newstats.corrected-oldstats.corrected)-1]++; + } + /* Set oldstats to newstats */ + oldstats.corrected = newstats.corrected; + } + if (newstats.failed > oldstats.failed) { + printf("\nECC failed at 0x%08x\n", + (unsigned)(ofs + memregion.offset)); + oldstats.failed = newstats.failed; + ecc_failed_cnt++; + } + + printf("\r0x%08x: checking...", (unsigned)(ofs + memregion.offset)); + + /* Compared written data with read data. + * If data is not identical, display a detailed + * debugging information. */ + ret = memcmp(data, rbuf, meminfo.erasesize); + if (ret < 0) { + printf("\ncompare failed. seed %d\n", seed); + for (i = 0; i < meminfo.erasesize; i++) { + if (data[i] != rbuf[i]) + printf("Byte 0x%x is %02x should be %02x\n", + i, rbuf[i], data[i]); + } + return ret; + } + return 0; +} + +/* Print stats of nandtest. */ +static void print_stats(int nr_passes, int length) +{ + int i; + printf("-------- Summary --------\n"); + printf("Tested blocks : %d\n", (length/meminfo.erasesize) + * nr_passes); + + for (i = 0; i < MAX_ECC_BITS; i++) + printf("ECC %d bit error(s) : %d\n", i+1, ecc_stats[i]); + + printf("ECC >%d bit error(s) : %d\n", MAX_ECC_BITS, ecc_stats_over); + printf("ECC corrections failed : %d\n", ecc_failed_cnt); + printf("-------------------------\n"); +} + +/* Main program. */ +static int do_nandtest(struct command *cmdtp, int argc, char *argv[]) +{ + int opt, length = -1, do_nandtest_dev = -1; + off_t flash_offset = 0; + off_t test_ofs; + unsigned int nr_passes = 1, pass; + int i; + int ret = -1; + unsigned char *wbuf, *rbuf; + + ecc_failed_cnt = 0; + ecc_stats_over = 0; + markbad = 0; + fd = -1; + + memset(ecc_stats, 0, MAX_ECC_BITS); + + while ((opt = getopt(argc, argv, "ms:p:o:l:t")) > 0) { + switch (opt) { + case 'm': + markbad = 1; + break; + case 's': + seed = simple_strtoul(optarg, NULL, 0); + break; + case 'p': + nr_passes = simple_strtoul(optarg, NULL, 0); + break; + case 'o': + flash_offset = simple_strtoul(optarg, NULL, 0); + break; + case 'l': + length = simple_strtoul(optarg, NULL, 0); + break; + case 't': + do_nandtest_dev = 1; + break; + default: + return COMMAND_ERROR_USAGE; + } + } + + /* Check if no device is given */ + if (optind >= argc) + return COMMAND_ERROR_USAGE; + + if (do_nandtest_dev == -1) { + printf("Please add -t parameter to start nandtest.\n"); + return 0; + } + + printf("Open device %s\n", argv[optind]); + + fd = open(argv[optind], O_RDWR); + if (fd < 0) { + perror("open"); + return COMMAND_ERROR_USAGE; + } + + /* Getting flash information. */ + + ret = ioctl(fd, MEMGETINFO, &meminfo); + if (ret < 0) { + perror("MEMGETINFO"); + goto err; + } + + ret = ioctl(fd, MEMGETREGIONINFO, &memregion); + if (ret < 0) { + perror("MEMGETREGIONINFO"); + goto err; + } + + ret = ioctl(fd, ECCGETSTATS, &oldstats); + if (ret < 0) { + perror("ECCGETSTATS"); + goto err; + } + + if (length == -1) { + length = meminfo.size; + length -= flash_offset; + } + + printf("Flash offset: 0x%08x\n", + (unsigned)(flash_offset+memregion.offset)); + printf("Length: 0x%08x\n", (unsigned)length); + printf("End address: 0x%08x\n", + (unsigned)(flash_offset+length+memregion.offset)); + printf("Erasesize: 0x%08x\n", (unsigned)(meminfo.erasesize)); + printf("Starting nandtest...\n"); + + if (flash_offset % meminfo.erasesize) { + printf("Offset 0x%08x not multiple of erase size 0x%08x\n", + (unsigned)flash_offset, meminfo.erasesize); + goto err; + } + if (length % meminfo.erasesize) { + printf("Length 0x%08x not multiple of erase size 0x%08x\n", + length, meminfo.erasesize); + goto err; + } + if (length + flash_offset > meminfo.size) { + printf("Length 0x%08x + offset 0x%08x exceeds " + "device size 0x%08x\n", + length, (unsigned)flash_offset, meminfo.size); + goto err; + } + + wbuf = malloc(meminfo.erasesize * 2); + if (!wbuf) { + printf("Could not allocate %d bytes for buffer\n", + meminfo.erasesize * 2); + goto err; + } + rbuf = wbuf + meminfo.erasesize; + + for (pass = 0; pass < nr_passes; pass++) { + for (test_ofs = flash_offset; + test_ofs < flash_offset+length; + test_ofs += meminfo.erasesize) { + + srand(seed); + seed = rand(); + + if (ioctl(fd, MEMGETBADBLOCK, (void *)test_ofs)) { + printf("\rBad block at 0x%08x\n", + (unsigned)(test_ofs + + memregion.offset)); + continue; + } + + for (i = 0; i < meminfo.erasesize; i++) + wbuf[i] = rand(); + + ret = erase_and_write(test_ofs, wbuf, rbuf); + if (ret < 0) + goto err2; + } + printf("\nFinished pass %d successfully\n", pass+1); + } + + print_stats(nr_passes, length); + + ret = close(fd); + if (ret < 0) { + perror("close"); + goto err2; + } + + free(wbuf); + + return 0; +err2: + free(wbuf); +err: + printf("Error occurred.\n"); + close(fd); + return 1; +} + +/* String for usage of nandtest */ +static const __maybe_unused char cmd_nandtest_help[] = +"Usage: nand [OPTION] \n" + " -t, Really do a nandtest on device.\n" + " -m, Mark blocks bad if they appear so.\n" + " -s , Supply random seed.\n" + " -p , Number of passes.\n" + " -o , Start offset on flash.\n" + " -l , Length of flash to test.\n"; + +BAREBOX_CMD_START(nandtest) + .cmd = do_nandtest, + .usage = "NAND Test", + BAREBOX_CMD_HELP(cmd_nandtest_help) +BAREBOX_CMD_END -- cgit v1.2.3