/*-
 * 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.
 */

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

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

#include <dev/nand/nandsim.h>

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

#include "nandsim_cfgparse.h"

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

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

#define MSG_MANDATORYKEYMISSING "mandatory key \"%s\" value belonging to " \
    "section \"%s\" is missing!\n"

#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 STRBUFSIZ 2000

/* Macros extracts type and type size */
#define TYPE(x) ((x) & 0xf8)
#define SIZE(x) (((x) & 0x07))

/* Erase/Prog/Read time max and min values */
#define DELAYTIME_MIN	10000
#define DELAYTIME_MAX	10000000

/* Structure holding configuration for controller. */
static struct sim_ctrl ctrl_conf;
/* Structure holding configuration for chip. */
static struct sim_chip chip_conf;

static struct nandsim_key nandsim_ctrl_keys[] = {
	{"num_cs", 1, VALUE_UINT | SIZE_8, (void *)&ctrl_conf.num_cs, 0},
	{"ctrl_num", 1, VALUE_UINT | SIZE_8, (void *)&ctrl_conf.num, 0},

	{"ecc_layout", 1, VALUE_UINTARRAY | SIZE_16,
	    (void *)&ctrl_conf.ecc_layout, MAX_ECC_BYTES},

	{"filename", 0, VALUE_STRING,
	    (void *)&ctrl_conf.filename, FILENAME_SIZE},

	{"ecc", 0, VALUE_BOOL, (void *)&ctrl_conf.ecc, 0},
	{NULL, 0, 0, NULL, 0},
};

static struct nandsim_key nandsim_chip_keys[] = {
	{"chip_cs", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.num, 0},
	{"chip_ctrl", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.ctrl_num,
	    0},
	{"device_id", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.device_id,
	    0},
	{"manufacturer_id", 1, VALUE_UINT | SIZE_8,
	    (void *)&chip_conf.manufact_id, 0},
	{"model", 0, VALUE_STRING, (void *)&chip_conf.device_model,
	    DEV_MODEL_STR_SIZE},
	{"manufacturer", 0, VALUE_STRING, (void *)&chip_conf.manufacturer,
	    MAN_STR_SIZE},
	{"page_size", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.page_size,
	    0},
	{"oob_size", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.oob_size,
	    0},
	{"pages_per_block", 1, VALUE_UINT | SIZE_32,
	    (void *)&chip_conf.pgs_per_blk, 0},
	{"blocks_per_lun", 1, VALUE_UINT | SIZE_32,
	    (void *)&chip_conf.blks_per_lun, 0},
	{"luns", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.luns, 0},
	{"column_addr_cycle", 1,VALUE_UINT | SIZE_8,
	    (void *)&chip_conf.col_addr_cycles, 0},
	{"row_addr_cycle", 1, VALUE_UINT | SIZE_8,
	    (void *)&chip_conf.row_addr_cycles, 0},
	{"program_time", 0, VALUE_UINT | SIZE_32,
	    (void *)&chip_conf.prog_time, 0},
	{"erase_time", 0, VALUE_UINT | SIZE_32,
	    (void *)&chip_conf.erase_time, 0},
	{"read_time", 0, VALUE_UINT | SIZE_32,
	    (void *)&chip_conf.read_time, 0},
	{"width", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.width, 0},
	{"wear_out", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.wear_level,
	    0},
	{"bad_block_map", 0, VALUE_UINTARRAY | SIZE_32,
	    (void *)&chip_conf.bad_block_map, MAX_BAD_BLOCKS},
	{NULL, 0, 0, NULL, 0},
};

static struct nandsim_section sections[] = {
	{"ctrl", (struct nandsim_key *)&nandsim_ctrl_keys},
	{"chip", (struct nandsim_key *)&nandsim_chip_keys},
	{NULL, NULL},
};

static uint8_t logoutputtoint(char *, int *);
static uint8_t validate_chips(struct sim_chip *, int, struct sim_ctrl *, int);
static uint8_t validate_ctrls(struct sim_ctrl *, int);
static int configure_sim(const char *, struct rcfile *);
static int create_ctrls(struct rcfile *, struct sim_ctrl **, int *);
static int create_chips(struct rcfile *, struct sim_chip **, int *);
static void destroy_ctrls(struct sim_ctrl *);
static void destroy_chips(struct sim_chip *);
static int validate_section_config(struct rcfile *, const char *, int);

int
convert_argint(char *arg, int *value)
{

	if (arg == NULL || value == NULL)
		return (EINVAL);

	errno = 0;
	*value = (int)strtol(arg, NULL, 0);
	if (*value == 0 && errno != 0) {
		error("Cannot convert to number argument \'%s\'", arg);
		return (EINVAL);
	}
	return (0);
}

int
convert_arguint(char *arg, unsigned int *value)
{

	if (arg == NULL || value == NULL)
		return (EINVAL);

	errno = 0;
	*value = (unsigned int)strtol(arg, NULL, 0);
	if (*value == 0 && errno != 0) {
		error("Cannot convert to number argument \'%s\'", arg);
		return (EINVAL);
	}
	return (0);
}

/* Parse given ',' separated list of bytes into buffer. */
int
parse_intarray(char *array, int **buffer)
{
	char *tmp, *tmpstr, *origstr;
	unsigned int currbufp = 0, i;
	unsigned int count = 0, from  = 0, to = 0;

	/* Remove square braces */
	if (array[0] == '[')
		array ++;
	if (array[strlen(array)-1] == ']')
		array[strlen(array)-1] = ',';

	from = strlen(array);
	origstr = (char *)malloc(sizeof(char) * from);
	strcpy(origstr, array);

	tmpstr = (char *)strtok(array, ",");
	/* First loop checks for how big int array we need to allocate */
	while (tmpstr != NULL) {
		errno = 0;
		if ((tmp = strchr(tmpstr, '-')) != NULL) {
			*tmp = ' ';
			if (convert_arguint(tmpstr, &from) ||
			    convert_arguint(tmp, &to)) {
				free(origstr);
				return (EINVAL);
			}

			count += to - from + 1;
		} else {
			if (convert_arguint(tmpstr, &from)) {
				free(origstr);
				return (EINVAL);
			}
			count++;
		}
		tmpstr = (char *)strtok(NULL, ",");
	}

	if (count == 0)
		goto out;

	/* Allocate buffer of ints */
	tmpstr = (char *)strtok(origstr, ",");
	*buffer = malloc(count * sizeof(int));

	/* Second loop is just inserting converted values into int array */
	while (tmpstr != NULL) {
		errno = 0;
		if ((tmp = strchr(tmpstr, '-')) != NULL) {
			*tmp = ' ';
			from = strtol(tmpstr, NULL, 0);
			to = strtol(tmp, NULL, 0);
			tmpstr = strtok(NULL, ",");
			for (i = from; i <= to; i ++)
				(*buffer)[currbufp++] = i;
			continue;
		}
		errno = 0;
		from = (int)strtol(tmpstr, NULL, 0);
		(*buffer)[currbufp++] = from;
		tmpstr = (char *)strtok(NULL, ",");
	}
out:
	free(origstr);
	return (count);
}

/* Convert logoutput strings literals into appropriate ints. */
static uint8_t
logoutputtoint(char *logoutput, int *output)
{
	int out;

	if (strcmp(logoutput, "file") == 0)
		out = NANDSIM_OUTPUT_FILE;

	else if (strcmp(logoutput, "console") == 0)
		out = NANDSIM_OUTPUT_CONSOLE;

	else if (strcmp(logoutput, "ram") == 0)
		out = NANDSIM_OUTPUT_RAM;

	else if (strcmp(logoutput, "none") == 0)
		out = NANDSIM_OUTPUT_NONE;
	else
		out = -1;

	*output = out;

	if (out == -1)
		return (EINVAL);
	else
		return (0);
}

static int
configure_sim(const char *devfname, struct rcfile *f)
{
	struct sim_param sim_conf;
	char buf[255];
	int err, tmpv, fd;

	err = rc_getint(f, "sim", 0, "log_level", &tmpv);

	if (tmpv < 0 || tmpv > 255 || err) {
		error("Bad log level specified (%d)\n", tmpv);
		return (ENOTSUP);
	} else
		sim_conf.log_level = tmpv;

	rc_getstring(f, "sim", 0, "log_output", 255, (char *)&buf);

	tmpv = -1;
	err = logoutputtoint((char *)&buf, &tmpv);
	if (err) {
		error("Log output specified in config file does not seem to "
		    "be valid (%s)!", (char *)&buf);
		return (ENOTSUP);
	}

	sim_conf.log_output = tmpv;

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

	err = ioctl(fd, NANDSIM_SIM_PARAM, &sim_conf);
	if (err) {
		error("simulator parameters could not be modified: %s",
		    strerror(errno));
		close(fd);
		return (ENXIO);
	}

	close(fd);
	return (EX_OK);
}

static int
create_ctrls(struct rcfile *f, struct sim_ctrl **ctrls, int *cnt)
{
	int count, i;
	struct sim_ctrl *ctrlsptr;

	count = rc_getsectionscount(f, "ctrl");
	if (count > MAX_SIM_DEV) {
		error("Too many CTRL sections specified(%d)", count);
		return (ENOTSUP);
	} else if (count == 0) {
		error("No ctrl sections specified");
		return (ENOENT);
	}

	ctrlsptr = (struct sim_ctrl *)malloc(sizeof(struct sim_ctrl) * count);
	if (ctrlsptr == NULL) {
		error("Could not allocate memory for ctrl configuration");
		return (ENOMEM);
	}

	for (i = 0; i < count; i++) {
		bzero((void *)&ctrl_conf, sizeof(ctrl_conf));

		/*
		 * ECC layout have to end up with 0xffff, so
		 * we're filling buffer with 0xff. If ecc_layout is
		 * defined in config file, values will be overridden.
		 */
		memset((void *)&ctrl_conf.ecc_layout, 0xff,
		    sizeof(ctrl_conf.ecc_layout));

		if (validate_section_config(f, "ctrl", i) != 0) {
			free(ctrlsptr);
			return (EINVAL);
		}

		if (parse_section(f, "ctrl", i) != 0) {
			free(ctrlsptr);
			return (EINVAL);
		}

		memcpy(&ctrlsptr[i], &ctrl_conf, sizeof(ctrl_conf));
		/* Try to create ctrl with config parsed */
		debug("NUM=%d\nNUM_CS=%d\nECC=%d\nFILENAME=%s\nECC_LAYOUT[0]"
		    "=%d\nECC_LAYOUT[1]=%d\n\n",
		    ctrlsptr[i].num, ctrlsptr[i].num_cs, ctrlsptr[i].ecc,
		    ctrlsptr[i].filename, ctrlsptr[i].ecc_layout[0],
		    ctrlsptr[i].ecc_layout[1]);
	}
	*cnt = count;
	*ctrls = ctrlsptr;
	return (0);
}

static void
destroy_ctrls(struct sim_ctrl *ctrls)
{

	free(ctrls);
}

static int
create_chips(struct rcfile *f, struct sim_chip **chips, int *cnt)
{
	struct sim_chip *chipsptr;
	int count, i;

	count = rc_getsectionscount(f, "chip");
	if (count > (MAX_CTRL_CS * MAX_SIM_DEV)) {
		error("Too many chip sections specified(%d)", count);
		return (ENOTSUP);
	} else if (count == 0) {
		error("No chip sections specified");
		return (ENOENT);
	}

	chipsptr = (struct sim_chip *)malloc(sizeof(struct sim_chip) * count);
	if (chipsptr == NULL) {
		error("Could not allocate memory for chip configuration");
		return (ENOMEM);
	}

	for (i = 0; i < count; i++) {
		bzero((void *)&chip_conf, sizeof(chip_conf));

		/*
		 * Bad block map have to end up with 0xffff, so
		 * we're filling array with 0xff. If bad block map is
		 * defined in config file, values will be overridden.
		 */
		memset((void *)&chip_conf.bad_block_map, 0xff,
		    sizeof(chip_conf.bad_block_map));

		if (validate_section_config(f, "chip", i) != 0) {
			free(chipsptr);
			return (EINVAL);
		}

		if (parse_section(f, "chip", i) != 0) {
			free(chipsptr);
			return (EINVAL);
		}

		memcpy(&chipsptr[i], &chip_conf, sizeof(chip_conf));

		/* Try to create chip with config parsed */
		debug("CHIP:\nNUM=%d\nCTRL_NUM=%d\nDEVID=%d\nMANID=%d\n"
		    "PAGE_SZ=%d\nOOBSZ=%d\nREAD_T=%d\nDEVMODEL=%s\n"
		    "MAN=%s\nCOLADDRCYCLES=%d\nROWADDRCYCLES=%d\nCHWIDTH=%d\n"
		    "PGS/BLK=%d\nBLK/LUN=%d\nLUNS=%d\nERR_RATIO=%d\n"
		    "WEARLEVEL=%d\nISWP=%d\n\n\n\n",
		    chipsptr[i].num, chipsptr[i].ctrl_num,
		    chipsptr[i].device_id, chipsptr[i].manufact_id,
		    chipsptr[i].page_size, chipsptr[i].oob_size,
		    chipsptr[i].read_time, chipsptr[i].device_model,
		    chipsptr[i].manufacturer, chipsptr[i].col_addr_cycles,
		    chipsptr[i].row_addr_cycles, chipsptr[i].width,
		    chipsptr[i].pgs_per_blk, chipsptr[i].blks_per_lun,
		    chipsptr[i].luns, chipsptr[i].error_ratio,
		    chipsptr[i].wear_level, chipsptr[i].is_wp);
	}
	*cnt = count;
	*chips = chipsptr;
	return (0);
}

static void
destroy_chips(struct sim_chip *chips)
{

	free(chips);
}

int
parse_config(char *cfgfname, const char *devfname)
{
	int err = 0, fd;
	unsigned int chipsectionscnt, ctrlsectionscnt, i;
	struct rcfile *f;
	struct sim_chip *chips;
	struct sim_ctrl *ctrls;

	err = rc_open(cfgfname, "r", &f);
	if (err) {
		error("could not open configuration file (%s)", cfgfname);
		return (EX_NOINPUT);
	}

	/* First, try to configure simulator itself. */
	if (configure_sim(devfname, f) != EX_OK) {
		rc_close(f);
		return (EINVAL);
	}

	debug("SIM CONFIGURED!\n");
	/* Then create controllers' configs */
	if (create_ctrls(f, &ctrls, &ctrlsectionscnt) != 0) {
		rc_close(f);
		return (ENXIO);
	}
	debug("CTRLS CONFIG READ!\n");

	/* Then create chips' configs */
	if (create_chips(f, &chips, &chipsectionscnt) != 0) {
		destroy_ctrls(ctrls);
		rc_close(f);
		return (ENXIO);
	}
	debug("CHIPS CONFIG READ!\n");

	if (validate_ctrls(ctrls, ctrlsectionscnt) != 0) {
		destroy_ctrls(ctrls);
		destroy_chips(chips);
		rc_close(f);
		return (EX_SOFTWARE);
	}
	if (validate_chips(chips, chipsectionscnt, ctrls,
	    ctrlsectionscnt) != 0) {
		destroy_ctrls(ctrls);
		destroy_chips(chips);
		rc_close(f);
		return (EX_SOFTWARE);
	}

	/* Open device */
	fd = open(devfname, O_RDWR);
	if (fd == -1) {
		error("could not open simulator device file (%s)!",
		    devfname);
		rc_close(f);
		destroy_chips(chips);
		destroy_ctrls(ctrls);
		return (EX_OSFILE);
	}

	debug("SIM CONFIG STARTED!\n");

	/* At this stage, both ctrls' and chips' configs should be valid */
	for (i = 0; i < ctrlsectionscnt; i++) {
		err = ioctl(fd, NANDSIM_CREATE_CTRL, &ctrls[i]);
		if (err) {
			if (err == EEXIST)
				error("Controller#%d already created\n",
				    ctrls[i].num);
			else if (err == EINVAL)
				error("Incorrect controler number (%d)\n",
				    ctrls[i].num);
			else
				error("Could not created controller#%d\n",
				    ctrls[i].num);
			/* Errors during controller creation stops parsing */
			close(fd);
			rc_close(f);
			destroy_ctrls(ctrls);
			destroy_chips(chips);
			return (ENXIO);
		}
		debug("CTRL#%d CONFIG STARTED!\n", i);
	}

	for (i = 0; i < chipsectionscnt; i++) {
		err = ioctl(fd, NANDSIM_CREATE_CHIP, &chips[i]);
		if (err) {
			if (err == EEXIST)
				error("Chip#%d for controller#%d already "
				    "created\n", chips[i].num,
				    chips[i].ctrl_num);
			else if (err == EINVAL)
				error("Incorrect chip number (%d:%d)\n",
				    chips[i].num, chips[i].ctrl_num);
			else
				error("Could not create chip (%d:%d)\n",
				    chips[i].num, chips[i].ctrl_num);
			error("Could not start chip#%d\n", i);
			destroy_chips(chips);
			destroy_ctrls(ctrls);
			close(fd);
			rc_close(f);
			return (ENXIO);
		}
	}
	debug("CHIPS CONFIG STARTED!\n");

	close(fd);
	rc_close(f);
	destroy_chips(chips);
	destroy_ctrls(ctrls);
	return (0);
}

/*
 * Function tries to get appropriate value for given key, convert it to
 * array of ints (of given size), and perform all the necessary checks and
 * conversions.
 */
static int
get_argument_intarray(const char *sect_name, int sectno,
    struct nandsim_key *key, struct rcfile *f)
{
	char strbuf[STRBUFSIZ];
	int *intbuf;
	int getres;
	uint32_t cnt, i = 0;

	getres = rc_getstring(f, sect_name, sectno, key->keyname, STRBUFSIZ,
	    (char *)&strbuf);

	if (getres != 0) {
		if (key->mandatory != 0) {
			error(MSG_MANDATORYKEYMISSING, key->keyname,
			    sect_name);
			return (EINVAL);
		} else
			/* Non-mandatory key, not present -- skip */
			return (0);
	}
	cnt = parse_intarray((char *)&strbuf, &intbuf);
	cnt = (cnt <= key->maxlength) ? cnt : key->maxlength;

	for (i = 0; i < cnt; i++) {
		if (SIZE(key->valuetype) == SIZE_8)
			*((uint8_t *)(key->field) + i) =
			    (uint8_t)intbuf[i];
		else if (SIZE(key->valuetype) == SIZE_16)
			*((uint16_t *)(key->field) + i) =
			    (uint16_t)intbuf[i];
		else
			*((uint32_t *)(key->field) + i) =
			    (uint32_t)intbuf[i];
	}
	free(intbuf);
	return (0);
}

/*
 *  Function tries to get appropriate value for given key, convert it to
 *  int of certain length.
 */
static int
get_argument_int(const char *sect_name, int sectno, struct nandsim_key *key,
    struct rcfile *f)
{
	int getres;
	uint32_t val;

	getres = rc_getint(f, sect_name, sectno, key->keyname, &val);
	if (getres != 0) {

		if (key->mandatory != 0) {
			error(MSG_MANDATORYKEYMISSING, key->keyname,
			    sect_name);

			return (EINVAL);
		} else
			/* Non-mandatory key, not present -- skip */
			return (0);
	}
	if (SIZE(key->valuetype) == SIZE_8)
		*(uint8_t *)(key->field) = (uint8_t)val;
	else if (SIZE(key->valuetype) == SIZE_16)
		*(uint16_t *)(key->field) = (uint16_t)val;
	else
		*(uint32_t *)(key->field) = (uint32_t)val;
	return (0);
}

/* Function tries to get string value for given key */
static int
get_argument_string(const char *sect_name, int sectno,
    struct nandsim_key *key, struct rcfile *f)
{
	char strbuf[STRBUFSIZ];
	int getres;

	getres = rc_getstring(f, sect_name, sectno, key->keyname, STRBUFSIZ,
	    strbuf);

	if (getres != 0) {
		if (key->mandatory != 0) {
			error(MSG_MANDATORYKEYMISSING, key->keyname,
			    sect_name);
			return (1);
		} else
			/* Non-mandatory key, not present -- skip */
			return (0);
	}
	strncpy(key->field, (char *)&strbuf, (size_t)(key->maxlength - 1));
	return (0);
}

/* Function tries to get on/off value for given key */
static int
get_argument_bool(const char *sect_name, int sectno, struct nandsim_key *key,
    struct rcfile *f)
{
	int getres, val;

	getres = rc_getbool(f, sect_name, sectno, key->keyname, &val);
	if (getres != 0) {
		if (key->mandatory != 0) {
			error(MSG_MANDATORYKEYMISSING, key->keyname,
			    sect_name);
			return (1);
		} else
			/* Non-mandatory key, not present -- skip */
			return (0);
	}
	*(uint8_t *)key->field = (uint8_t)val;
	return (0);
}

int
parse_section(struct rcfile *f, const char *sect_name, int sectno)
{
	struct nandsim_key *key;
	struct nandsim_section *sect = (struct nandsim_section *)&sections;
	int getres = 0;

	while (1) {
		if (sect == NULL)
			return (EINVAL);

		if (strcmp(sect->name, sect_name) == 0)
			break;
		else
			sect++;
	}
	key = sect->keys;
	do {
		debug("->Section: %s, Key: %s, type: %d, size: %d",
		    sect_name, key->keyname, TYPE(key->valuetype),
		    SIZE(key->valuetype)/2);

		switch (TYPE(key->valuetype)) {
		case VALUE_UINT:
			/* Single int value */
			getres = get_argument_int(sect_name, sectno, key, f);

			if (getres != 0)
				return (getres);

			break;
		case VALUE_UINTARRAY:
			/* Array of ints */
			getres = get_argument_intarray(sect_name,
			    sectno, key, f);

			if (getres != 0)
				return (getres);

			break;
		case VALUE_STRING:
			/* Array of chars */
			getres = get_argument_string(sect_name, sectno, key,
			    f);

			if (getres != 0)
				return (getres);

			break;
		case VALUE_BOOL:
			/* Boolean value (true/false/on/off/yes/no) */
			getres = get_argument_bool(sect_name, sectno, key,
			    f);

			if (getres != 0)
				return (getres);

			break;
		}
	} while ((++key)->keyname != NULL);

	return (0);
}

static uint8_t
validate_chips(struct sim_chip *chips, int chipcnt,
    struct sim_ctrl *ctrls, int ctrlcnt)
{
	int cchipcnt, i, width, j, id, max;

	cchipcnt = chipcnt;
	for (chipcnt -= 1; chipcnt >= 0; chipcnt--) {
		if (chips[chipcnt].num >= MAX_CTRL_CS) {
			error("chip no. too high (%d)!!\n",
			    chips[chipcnt].num);
			return (EINVAL);
		}

		if (chips[chipcnt].ctrl_num >= MAX_SIM_DEV) {
			error("controller no. too high (%d)!!\n",
			    chips[chipcnt].ctrl_num);
			return (EINVAL);
		}

		if (chips[chipcnt].width != 8 &&
		    chips[chipcnt].width != 16) {
			error("invalid width:%d for chip#%d",
			    chips[chipcnt].width, chips[chipcnt].num);
			return (EINVAL);
		}

		/* Check if page size is > 512 and if its power of 2 */
		if (chips[chipcnt].page_size < 512 ||
		    (chips[chipcnt].page_size &
		    (chips[chipcnt].page_size - 1)) != 0) {
			error("invalid page size:%d for chip#%d at ctrl#%d!!"
			    "\n", chips[chipcnt].page_size,
			    chips[chipcnt].num,
			    chips[chipcnt].ctrl_num);
			return (EINVAL);
		}

		/* Check if controller no. ctrl_num is configured */
		for (i = 0, id = -1; i < ctrlcnt && id == -1; i++)
			if (ctrls[i].num == chips[chipcnt].ctrl_num)
				id = i;

		if (i == ctrlcnt && id == -1) {
			error("Missing configuration for controller %d"
			    " (at least one chip is connected to it)",
			    chips[chipcnt].ctrl_num);
			return (EINVAL);
		} else {
			/*
			 * Controller is configured -> check oob_size
			 * validity
			 */
			i = 0;
			max = ctrls[id].ecc_layout[0];
			while (i < MAX_ECC_BYTES &&
			    ctrls[id].ecc_layout[i] != 0xffff) {

				if (ctrls[id].ecc_layout[i] > max)
					max = ctrls[id].ecc_layout[i];
				i++;
			}

			if (chips[chipcnt].oob_size < (unsigned)i) {
				error("OOB size for chip#%d at ctrl#%d is "
				    "smaller than ecc layout length!",
				    chips[chipcnt].num,
				    chips[chipcnt].ctrl_num);
				exit(EINVAL);
			}

			if (chips[chipcnt].oob_size < (unsigned)max) {
				error("OOB size for chip#%d at ctrl#%d is "
				    "smaller than maximal ecc position in "
				    "defined layout!", chips[chipcnt].num,
				    chips[chipcnt].ctrl_num);
				exit(EINVAL);
			}


		}

		if ((chips[chipcnt].erase_time < DELAYTIME_MIN ||
		    chips[chipcnt].erase_time > DELAYTIME_MAX) &&
		    chips[chipcnt].erase_time != 0) {
			error("Invalid erase time value for chip#%d at "
			    "ctrl#%d",
			    chips[chipcnt].num,
			    chips[chipcnt].ctrl_num);
			return (EINVAL);
		}

		if ((chips[chipcnt].prog_time < DELAYTIME_MIN ||
		    chips[chipcnt].prog_time > DELAYTIME_MAX) &&
		    chips[chipcnt].prog_time != 0) {
			error("Invalid prog time value for chip#%d at "
			    "ctr#%d!",
			    chips[chipcnt].num,
			    chips[chipcnt].ctrl_num);
			return (EINVAL);
		}

		if ((chips[chipcnt].read_time < DELAYTIME_MIN ||
		    chips[chipcnt].read_time > DELAYTIME_MAX) &&
		    chips[chipcnt].read_time != 0) {
			error("Invalid read time value for chip#%d at "
			    "ctrl#%d!",
			    chips[chipcnt].num,
			    chips[chipcnt].ctrl_num);
			return (EINVAL);
		}
	}
	/* Check if chips attached to the same controller, have same width */
	for (i = 0; i < ctrlcnt; i++) {
		width = -1;
		for (j = 0; j < cchipcnt; j++) {
			if (chips[j].ctrl_num == i) {
				if (width == -1) {
					width = chips[j].width;
				} else {
					if (width != chips[j].width) {
						error("Chips attached to "
						    "ctrl#%d have different "
						    "widths!\n", i);
						return (EINVAL);
					}
				}
			}
		}
	}

	return (0);
}

static uint8_t
validate_ctrls(struct sim_ctrl *ctrl, int ctrlcnt)
{
	for (ctrlcnt -= 1; ctrlcnt >= 0; ctrlcnt--) {
		if (ctrl[ctrlcnt].num > MAX_SIM_DEV) {
			error("Controller no. too high (%d)!!\n",
			    ctrl[ctrlcnt].num);
			return (EINVAL);
		}
		if (ctrl[ctrlcnt].num_cs > MAX_CTRL_CS) {
			error("Too many CS (%d)!!\n", ctrl[ctrlcnt].num_cs);
			return (EINVAL);
		}
		if (ctrl[ctrlcnt].ecc != 0 && ctrl[ctrlcnt].ecc != 1) {
			error("ECC is set to neither 0 nor 1 !\n");
			return (EINVAL);
		}
	}

	return (0);
}

static int validate_section_config(struct rcfile *f, const char *sect_name,
    int sectno)
{
	struct nandsim_key *key;
	struct nandsim_section *sect;
	char **keys_tbl;
	int i, match;

	for (match = 0, sect = (struct nandsim_section *)&sections;
	    sect != NULL; sect++) {
		if (strcmp(sect->name, sect_name) == 0) {
			match = 1;
			break;
		}
	}

	if (match == 0)
		return (EINVAL);

	keys_tbl = rc_getkeys(f, sect_name, sectno);
	if (keys_tbl == NULL)
		return (ENOMEM);

	for (i = 0; keys_tbl[i] != NULL; i++) {
		key = sect->keys;
		match = 0;
		do {
			if (strcmp(keys_tbl[i], key->keyname) == 0) {
				match = 1;
				break;
			}
		} while ((++key)->keyname != NULL);

		if (match == 0) {
			error("Invalid key in config file: %s\n", keys_tbl[i]);
			free(keys_tbl);
			return (EINVAL);
		}
	}

	free(keys_tbl);
	return (0);
}