/*
 * Copyright (C) 2006-2008 Nokia Corporation
 * Copyright (C) 2015 sigma star gmbh
 *
 * 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; see the file COPYING. If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Check MTD device read.
 *
 * Author: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
 *
 * Based on linux readtest.c
 * Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
 */
#define PROGRAM_NAME "flash_readtest"

#include <mtd/mtd-user.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <libmtd.h>
#include <getopt.h>
#include <stdio.h>
#include <fcntl.h>
#include <time.h>

#include "common.h"

#define FLAG_VERBOSE 1

static int peb=-1, skip=-1, count=-1, flags=0, pgcnt, pgsize, fd;
static unsigned char *iobuf, *iobuf1;
static struct mtd_dev_info mtd;
static const char *mtddev;
static libmtd_t mtd_desc;

static const struct option options[] = {
	{ "help", no_argument, NULL, 'h' },
	{ "verbose", no_argument, NULL, 'v' },
	{ "peb", required_argument, NULL, 'b' },
	{ "count", required_argument, NULL, 'c' },
	{ "skip", required_argument, NULL, 's' },
	{ NULL, 0, NULL, 0 },
};

static NORETURN void usage(int status)
{
	fputs(
	"Usage: "PROGRAM_NAME" [OPTIONS] <device>\n\n"
	"Common options:\n"
	"  -h, --help          Display this help output\n"
	"  -b, --peb <num>     Start from this physical erase block\n"
	"  -c, --count <num>   Number of erase blocks to process (default: all)\n"
	"  -s, --skip <num>    Number of blocks to skip\n"
	"  -v, --verbose       Generate more verbose output\n\n",
	status==EXIT_SUCCESS ? stdout : stderr);
	exit(status);
}

static long read_num(int opt, const char *arg)
{
	char *end;
	long num;

	num = strtol(arg, &end, 0);

	if (!end || *end != '\0') {
		fprintf(stderr, "-%c: expected integer argument\n", opt);
		exit(EXIT_FAILURE);
	}
	return num;
}

static void process_options(int argc, char **argv)
{
	int c;

	while (1) {
		c = getopt_long(argc, argv, "hb:c:s:v", options, NULL);
		if (c == -1)
			break;

		switch (c) {
		case 'v':
			if (flags & FLAG_VERBOSE)
				goto failmulti;
			flags |= FLAG_VERBOSE;
			break;
		case 'b':
			if (peb >= 0)
				goto failmulti;
			peb = read_num(c, optarg);
			if (peb < 0)
				goto failarg;
			break;
		case 'c':
			if (count >= 0)
				goto failmulti;
			count = read_num(c, optarg);
			if (count < 0)
				goto failarg;
			break;
		case 's':
			if (skip >= 0)
				goto failmulti;
			skip = read_num(c, optarg);
			if (skip < 0)
				goto failarg;
			break;
		case 'h':
			usage(EXIT_SUCCESS);
		default:
			exit(EXIT_FAILURE);
		}
	}

	if (optind < argc) {
		mtddev = mtd_find_dev_node(argv[optind]);
		if (!mtddev)
			errmsg_die("Can't find MTD device %s", argv[optind]);
		optind++;
	} else {
		errmsg_die("No device specified!\n");
	}

	if (optind < argc)
		usage(EXIT_FAILURE);
	if (peb < 0)
		peb = 0;
	if (skip < 0)
		skip = 0;
	return;
failmulti:
	errmsg_die("'-%c' specified more than once!", c);
failarg:
	errmsg_die("Invalid argument for '-%c'!", c);
}

static int read_eraseblock_by_page(int ebnum)
{
	unsigned char *buf = iobuf, *oobbuf = iobuf1;
	uint64_t addr = ((uint64_t)ebnum) * ((uint64_t)mtd.eb_size);
	int i, ret;

	for (i = 0; i < pgcnt; ++i) {
		memset(buf, 0, pgsize);
		ret = mtd_read(&mtd, fd, ebnum, i*pgsize, buf, pgsize);
		if (ret) {
			fprintf(stderr, "Error reading block %d, page %d\n", ebnum, i);
			return -1;
		}
		if (mtd.oob_size) {
			ret = mtd_read_oob(mtd_desc, &mtd, fd,
							addr, mtd.oob_size, oobbuf);

			if (ret) {
				fprintf(stderr, "Error reading OOB in block %d, page %d\n",
						ebnum, i);
				return -1;
			}
			oobbuf += mtd.oob_size;
		}
		buf += pgsize;
		addr += pgsize;
	}

	if (flags & FLAG_VERBOSE)
		printf("Successfully read erase block %d\n", ebnum);

	return 0;
}

static void dump_eraseblock(int ebnum)
{
	char line[128];
	int i, j, n;
	int pg, oob;

	printf("dumping eraseblock %d\n", ebnum);
	n = mtd.eb_size;
	for (i = 0; i < n;) {
		char *p = line;

		p += sprintf(p, "%05x: ", i);
		for (j = 0; j < 32 && i < n; j++, i++)
			p += sprintf(p, "%02x", (unsigned int)iobuf[i]);
		printf("%s\n", line);
	}
	if (!mtd.oob_size)
		return;
	printf("dumping oob from eraseblock %d\n", ebnum);
	n = mtd.oob_size;
	for (pg = 0, i = 0; pg < pgcnt; ++pg) {
		for (oob = 0; oob < n;) {
			char *p = line;

			p += sprintf(p, "%05x: ", i);
			for (j = 0; j < 32 && oob < n; ++j, ++oob, ++i)
				p += sprintf(p, "%02x", (unsigned int)iobuf1[i]);
			printf("%s\n", line);
		}
	}
	putchar('\n');
}

int main(int argc, char **argv)
{
	int status = EXIT_SUCCESS, i, ret, blk;

	process_options(argc, argv);

	mtd_desc = libmtd_open();
	if (!mtd_desc)
		return errmsg("can't initialize libmtd");

	if (mtd_get_dev_info(mtd_desc, mtddev, &mtd) < 0)
		return errmsg("mtd_get_dev_info failed");

	if (mtd.subpage_size == 1) {
		puts("not NAND flash, assume page size is 512 bytes.");
		pgsize = 512;
	} else {
		pgsize = mtd.min_io_size;
	}

	pgcnt = mtd.eb_size / pgsize;

	if (count < 0)
		count = mtd.eb_cnt;

	if (peb >= mtd.eb_cnt)
		return errmsg("Physical erase block %d is out of range!\n", peb);

	if ((peb + (count - 1)*(skip + 1)) >= mtd.eb_cnt) {
		return errmsg("Given block range exceeds block count of %d!\n",
					mtd.eb_cnt);
	}

	iobuf = xmalloc(mtd.eb_size);
	iobuf1 = xmalloc(mtd.eb_size);

	if ((fd = open(mtddev, O_RDWR)) == -1) {
		perror(mtddev);
		status = EXIT_FAILURE;
		goto out;
	}

	/* Read all eraseblocks 1 page at a time */
	puts("testing page read");

	for (i = 0; i < count; ++i) {
		blk = peb + i*(skip+1);

		if (mtd_is_bad(&mtd, fd, blk)) {
			printf("Skipping bad block %d\n", blk);
			continue;
		}
		ret = read_eraseblock_by_page(blk);
		if (ret && (flags & FLAG_VERBOSE)) {
			dump_eraseblock(blk);
			status = EXIT_FAILURE;
		}
	}
out:
	free(iobuf);
	free(iobuf1);
	return status;
}
