[PATCH 2/3] nandtest: add nandtest command

Sascha Hauer s.hauer at pengutronix.de
Fri Dec 9 07:10:41 EST 2011


Hi Alexander,

On Fri, Dec 09, 2011 at 10:51:47AM +0100, Alexander Aring wrote:
> Add nandtest command to test nand devices
> and display ecc stats at the end of test.

Nice patch. Nand is often a 'hope for the best' case. Good to have a
test program for this.

As this command scratches every device it is given to we should
clearly state this somewhere in the help text. I think we
should also only make this command work if some additional command
line switch is given, just to prevent users from doing a quick
'Test? Cool, let's test this device' without knowing it.

> 
> Signed-off-by: Alexander Aring <a.aring at phytec.de>
> ---
>  commands/Kconfig    |    7 +
>  commands/Makefile   |    1 +
>  commands/nandtest.c |  397 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 405 insertions(+), 0 deletions(-)
>  create mode 100644 commands/nandtest.c
> 
> diff --git a/commands/Kconfig b/commands/Kconfig
> index ebc9c7f..0ca37c9 100644
> --- a/commands/Kconfig
> +++ b/commands/Kconfig
> @@ -173,6 +173,13 @@ config CMD_NAND
>  	depends on NAND
>  	prompt "nand"
>  
> +config CMD_NANDTEST
> +	tristate
> +	depends on NAND
> +	depends on PARTITION
> +	depends on NAND_ECC_HW || NAND_ECC_SOFT
> +	prompt "nandtest"
> +
>  endmenu
>  
>  menu "console                       "
> diff --git a/commands/Makefile b/commands/Makefile
> index aa013de..bbd9a88 100644
> --- a/commands/Makefile
> +++ b/commands/Makefile
> @@ -39,6 +39,7 @@ obj-$(CONFIG_CMD_PRINTENV)	+= printenv.o
>  obj-$(CONFIG_CMD_SAVEENV)	+= saveenv.o
>  obj-$(CONFIG_CMD_LOADENV)	+= loadenv.o
>  obj-$(CONFIG_CMD_NAND)		+= nand.o
> +obj-$(CONFIG_CMD_NANDTEST)	+= nandtest.o
>  obj-$(CONFIG_CMD_TRUE)		+= true.o
>  obj-$(CONFIG_CMD_FALSE)		+= false.o
>  obj-$(CONFIG_CMD_VERSION)	+= version.o
> diff --git a/commands/nandtest.c b/commands/nandtest.c
> new file mode 100644
> index 0000000..298711c
> --- /dev/null
> +++ b/commands/nandtest.c
> @@ -0,0 +1,397 @@
> +/*
> + * 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 <common.h>
> +#include <command.h>
> +#include <fs.h>
> +#include <errno.h>
> +#include <malloc.h>
> +#include <getopt.h>
> +#include <ioctl.h>
> +#include <linux/mtd/mtd-abi.h>
> +#include <fcntl.h>
> +#include <stdlib.h>
> +
> +/* 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");
> +		return -1;

You should propagate the errors instead of returning -1.

> +	}
> +
> +	/* Read from flash and put it into buf  */
> +	ret = read(fd, buf, count);
> +	if (ret < 0) {
> +		perror("read");
> +		return -1;
> +	}
> +
> +	return ret;
> +}
> +
> +/*
> + * 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");
> +		return -1;
> +	}
> +
> +	ret = write(fd, buf, count);
> +	if (ret < 0) {
> +		perror("read");
> +		return -1;
> +	}
> +
> +	flush(fd);
> +	return ret;
> +}
> +
> +/*
> + * 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;
> +	ssize_t len;
> +	int i;
> +
> +	printf("\r0x%08x: erasing... ", (unsigned)(ofs + memregion.offset));
> +	flush(stdout);

You don't need this flush.

> +
> +	er.start = ofs;
> +	er.length = meminfo.erasesize;
> +
> +	erase(fd, er.length, er.start);

Check return value?

> +
> +	printf("\r0x%08x: writing...", (unsigned)(ofs + memregion.offset));
> +	flush(stdout);
> +
> +	/* Write data to given offset */
> +	len = pwrite(fd, data, meminfo.erasesize, ofs);
> +	if (len < 0) {
> +		printf("\n");
> +		perror("write");

You already did a perror in pwrite, so this error will be printed twice.
As a rule of thumb it is mostly more useful to print the error in the
calling function rather than in the called function.

> +		if (markbad) {
> +			printf("Mark block bad at 0x%08x\n",
> +					(unsigned)(ofs + memregion.offset));
> +			ioctl(fd, MEMSETBADBLOCK, &ofs);
> +		}
> +		return -1;
> +	}
> +
> +	if (len < meminfo.erasesize) {
> +		printf("\n");
> +		printf("Short write (%zd bytes)\n", len);
> +		return -1;
> +	}
> +
> +	printf("\r0x%08x: reading...", (unsigned)(ofs + memregion.offset));
> +	flush(stdout);
> +
> +	/* Read data from offset */
> +	len = pread(fd, rbuf, meminfo.erasesize, ofs);
> +	if (len < meminfo.erasesize) {
> +		printf("\n");
> +		if (len)
> +			printf("Short read (%zd bytes)\n", len);
> +		else
> +			perror("read");
> +		return -1;
> +	}
> +
> +	if (ioctl(fd, ECCGETSTATS, &newstats)) {
> +		printf("\n");
> +		perror("ECCGETSTATS");
> +		return -1;
> +	}
> +
> +	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));
> +	flush(stdout);
> +
> +	/* Check written data with readed data.
> +	 * If data aren't compare, display a detailed
> +	 * debugging information. */
> +	if (memcmp(data, rbuf, meminfo.erasesize)) {
> +		printf("\n");
> +		printf("compare 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 -1;
> +	}
> +	return 0;
> +}
> +
> +/* Main program. */
> +static int do_nandtest(struct command *cmdtp, int argc, char *argv[])
> +{
> +	int opt, command = 0, length = -1;
> +	int pass;
> +	off_t flash_offset = 0;
> +	off_t test_ofs;
> +	int nr_passes = 1;
> +	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, "hms:p:o:l:k")) > 0) {
> +		if (command) {
> +			printf("only one command may be given\n");
> +			return 1;
> +		}
> +
> +		switch (opt) {
> +		case 'h':
> +			return COMMAND_ERROR_USAGE;

You don't need this as we have a help command. I have more than once
typed 'command -h' which does not work, but that's the way it currently
is. If we want to change this we should make a general decision not
limited to this single command.

> +		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;
> +		default:
> +			return COMMAND_ERROR_USAGE;
> +		}
> +	}
> +
> +	if (argc == 1 || optind >= argc)
> +		return COMMAND_ERROR_USAGE;

Isn't the first check also covered by the second check?

> +
> +	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. */
> +
> +	if (ioctl(fd, MEMGETINFO, &meminfo)) {
> +		perror("MEMGETINFO");
> +		goto err;
> +	}
> +
> +	if (ioctl(fd, MEMGETREGIONINFO, &memregion)) {
> +		perror("MEMGETREGIONINFO");
> +		goto err;
> +	}
> +
> +	if (ioctl(fd, ECCGETSTATS, &oldstats)) {
> +		printf("\n");

unnecessary new line, also in some other places.

> +		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;
> +
> +	/* Need to reopen device to erase */
> +	ret = close(fd);
> +	if (ret < 0) {
> +		perror("close");
> +		free(wbuf);
> +		printf("Error occurred.\n");
> +		return 1;
> +	}

Why do you have to reopen here? It looks like a bug in barebox if you
have to.

> +
> +	for (pass = 0; pass < nr_passes; pass++) {
> +		/* Need to reopen device to erase */
> +		fd = open(argv[optind], O_RDWR);
> +		if (fd < 0) {
> +			perror("open");
> +			goto err2;
> +		}
> +
> +		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);
> +
> +		ret = close(fd);
> +		if (ret < 0) {
> +			perror("close");
> +			free(wbuf);
> +			printf("Error occurred.\n");
> +			return 1;
> +		}
> +	}
> +
> +	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");

Generally it has proven good to put the functionality of a command into
a seperate function. This way we can easier decide to call it from other
places.


> +
> +	/* Free allocated wbuf memory */
> +	free(wbuf);
> +
> +	return 0;
> +err2:
> +	/* Free allocated wbuf memory */
> +	free(wbuf);

Please remove these comments, they contain no useful information.

Sascha

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



More information about the barebox mailing list