/*-
 * Copyright (C) 2009-2012 Semihalf
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Control application for the NAND simulator.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <dev/nand/nandsim.h>
#include <dev/nand/nand_dev.h>

#include <ctype.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <sysexits.h>

#include "nandsim_cfgparse.h"

#define SIMDEVICE	"/dev/nandsim.ioctl"

#define error(fmt, args...) do { \
    printf("ERROR: " fmt "\n", ##args); } while (0)

#define warn(fmt, args...) do { \
    printf("WARNING: " fmt "\n", ##args); } while (0)

#define DEBUG
#undef DEBUG

#ifdef DEBUG
#define debug(fmt, args...) do { \
    printf("NANDSIM_CONF:" fmt "\n", ##args); } while (0)
#else
#define debug(fmt, args...) do {} while(0)
#endif

#define NANDSIM_RAM_LOG_SIZE 16384

#define MSG_NOTRUNNING		"Controller#%d is not running.Please start" \
    " it first."
#define MSG_RUNNING		"Controller#%d is already running!"
#define MSG_CTRLCHIPNEEDED	"You have to specify ctrl_no:cs_no pair!"
#define MSG_STATUSACQCTRLCHIP	"Could not acquire status for ctrl#%d chip#%d"
#define MSG_STATUSACQCTRL	"Could not acquire status for ctrl#%d"
#define MSG_NOCHIP		"There is no such chip configured (chip#%d "\
    "at ctrl#%d)!"

#define MSG_NOCTRL		"Controller#%d is not configured!"
#define MSG_NOTCONFIGDCTRLCHIP	"Chip connected to ctrl#%d at cs#%d " \
    "is not configured."

typedef int (commandfunc_t)(int , char **);

static struct nandsim_command *getcommand(char *);
static int parse_devstring(char *, int *, int *);
static void printchip(struct sim_chip *, uint8_t);
static void printctrl(struct sim_ctrl *);
static int opendev(int *);
static commandfunc_t cmdstatus;
static commandfunc_t cmdconf;
static commandfunc_t cmdstart;
static commandfunc_t cmdstop;
static commandfunc_t cmdmod;
static commandfunc_t cmderror;
static commandfunc_t cmdbb;
static commandfunc_t cmdfreeze;
static commandfunc_t cmdlog;
static commandfunc_t cmdstats;
static commandfunc_t cmddump;
static commandfunc_t cmdrestore;
static commandfunc_t cmddestroy;
static commandfunc_t cmdhelp;
static int checkusage(int, int, char **);
static int is_chip_created(int, int, int *);
static int is_ctrl_created(int, int *);
static int is_ctrl_running(int, int *);
static int assert_chip_connected(int , int);
static int printstats(int, int, uint32_t, int);

struct nandsim_command {
	const char	*cmd_name;	/* Command name */
	commandfunc_t	*commandfunc;	/* Ptr to command function */
	uint8_t		req_argc;	/* Mandatory arguments count */
	const char	*usagestring;	/* Usage string */
};

static struct nandsim_command commands[] = {
	{"status", cmdstatus, 1,
	    "status <ctl_no|--all|-a> [-v]\n" },
	{"conf", cmdconf, 1,
	    "conf <filename>\n" },
	{"start", cmdstart, 1,
	    "start <ctrl_no>\n" },
	{"mod", cmdmod, 2,
	    "mod [-l <loglevel>] | <ctl_no:cs_no> [-p <prog_time>]\n"
	    "\t[-e <erase_time>] [-r <read_time>]\n"
	    "\t[-E <error_ratio>] | [-h]\n" },
	{"stop", cmdstop, 1,
	    "stop <ctrl_no>\n" },
	{"error", cmderror, 5,
	    "error <ctrl_no:cs_no> <page_num> <column> <length> <pattern>\n" },
	{"bb", cmdbb, 2,
	    "bb <ctl_no:cs_no>  [blk_num1,blk_num2,..] [-U] [-L]\n" },
	{"freeze", cmdfreeze, 1,
	    "freeze [ctrl_no]\n" },
	{"log", cmdlog, 1,
	    "log <ctrl_no|--all|-a>\n" },
	{"stats", cmdstats, 2,
	    "stats <ctrl_no:cs_no> <pagenumber>\n" },
	{"dump", cmddump, 2,
	    "dump <ctrl_no:cs_no> <filename>\n" },
	{"restore", cmdrestore, 2,
	    "restore <ctrl_no:chip_no> <filename>\n" },
	{"destroy", cmddestroy, 1,
	    "destroy <ctrl_no[:cs_no]|--all|-a>\n" },
	{"help", cmdhelp, 0,
	    "help [-v]" },
	{NULL, NULL, 0, NULL},
};


/* Parse command name, and start appropriate function */
static struct nandsim_command*
getcommand(char *arg)
{
	struct nandsim_command *opts;

	for (opts = commands; (opts != NULL) &&
	    (opts->cmd_name != NULL); opts++) {
		if (strcmp(opts->cmd_name, arg) == 0)
			return (opts);
	}
	return (NULL);
}

/*
 * Parse given string in format <ctrl_no>:<cs_no>, if possible -- set
 * ctrl and/or cs, and return 0 (success) or 1 (in case of error).
 *
 * ctrl == 0xff && chip == 0xff  : '--all' flag specified
 * ctrl != 0xff && chip != 0xff  : both ctrl & chip were specified
 * ctrl != 0xff && chip == 0xff  : only ctrl was specified
 */
static int
parse_devstring(char *str, int *ctrl, int *cs)
{
	char *tmpstr;
	unsigned int num = 0;

	/* Ignore white spaces at the beginning */
	while (isspace(*str) && (*str != '\0'))
		str++;

	*ctrl = 0xff;
	*cs = 0xff;
	if (strcmp(str, "--all") == 0 ||
	    strcmp(str, "-a") == 0) {
		/* If --all or -a is specified, ctl==chip==0xff */
		debug("CTRL=%d CHIP=%d\n", *ctrl, *cs);
		return (0);
	}
	/* Separate token and try to convert it to int */
	tmpstr = (char *)strtok(str, ":");
	if ((tmpstr != NULL) && (*tmpstr != '\0')) {
		if (convert_arguint(tmpstr, &num) != 0)
			return (1);

		if (num > MAX_SIM_DEV - 1) {
			error("Invalid ctrl_no supplied: %s. Valid ctrl_no "
			    "value must lie between 0 and 3!", tmpstr);
			return (1);
		}

		*ctrl = num;
		tmpstr = (char *)strtok(NULL, ":");

		if ((tmpstr != NULL) && (*tmpstr != '\0')) {
			if (convert_arguint(tmpstr, &num) != 0)
				return (1);

			/* Check if chip_no is valid */
			if (num > MAX_CTRL_CS - 1) {
				error("Invalid chip_no supplied: %s. Valid "
				    "chip_no value must lie between 0 and 3!",
				    tmpstr);
				return (1);
			}
			*cs = num;
		}
	} else
		/* Empty devstring supplied */
		return (1);

	debug("CTRL=%d CHIP=%d\n", *ctrl, *cs);
	return (0);
}

static int
opendev(int *fd)
{

	*fd = open(SIMDEVICE, O_RDWR);
	if (*fd == -1) {
		error("Could not open simulator device file (%s)!",
		    SIMDEVICE);
		return (EX_OSFILE);
	}
	return (EX_OK);
}

static int
opencdev(int *cdevd, int ctrl, int chip)
{
	char fname[255];

	sprintf(fname, "/dev/nandsim%d.%d", ctrl, chip);
	*cdevd = open(fname, O_RDWR);
	if (*cdevd == -1)
		return (EX_NOINPUT);

	return (EX_OK);
}

/*
 * Check if given arguments count match requirements. If no, or
 * --help (-h) flag is specified -- return 1 (print usage)
 */
static int
checkusage(int gargc, int argsreqd, char **gargv)
{

	if (gargc < argsreqd + 2 || (gargc >= (argsreqd + 2) &&
	    (strcmp(gargv[1], "--help") == 0 ||
	    strcmp(gargv[1], "-h") == 0)))
		return (1);

	return (0);
}

static int
cmdstatus(int gargc, char **gargv)
{
	int chip = 0, ctl = 0, err = 0, fd, idx, idx2, start, stop;
	uint8_t verbose = 0;
	struct sim_ctrl ctrlconf;
	struct sim_chip chipconf;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err) {
		return (EX_USAGE);
	} else if (ctl == 0xff) {
		/* Every controller */
		start = 0;
		stop = MAX_SIM_DEV-1;
	} else {
		/* Specified controller only */
		start = ctl;
		stop = ctl;
	}

	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	for (idx = 0; idx < gargc; idx ++)
		if (strcmp(gargv[idx], "-v") == 0 ||
		    strcmp(gargv[idx], "--verbose") == 0)
			verbose = 1;

	for (idx = start; idx <= stop; idx++) {
		ctrlconf.num = idx;
		err = ioctl(fd, NANDSIM_STATUS_CTRL, &ctrlconf);
		if (err) {
			err = EX_SOFTWARE;
			error(MSG_STATUSACQCTRL, idx);
			continue;
		}

		printctrl(&ctrlconf);

		for (idx2 = 0; idx2 < MAX_CTRL_CS; idx2++) {
			chipconf.num = idx2;
			chipconf.ctrl_num = idx;

			err = ioctl(fd, NANDSIM_STATUS_CHIP, &chipconf);
			if (err) {
				err = EX_SOFTWARE;
				error(MSG_STATUSACQCTRL, idx);
				continue;
			}

			printchip(&chipconf, verbose);
		}
	}
	close(fd);
	return (err);
}

static int
cmdconf(int gargc __unused, char **gargv)
{
	int err;

	err = parse_config(gargv[2], SIMDEVICE);
	if (err)
		return (EX_DATAERR);

	return (EX_OK);
}

static int
cmdstart(int gargc __unused, char **gargv)
{
	int chip = 0, ctl = 0, err = 0, fd, running, state;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);

	err = is_ctrl_created(ctl, &state);
	if (err) {
		return (EX_SOFTWARE);
	} else if (state == 0) {
		error(MSG_NOCTRL, ctl);
		return (EX_SOFTWARE);
	}

	err = is_ctrl_running(ctl, &running);
	if (err)
		return (EX_SOFTWARE);

	if (running) {
		warn(MSG_RUNNING, ctl);
	} else {
		if (opendev(&fd) != EX_OK)
			return (EX_OSFILE);

		err = ioctl(fd, NANDSIM_START_CTRL, &ctl);
		close(fd);
		if (err) {
			error("Cannot start controller#%d", ctl);
			err = EX_SOFTWARE;
		}
	}
	return (err);
}

static int
cmdstop(int gargc __unused, char **gargv)
{
	int chip = 0, ctl = 0, err = 0, fd, running;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);

	err = is_ctrl_running(ctl, &running);
	if (err)
		return (EX_SOFTWARE);

	if (!running) {
		error(MSG_NOTRUNNING, ctl);
	} else {
		if (opendev(&fd) != EX_OK)
			return (EX_OSFILE);

		err = ioctl(fd, NANDSIM_STOP_CTRL, &ctl);
		close(fd);
		if (err) {
			error("Cannot stop controller#%d", ctl);
			err = EX_SOFTWARE;
		}
	}

	return (err);
}

static int
cmdmod(int gargc __unused, char **gargv)
{
	int chip, ctl, err = 0, fd = -1, i;
	struct sim_mod mods;

	if (gargc >= 4) {
		if (strcmp(gargv[2], "--loglevel") == 0 || strcmp(gargv[2],
		    "-l") == 0) {
			/* Set loglevel (ctrl:chip pair independent) */
			mods.field = SIM_MOD_LOG_LEVEL;

			if (convert_arguint(gargv[3], &mods.new_value) != 0)
				return (EX_SOFTWARE);

			if (opendev(&fd) != EX_OK)
				return (EX_OSFILE);

			err = ioctl(fd, NANDSIM_MODIFY, &mods);
			if (err) {
				error("simulator parameter %s could not be "
				    "modified !", gargv[3]);
				close(fd);
				return (EX_SOFTWARE);
			}

			debug("request : loglevel = %d\n", mods.new_value);
			close(fd);
			return (EX_OK);
		}
	}

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);

	else if (chip == 0xff) {
		error(MSG_CTRLCHIPNEEDED);
		return (EX_USAGE);
	}

	if (!assert_chip_connected(ctl, chip))
		return (EX_SOFTWARE);

	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	/* Find out which flags were passed */
	for (i = 3; i < gargc; i++) {

		if (convert_arguint(gargv[i + 1], &mods.new_value) != 0)
			continue;

		if (strcmp(gargv[i], "--prog-time") == 0 ||
		    strcmp(gargv[i], "-p") == 0) {

			mods.field = SIM_MOD_PROG_TIME;
			debug("request : progtime = %d\n", mods.new_value);

		} else if (strcmp(gargv[i], "--erase-time") == 0 ||
		    strcmp(gargv[i], "-e") == 0) {

			mods.field = SIM_MOD_ERASE_TIME;
			debug("request : eraseime = %d\n", mods.new_value);

		} else if (strcmp(gargv[i], "--read-time") == 0 ||
		    strcmp(gargv[i], "-r") == 0) {

			mods.field = SIM_MOD_READ_TIME;
			debug("request : read_time = %d\n", mods.new_value);

		} else if (strcmp(gargv[i], "--error-ratio") == 0 ||
		    strcmp(gargv[i], "-E") == 0) {

			mods.field = SIM_MOD_ERROR_RATIO;
			debug("request : error_ratio = %d\n", mods.new_value);

		} else {
			/* Flag not recognized, or nothing specified. */
			error("Unrecognized flag:%s\n", gargv[i]);
			if (fd >= 0)
				close(fd);
			return (EX_USAGE);
		}

		mods.chip_num = chip;
		mods.ctrl_num = ctl;

		/* Call appropriate ioctl */
		err = ioctl(fd, NANDSIM_MODIFY, &mods);
		if (err) {
			error("simulator parameter %s could not be modified! ",
			    gargv[i]);
			continue;
		}
		i++;
	}
	close(fd);
	return (EX_OK);
}

static int
cmderror(int gargc __unused, char **gargv)
{
	uint32_t page, column, len, pattern;
	int chip = 0, ctl = 0, err = 0, fd;
	struct sim_error sim_err;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);

	if (chip == 0xff) {
		error(MSG_CTRLCHIPNEEDED);
		return (EX_USAGE);
	}

	if (convert_arguint(gargv[3], &page) ||
	    convert_arguint(gargv[4], &column) ||
	    convert_arguint(gargv[5], &len) ||
	    convert_arguint(gargv[6], &pattern))
		return (EX_SOFTWARE);

	if (!assert_chip_connected(ctl, chip))
		return (EX_SOFTWARE);

	sim_err.page_num = page;
	sim_err.column = column;
	sim_err.len = len;
	sim_err.pattern = pattern;
	sim_err.ctrl_num = ctl;
	sim_err.chip_num = chip;

	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	err = ioctl(fd, NANDSIM_INJECT_ERROR, &sim_err);

	close(fd);
	if (err) {
		error("Could not inject error !");
		return (EX_SOFTWARE);
	}
	return (EX_OK);
}

static int
cmdbb(int gargc, char **gargv)
{
	struct sim_block_state bs;
	struct chip_param_io cparams;
	uint32_t blkidx;
	int c, cdevd, chip = 0, ctl = 0, err = 0, fd, idx;
	uint8_t flagL = 0, flagU = 0;
	int *badblocks = NULL;

	/* Check for --list/-L or --unmark/-U flags */
	for (idx = 3; idx < gargc; idx++) {
		if (strcmp(gargv[idx], "--list") == 0 ||
		    strcmp(gargv[idx], "-L") == 0)
			flagL = idx;
		if (strcmp(gargv[idx], "--unmark") == 0 ||
		    strcmp(gargv[idx], "-U") == 0)
			flagU = idx;
	}

	if (flagL == 2 || flagU == 2 || flagU == 3)
		return (EX_USAGE);

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err) {
		return (EX_USAGE);
	}
	if (chip == 0xff || ctl == 0xff) {
		error(MSG_CTRLCHIPNEEDED);
		return (EX_USAGE);
	}

	bs.ctrl_num = ctl;
	bs.chip_num = chip;

	if (!assert_chip_connected(ctl, chip))
		return (EX_SOFTWARE);

	if (opencdev(&cdevd, ctl, chip) != EX_OK)
		return (EX_OSFILE);

	err = ioctl(cdevd, NAND_IO_GET_CHIP_PARAM, &cparams);
	if (err)
		return (EX_SOFTWARE);

	close(cdevd);

	bs.ctrl_num = ctl;
	bs.chip_num = chip;

	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	if (flagL != 3) {
		/*
		 * Flag -L was specified either after blocklist or was not
		 * specified at all.
		 */
		c = parse_intarray(gargv[3], &badblocks);

		for (idx = 0; idx < c; idx++) {
			bs.block_num = badblocks[idx];
			/* Do not change wearout */
			bs.wearout = -1;
			bs.state = (flagU == 0) ? NANDSIM_BAD_BLOCK :
			    NANDSIM_GOOD_BLOCK;

			err = ioctl(fd, NANDSIM_SET_BLOCK_STATE, &bs);
			if (err) {
				error("Could not set bad block(%d) for "
				    "controller (%d)!",
				    badblocks[idx], ctl);
				err = EX_SOFTWARE;
				break;
			}
		}
	}
	if (flagL != 0) {
		/* If flag -L was specified (anywhere) */
		for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
			bs.block_num = blkidx;
			/* Do not change the wearout */
			bs.wearout = -1;
			err = ioctl(fd, NANDSIM_GET_BLOCK_STATE, &bs);
			if (err) {
				error("Could not acquire block state");
				err = EX_SOFTWARE;
				continue;
			}
			printf("Block#%d: wear count: %d %s\n", blkidx,
			    bs.wearout,
			    (bs.state == NANDSIM_BAD_BLOCK) ? "BAD":"GOOD");
		}
	}
	close(fd);
	return (err);
}

static int
cmdfreeze(int gargc __unused, char **gargv)
{
	int chip = 0, ctl = 0, err = 0, fd, i, start = 0, state, stop = 0;
	struct sim_ctrl_chip ctrlchip;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);

	if (ctl == 0xff) {
		error("You have to specify at least controller number");
		return (EX_USAGE);
	}

	if (ctl != 0xff && chip == 0xff) {
		start = 0;
		stop = MAX_CTRL_CS - 1;
	} else {
		start = chip;
		stop = chip;
	}

	ctrlchip.ctrl_num = ctl;

	err = is_ctrl_running(ctl, &state);
	if (err)
		return (EX_SOFTWARE);
	if (state == 0) {
		error(MSG_NOTRUNNING, ctl);
		return (EX_SOFTWARE);
	}

	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	for (i = start; i <= stop; i++) {
		err = is_chip_created(ctl, i, &state);
		if (err)
			return (EX_SOFTWARE);
		else if (state == 0) {
			continue;
		}

		ctrlchip.chip_num = i;
		err = ioctl(fd, NANDSIM_FREEZE, &ctrlchip);
		if (err) {
			error("Could not freeze ctrl#%d chip#%d", ctl, i);
			close(fd);
			return (EX_SOFTWARE);
		}
	}
	close(fd);
	return (EX_OK);
}

static int
cmdlog(int gargc __unused, char **gargv)
{
	struct sim_log log;
	int chip = 0, ctl = 0, err = 0, fd, idx, start = 0, stop = 0;
	char *logbuf;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);

	logbuf = (char *)malloc(sizeof(char) * NANDSIM_RAM_LOG_SIZE);
	if (logbuf == NULL) {
		error("Not enough memory to create log buffer");
		return (EX_SOFTWARE);
	}

	memset(logbuf, 0, NANDSIM_RAM_LOG_SIZE);
	log.log = logbuf;
	log.len = NANDSIM_RAM_LOG_SIZE;

	if (ctl == 0xff) {
		start = 0;
		stop = MAX_SIM_DEV-1;
	} else {
		start = ctl;
		stop = ctl;
	}

	if (opendev(&fd) != EX_OK) {
		free(logbuf);
		return (EX_OSFILE);
	}

	/* Print logs for selected controller(s) */
	for (idx = start; idx <= stop; idx++) {
		log.ctrl_num = idx;

		err = ioctl(fd, NANDSIM_PRINT_LOG, &log);
		if (err) {
			error("Could not get log for controller %d!", idx);
			continue;
		}

		printf("Logs for controller#%d:\n%s\n", idx, logbuf);
	}

	free(logbuf);
	close(fd);
	return (EX_OK);
}

static int
cmdstats(int gargc __unused, char **gargv)
{
	int cdevd, chip = 0, ctl = 0, err = 0;
	uint32_t pageno = 0;

	err = parse_devstring(gargv[2], &ctl, &chip);

	if (err)
		return (EX_USAGE);

	if (chip == 0xff) {
		error(MSG_CTRLCHIPNEEDED);
		return (EX_USAGE);
	}

	if (convert_arguint(gargv[3], &pageno) != 0)
		return (EX_USAGE);

	if (!assert_chip_connected(ctl, chip))
		return (EX_SOFTWARE);

	if (opencdev(&cdevd, ctl, chip) != EX_OK)
		return (EX_OSFILE);

	err = printstats(ctl, chip, pageno, cdevd);
	if (err) {
		close(cdevd);
		return (EX_SOFTWARE);
	}
	close(cdevd);
	return (EX_OK);
}

static int
cmddump(int gargc __unused, char **gargv)
{
	struct sim_dump dump;
	struct sim_block_state bs;
	struct chip_param_io cparams;
	int chip = 0, ctl = 0, err = EX_OK, fd, dumpfd;
	uint32_t blkidx, bwritten = 0, totalwritten = 0;
	void *buf;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);

	if (chip == 0xff || ctl == 0xff) {
		error(MSG_CTRLCHIPNEEDED);
		return (EX_USAGE);
	}

	if (!assert_chip_connected(ctl, chip))
		return (EX_SOFTWARE);

	if (opencdev(&fd, ctl, chip) != EX_OK)
		return (EX_OSFILE);

	err = ioctl(fd, NAND_IO_GET_CHIP_PARAM, &cparams);
	if (err) {
		error("Cannot get parameters for chip %d:%d", ctl, chip);
		close(fd);
		return (EX_SOFTWARE);
	}
	close(fd);

	dump.ctrl_num = ctl;
	dump.chip_num = chip;

	dump.len = cparams.pages_per_block * (cparams.page_size +
	    cparams.oob_size);

	buf = malloc(dump.len);
	if (buf == NULL) {
		error("Could not allocate memory!");
		return (EX_SOFTWARE);
	}
	dump.data = buf;

	errno = 0;
	dumpfd = open(gargv[3], O_WRONLY | O_CREAT, 0666);
	if (dumpfd == -1) {
		error("Cannot create dump file.");
		free(buf);
		return (EX_SOFTWARE);
	}

	if (opendev(&fd)) {
		close(dumpfd);
		free(buf);
		return (EX_SOFTWARE);
	}

	bs.ctrl_num = ctl;
	bs.chip_num = chip;

	/* First uint32_t in file shall contain block count */
	if (write(dumpfd, &cparams, sizeof(cparams)) < (int)sizeof(cparams)) {
		error("Error writing to dumpfile!");
		close(fd);
		close(dumpfd);
		free(buf);
		return (EX_SOFTWARE);
	}

	/*
	 * First loop acquires blocks states and writes them to
	 * the dump file.
	 */
	for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
		bs.block_num = blkidx;
		err = ioctl(fd, NANDSIM_GET_BLOCK_STATE, &bs);
		if (err) {
			error("Could not get bad block(%d) for "
			    "controller (%d)!", blkidx, ctl);
			close(fd);
			close(dumpfd);
			free(buf);
			return (EX_SOFTWARE);
		}

		bwritten = write(dumpfd, &bs, sizeof(bs));
		if (bwritten != sizeof(bs)) {
			error("Error writing to dumpfile");
			close(fd);
			close(dumpfd);
			free(buf);
			return (EX_SOFTWARE);
		}
	}

	/* Second loop dumps the data */
	for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
		debug("Block#%d...", blkidx);
		dump.block_num = blkidx;

		err = ioctl(fd, NANDSIM_DUMP, &dump);
		if (err) {
			error("Could not dump ctrl#%d chip#%d "
			    "block#%d", ctl, chip, blkidx);
			err = EX_SOFTWARE;
			break;
		}

		bwritten = write(dumpfd, dump.data, dump.len);
		if (bwritten != dump.len) {
			error("Error writing to dumpfile");
			err = EX_SOFTWARE;
			break;
		}
		debug("OK!\n");
		totalwritten += bwritten;
	}
	printf("%d out of %d B written.\n", totalwritten, dump.len * blkidx);

	close(fd);
	close(dumpfd);
	free(buf);
	return (err);
}

static int
cmdrestore(int gargc __unused, char **gargv)
{
	struct sim_dump dump;
	struct sim_block_state bs;
	struct stat filestat;
	int chip = 0, ctl = 0, err = 0, fd, dumpfd = -1;
	uint32_t blkidx, blksz, fsize = 0, expfilesz;
	void *buf;
	struct chip_param_io cparams, dumpcparams;

	err = parse_devstring(gargv[2], &ctl, &chip);
	if (err)
		return (EX_USAGE);
	else if (ctl == 0xff) {
		error(MSG_CTRLCHIPNEEDED);
		return (EX_USAGE);
	}

	if (!assert_chip_connected(ctl, chip))
		return (EX_SOFTWARE);

	/* Get chip geometry */
	if (opencdev(&fd, ctl, chip) != EX_OK)
		return (EX_OSFILE);

	err = ioctl(fd, NAND_IO_GET_CHIP_PARAM, &cparams);
	if (err) {
		error("Cannot get parameters for chip %d:%d", ctl, chip);
		close(fd);
		return (err);
	}
	close(fd);

	/* Obtain dump file size */
	errno = 0;
	if (stat(gargv[3], &filestat) != 0) {
		error("Could not acquire file size! : %s",
		    strerror(errno));
		return (EX_IOERR);
	}

	fsize = filestat.st_size;
	blksz = cparams.pages_per_block * (cparams.page_size +
	    cparams.oob_size);

	/* Expected dump file size for chip */
	expfilesz = cparams.blocks * (blksz + sizeof(bs)) + sizeof(cparams);

	if (fsize != expfilesz) {
		error("File size does not match chip geometry (file size: %d"
		    ", dump size: %d)", fsize, expfilesz);
		return (EX_SOFTWARE);
	}

	dumpfd = open(gargv[3], O_RDONLY);
	if (dumpfd == -1) {
		error("Could not open dump file!");
		return (EX_IOERR);
	}

	/* Read chip params saved in dumpfile */
	read(dumpfd, &dumpcparams, sizeof(dumpcparams));

	/* XXX */
	if (bcmp(&dumpcparams, &cparams, sizeof(cparams)) != 0) {
		error("Supplied dump is created for a chip with different "
		    "chip configuration!");
		close(dumpfd);
		return (EX_SOFTWARE);
	}

	if (opendev(&fd) != EX_OK) {
		close(dumpfd);
		return (EX_OSFILE);
	}

	buf = malloc(blksz);
	if (buf == NULL) {
		error("Could not allocate memory for block buffer");
		close(dumpfd);
		close(fd);
		return (EX_SOFTWARE);
	}

	dump.ctrl_num = ctl;
	dump.chip_num = chip;
	dump.data = buf;
	/* Restore block states and wearouts */
	for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
		dump.block_num = blkidx;
		if (read(dumpfd, &bs, sizeof(bs)) != sizeof(bs)) {
			error("Error reading dumpfile");
			close(dumpfd);
			close(fd);
			free(buf);
			return (EX_SOFTWARE);
		}
		bs.ctrl_num = ctl;
		bs.chip_num = chip;
		debug("BLKIDX=%d BLOCKS=%d CTRL=%d CHIP=%d STATE=%d\n"
		    "WEAROUT=%d BS.CTRL_NUM=%d BS.CHIP_NUM=%d\n",
		    blkidx, cparams.blocks, dump.ctrl_num, dump.chip_num,
		    bs.state, bs.wearout, bs.ctrl_num, bs.chip_num);

		err = ioctl(fd, NANDSIM_SET_BLOCK_STATE, &bs);
		if (err) {
			error("Could not set bad block(%d) for "
			    "controller: %d, chip: %d!", blkidx, ctl, chip);
			close(dumpfd);
			close(fd);
			free(buf);
			return (EX_SOFTWARE);
		}
	}
	/* Restore data */
	for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
		errno = 0;
		dump.len = read(dumpfd, buf, blksz);
		if (errno) {
			error("Failed to read block#%d from dumpfile.", blkidx);
			err = EX_SOFTWARE;
			break;
		}
		dump.block_num = blkidx;
		err = ioctl(fd, NANDSIM_RESTORE, &dump);
		if (err) {
			error("Could not restore block#%d of ctrl#%d chip#%d"
			    ": %s", blkidx, ctl, chip, strerror(errno));
			err = EX_SOFTWARE;
			break;
		}
	}

	free(buf);
	close(dumpfd);
	close(fd);
	return (err);

}

static int
cmddestroy(int gargc __unused, char **gargv)
{
	int chip = 0, ctl = 0, err = 0, fd, idx, idx2, state;
	int chipstart, chipstop, ctrlstart, ctrlstop;
	struct sim_chip_destroy chip_destroy;

	err = parse_devstring(gargv[2], &ctl, &chip);

	if (err)
		return (EX_USAGE);

	if (ctl == 0xff) {
		/* Every chip at every controller */
		ctrlstart = chipstart = 0;
		ctrlstop = MAX_SIM_DEV - 1;
		chipstop = MAX_CTRL_CS - 1;
	} else {
		ctrlstart = ctrlstop = ctl;
		if (chip == 0xff) {
			/* Every chip at selected controller */
			chipstart = 0;
			chipstop = MAX_CTRL_CS - 1;
		} else
			/* Selected chip at selected controller */
			chipstart = chipstop = chip;
	}
	debug("CTRLSTART=%d CTRLSTOP=%d CHIPSTART=%d CHIPSTOP=%d\n",
	    ctrlstart, ctrlstop, chipstart, chipstop);
	for (idx = ctrlstart; idx <= ctrlstop; idx++) {
		err = is_ctrl_created(idx, &state);
		if (err) {
			error("Could not acquire ctrl#%d state. Cannot "
			    "destroy controller.", idx);
			return (EX_SOFTWARE);
		}
		if (state == 0) {
			continue;
		}
		err = is_ctrl_running(idx, &state);
		if (err) {
			error(MSG_STATUSACQCTRL, idx);
			return (EX_SOFTWARE);
		}
		if (state != 0) {
			error(MSG_RUNNING, ctl);
			return (EX_SOFTWARE);
		}
		if (opendev(&fd) != EX_OK)
			return (EX_OSFILE);

		for (idx2 = chipstart; idx2 <= chipstop; idx2++) {
			err = is_chip_created(idx, idx2, &state);
			if (err) {
				error(MSG_STATUSACQCTRLCHIP, idx2, idx);
				continue;
			}
			if (state == 0)
				/* There is no such chip running */
				continue;
			chip_destroy.ctrl_num = idx;
			chip_destroy.chip_num = idx2;
			ioctl(fd, NANDSIM_DESTROY_CHIP,
			    &chip_destroy);
		}
		/* If chip isn't explicitly specified -- destroy ctrl */
		if (chip == 0xff) {
			err = ioctl(fd, NANDSIM_DESTROY_CTRL, &idx);
			if (err) {
				error("Could not destroy ctrl#%d", idx);
				continue;
			}
		}
		close(fd);
	}
	return (err);
}

int
main(int argc, char **argv)
{
	struct nandsim_command *cmdopts;
	int retcode = 0;

	if (argc < 2) {
		cmdhelp(argc, argv);
		retcode = EX_USAGE;
	} else {
		cmdopts = getcommand(argv[1]);
		if (cmdopts != NULL && cmdopts->commandfunc != NULL) {
			if (checkusage(argc, cmdopts->req_argc, argv) == 1) {
				/* Print command specific usage */
				printf("nandsim %s", cmdopts->usagestring);
				return (EX_USAGE);
			}
			retcode = cmdopts->commandfunc(argc, argv);

			if (retcode == EX_USAGE) {
				/* Print command-specific usage */
				printf("nandsim %s", cmdopts->usagestring);
			} else if (retcode == EX_OSFILE) {
				error("Could not open device file");
			}

		} else {
			error("Unknown command!");
			retcode = EX_USAGE;
		}
	}
	return (retcode);
}

static int
cmdhelp(int gargc __unused, char **gargv __unused)
{
	struct nandsim_command *opts;

	printf("usage:  nandsim <command> [command params] [params]\n\n");

	for (opts = commands; (opts != NULL) &&
	    (opts->cmd_name != NULL); opts++)
		printf("nandsim %s", opts->usagestring);

	printf("\n");
	return (EX_OK);
}

static void
printchip(struct sim_chip *chip, uint8_t verbose)
{

	if (chip->created == 0)
		return;
	if (verbose > 0) {
		printf("\n[Chip info]\n");
		printf("num= %d\nctrl_num=%d\ndevice_id=%02x"
		    "\tmanufacturer_id=%02x\ndevice_model=%s\nmanufacturer="
		    "%s\ncol_addr_cycles=%d\nrow_addr_cycles=%d"
		    "\npage_size=%d\noob_size=%d\npages_per_block=%d\n"
		    "blocks_per_lun=%d\nluns=%d\n\nprog_time=%d\n"
		    "erase_time=%d\nread_time=%d\n"
		    "error_ratio=%d\nwear_level=%d\nwrite_protect=%c\n"
		    "chip_width=%db\n", chip->num, chip->ctrl_num,
		    chip->device_id, chip->manufact_id,chip->device_model,
		    chip->manufacturer, chip->col_addr_cycles,
		    chip->row_addr_cycles, chip->page_size,
		    chip->oob_size, chip->pgs_per_blk, chip->blks_per_lun,
		    chip->luns,chip->prog_time, chip->erase_time,
		    chip->read_time, chip->error_ratio, chip->wear_level,
		    (chip->is_wp == 0) ? 'N':'Y', chip->width);
	} else {
		printf("[Chip info]\n");
		printf("\tnum=%d\n\tdevice_model=%s\n\tmanufacturer=%s\n"
		    "\tpage_size=%d\n\twrite_protect=%s\n",
		    chip->num, chip->device_model, chip->manufacturer,
		    chip->page_size, (chip->is_wp == 0) ? "NO":"YES");
	}
}

static void
printctrl(struct sim_ctrl *ctrl)
{
	int i;

	if (ctrl->created == 0) {
		printf(MSG_NOCTRL "\n", ctrl->num);
		return;
	}
	printf("\n[Controller info]\n");
	printf("\trunning: %s\n", ctrl->running ? "yes" : "no");
	printf("\tnum cs: %d\n", ctrl->num_cs);
	printf("\tecc: %d\n", ctrl->ecc);
	printf("\tlog_filename: %s\n", ctrl->filename);
	printf("\tecc_layout:");
	for (i = 0; i < MAX_ECC_BYTES; i++) {
		if (ctrl->ecc_layout[i] == 0xffff)
			break;
		else
			printf("%c%d", i%16 ? ' ' : '\n',
			    ctrl->ecc_layout[i]);
	}
	printf("\n");
}

static int
is_ctrl_running(int ctrl_no, int *running)
{
	struct sim_ctrl ctrl;
	int err, fd;

	ctrl.num = ctrl_no;
	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	err = ioctl(fd, NANDSIM_STATUS_CTRL, &ctrl);
	if (err) {
		error(MSG_STATUSACQCTRL, ctrl_no);
		close(fd);
		return (err);
	}
	*running = ctrl.running;
	close(fd);
	return (0);
}

static int
is_ctrl_created(int ctrl_no, int *created)
{
	struct sim_ctrl ctrl;
	int err, fd;

	ctrl.num = ctrl_no;

	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	err = ioctl(fd, NANDSIM_STATUS_CTRL, &ctrl);
	if (err) {
		error("Could not acquire conf for ctrl#%d", ctrl_no);
		close(fd);
		return (err);
	}
	*created = ctrl.created;
	close(fd);
	return (0);
}

static int
is_chip_created(int ctrl_no, int chip_no, int *created)
{
	struct sim_chip chip;
	int err, fd;

	chip.ctrl_num = ctrl_no;
	chip.num = chip_no;

	if (opendev(&fd) != EX_OK)
		return (EX_OSFILE);

	err = ioctl(fd, NANDSIM_STATUS_CHIP, &chip);
	if (err) {
		error("Could not acquire conf for chip#%d", chip_no);
		close(fd);
		return (err);
	}
	*created = chip.created;
	close(fd);
	return (0);
}

static int
assert_chip_connected(int ctrl_no, int chip_no)
{
	int created, running;

	if (is_ctrl_created(ctrl_no, &created))
		return (0);

	if (!created) {
		error(MSG_NOCTRL, ctrl_no);
		return (0);
	}

	if (is_chip_created(ctrl_no, chip_no, &created))
		return (0);

	if (!created) {
		error(MSG_NOTCONFIGDCTRLCHIP, ctrl_no, chip_no);
		return (0);
	}

	if (is_ctrl_running(ctrl_no, &running))
		return (0);

	if (!running) {
		error(MSG_NOTRUNNING, ctrl_no);
		return (0);
	}

	return (1);
}

static int
printstats(int ctrlno, int chipno, uint32_t pageno, int cdevd)
{
	struct page_stat_io pstats;
	struct block_stat_io bstats;
	struct chip_param_io cparams;
	uint32_t blkidx;
	int err;

	/* Gather information about chip */
	err = ioctl(cdevd, NAND_IO_GET_CHIP_PARAM, &cparams);

	if (err) {
		error("Could not acquire chip info for chip attached to cs#"
		    "%d, ctrl#%d", chipno, ctrlno);
		return (EX_SOFTWARE);
	}

	blkidx = (pageno / cparams.pages_per_block);
	bstats.block_num = blkidx;

	err = ioctl(cdevd, NAND_IO_BLOCK_STAT, &bstats);
	if (err) {
		error("Could not acquire block#%d statistics!", blkidx);
		return (ENXIO);
	}

	printf("Block #%d erased: %d\n", blkidx, bstats.block_erased);
	pstats.page_num = pageno;

	err = ioctl(cdevd, NAND_IO_PAGE_STAT, &pstats);
	if (err) {
		error("Could not acquire page statistics!");
		return (ENXIO);
	}

	debug("BLOCKIDX = %d PAGENO (REL. TO BLK) = %d\n", blkidx,
	    pstats.page_num);

	printf("Page#%d : reads:%d writes:%d \n\traw reads:%d raw writes:%d "
	    "\n\tecc_succeeded:%d ecc_corrected:%d ecc_failed:%d\n",
	    pstats.page_num, pstats.page_read, pstats.page_written,
	    pstats.page_raw_read, pstats.page_raw_written,
	    pstats.ecc_succeded, pstats.ecc_corrected, pstats.ecc_failed);
	return (0);
}