/*-
 * 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 THE 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/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/kthread.h>
#include <sys/unistd.h>

#include <dev/nand/nand.h>
#include <dev/nand/nandsim_chip.h>
#include <dev/nand/nandsim_log.h>
#include <dev/nand/nandsim_swap.h>

MALLOC_DEFINE(M_NANDSIM, "NANDsim", "NANDsim dynamic data");

#define NANDSIM_CHIP_LOCK(chip)		mtx_lock(&(chip)->ns_lock)
#define	NANDSIM_CHIP_UNLOCK(chip)	mtx_unlock(&(chip)->ns_lock)

static nandsim_evh_t erase_evh;
static nandsim_evh_t idle_evh;
static nandsim_evh_t poweron_evh;
static nandsim_evh_t reset_evh;
static nandsim_evh_t read_evh;
static nandsim_evh_t readid_evh;
static nandsim_evh_t readparam_evh;
static nandsim_evh_t write_evh;

static void nandsim_loop(void *);
static void nandsim_undefined(struct nandsim_chip *, uint8_t);
static void nandsim_bad_address(struct nandsim_chip *, uint8_t *);
static void nandsim_ignore_address(struct nandsim_chip *, uint8_t);
static void nandsim_sm_error(struct nandsim_chip *);
static void nandsim_start_handler(struct nandsim_chip *, nandsim_evh_t);

static void nandsim_callout_eh(void *);
static int  nandsim_delay(struct nandsim_chip *, int);

static int  nandsim_bbm_init(struct nandsim_chip *, uint32_t, uint32_t *);
static int  nandsim_blk_state_init(struct nandsim_chip *, uint32_t, uint32_t);
static void nandsim_blk_state_destroy(struct nandsim_chip *);
static int  nandchip_is_block_valid(struct nandsim_chip *, int);

static void nandchip_set_status(struct nandsim_chip *, uint8_t);
static void nandchip_clear_status(struct nandsim_chip *, uint8_t);

struct proc *nandsim_proc;

struct nandsim_chip *
nandsim_chip_init(struct nandsim_softc* sc, uint8_t chip_num,
    struct sim_chip *sim_chip)
{
	struct nandsim_chip *chip;
	struct onfi_params *chip_param;
	char swapfile[20];
	uint32_t size;
	int error;

	chip = malloc(sizeof(*chip), M_NANDSIM, M_WAITOK | M_ZERO);
	if (!chip)
		return (NULL);

	mtx_init(&chip->ns_lock, "nandsim lock", NULL, MTX_DEF);
	callout_init(&chip->ns_callout, CALLOUT_MPSAFE);
	STAILQ_INIT(&chip->nandsim_events);

	chip->chip_num = chip_num;
	chip->ctrl_num = sim_chip->ctrl_num;
	chip->sc = sc;

	if (!sim_chip->is_wp)
		nandchip_set_status(chip, NAND_STATUS_WP);

	chip_param = &chip->params;

	chip->id.dev_id = sim_chip->device_id;
	chip->id.man_id = sim_chip->manufact_id;

	chip->error_ratio = sim_chip->error_ratio;
	chip->wear_level = sim_chip->wear_level;
	chip->prog_delay = sim_chip->prog_time;
	chip->erase_delay = sim_chip->erase_time;
	chip->read_delay = sim_chip->read_time;

	chip_param->t_prog = sim_chip->prog_time;
	chip_param->t_bers = sim_chip->erase_time;
	chip_param->t_r = sim_chip->read_time;
	bcopy("onfi", &chip_param->signature, 4);

	chip_param->manufacturer_id = sim_chip->manufact_id;
	strncpy(chip_param->manufacturer_name, sim_chip->manufacturer, 12);
	chip_param->manufacturer_name[11] = 0;
	strncpy(chip_param->device_model, sim_chip->device_model, 20);
	chip_param->device_model[19] = 0;

	chip_param->bytes_per_page = sim_chip->page_size;
	chip_param->spare_bytes_per_page = sim_chip->oob_size;
	chip_param->pages_per_block = sim_chip->pgs_per_blk;
	chip_param->blocks_per_lun = sim_chip->blks_per_lun;
	chip_param->luns = sim_chip->luns;

	init_chip_geom(&chip->cg, chip_param->luns, chip_param->blocks_per_lun,
	    chip_param->pages_per_block, chip_param->bytes_per_page,
	    chip_param->spare_bytes_per_page);

	chip_param->address_cycles = sim_chip->row_addr_cycles |
	    (sim_chip->col_addr_cycles << 4);
	chip_param->features = sim_chip->features;
	if (sim_chip->width == 16)
		chip_param->features |= ONFI_FEAT_16BIT;

	size = chip_param->blocks_per_lun * chip_param->luns;

	error = nandsim_blk_state_init(chip, size, sim_chip->wear_level);
	if (error) {
		mtx_destroy(&chip->ns_lock);
		free(chip, M_NANDSIM);
		return (NULL);
	}

	error = nandsim_bbm_init(chip, size, sim_chip->bad_block_map);
	if (error) {
		mtx_destroy(&chip->ns_lock);
		nandsim_blk_state_destroy(chip);
		free(chip, M_NANDSIM);
		return (NULL);
	}

	nandsim_start_handler(chip, poweron_evh);

	nand_debug(NDBG_SIM,"Create thread for chip%d [%8p]", chip->chip_num,
	    chip);
	/* Create chip thread */
	error = kproc_kthread_add(nandsim_loop, chip, &nandsim_proc,
	    &chip->nandsim_td, RFSTOPPED | RFHIGHPID,
	    0, "nandsim", "chip");
	if (error) {
		mtx_destroy(&chip->ns_lock);
		nandsim_blk_state_destroy(chip);
		free(chip, M_NANDSIM);
		return (NULL);
	}

	thread_lock(chip->nandsim_td);
	sched_class(chip->nandsim_td, PRI_REALTIME);
	sched_add(chip->nandsim_td, SRQ_BORING);
	thread_unlock(chip->nandsim_td);

	size = (chip_param->bytes_per_page +
	    chip_param->spare_bytes_per_page) *
	    chip_param->pages_per_block;

	sprintf(swapfile, "chip%d%d.swp", chip->ctrl_num, chip->chip_num);
	chip->swap = nandsim_swap_init(swapfile, chip_param->blocks_per_lun *
	    chip_param->luns, size);
	if (!chip->swap)
		nandsim_chip_destroy(chip);

	/* Wait for new thread to enter main loop */
	tsleep(chip->nandsim_td, PWAIT, "ns_chip", 1 * hz);

	return (chip);
}

static int
nandsim_blk_state_init(struct nandsim_chip *chip, uint32_t size,
    uint32_t wear_lev)
{
	int i;

	if (!chip || size == 0)
		return (-1);

	chip->blk_state = malloc(size * sizeof(struct nandsim_block_state),
	    M_NANDSIM, M_WAITOK | M_ZERO);
	if (!chip->blk_state) {
		return (-1);
	}

	for (i = 0; i < size; i++) {
		if (wear_lev)
			chip->blk_state[i].wear_lev = wear_lev;
		else
			chip->blk_state[i].wear_lev = -1;
	}

	return (0);
}

static void
nandsim_blk_state_destroy(struct nandsim_chip *chip)
{

	if (chip && chip->blk_state)
		free(chip->blk_state, M_NANDSIM);
}

static int
nandsim_bbm_init(struct nandsim_chip *chip, uint32_t size,
    uint32_t *sim_bbm)
{
	uint32_t index;
	int i;

	if ((chip == NULL) || (size == 0))
		return (-1);

	if (chip->blk_state == NULL)
		return (-1);

	if (sim_bbm == NULL)
		return (0);

	for (i = 0; i < MAX_BAD_BLOCKS; i++) {
		index = sim_bbm[i];

		if (index == 0xffffffff)
			break;
		else if (index > size)
			return (-1);
		else
			chip->blk_state[index].is_bad = 1;
	}

	return (0);
}

void
nandsim_chip_destroy(struct nandsim_chip *chip)
{
	struct nandsim_ev *ev;

	ev = create_event(chip, NANDSIM_EV_EXIT, 0);
	if (ev)
		send_event(ev);
}

void
nandsim_chip_freeze(struct nandsim_chip *chip)
{

	chip->flags |= NANDSIM_CHIP_FROZEN;
}

static void
nandsim_loop(void *arg)
{
	struct nandsim_chip *chip = (struct nandsim_chip *)arg;
	struct nandsim_ev *ev;

	nand_debug(NDBG_SIM,"Start main loop for chip%d [%8p]", chip->chip_num,
	    chip);
	for(;;) {
		NANDSIM_CHIP_LOCK(chip);
		if (!(chip->flags & NANDSIM_CHIP_ACTIVE)) {
			chip->flags |= NANDSIM_CHIP_ACTIVE;
			wakeup(chip->nandsim_td);
		}

		if (STAILQ_EMPTY(&chip->nandsim_events)) {
			nand_debug(NDBG_SIM,"Chip%d [%8p] going sleep",
			    chip->chip_num, chip);
			msleep(chip, &chip->ns_lock, PRIBIO, "nandev", 0);
		}

		ev = STAILQ_FIRST(&chip->nandsim_events);
		STAILQ_REMOVE_HEAD(&chip->nandsim_events, links);
		NANDSIM_CHIP_UNLOCK(chip);
		if (ev->type == NANDSIM_EV_EXIT) {
			NANDSIM_CHIP_LOCK(chip);
			destroy_event(ev);
			wakeup(ev);
			while (!STAILQ_EMPTY(&chip->nandsim_events)) {
				ev = STAILQ_FIRST(&chip->nandsim_events);
				STAILQ_REMOVE_HEAD(&chip->nandsim_events,
				    links);
				destroy_event(ev);
				wakeup(ev);
			};
			NANDSIM_CHIP_UNLOCK(chip);
			nandsim_log(chip, NANDSIM_LOG_SM, "destroyed\n");
			mtx_destroy(&chip->ns_lock);
			nandsim_blk_state_destroy(chip);
			nandsim_swap_destroy(chip->swap);
			free(chip, M_NANDSIM);
			nandsim_proc = NULL;

			kthread_exit();
		}

		if (!(chip->flags & NANDSIM_CHIP_FROZEN)) {
			nand_debug(NDBG_SIM,"Chip [%x] get event [%x]",
			    chip->chip_num, ev->type);
			chip->ev_handler(chip, ev->type, ev->data);
		}

		wakeup(ev);
		destroy_event(ev);
	}

}

struct nandsim_ev *
create_event(struct nandsim_chip *chip, uint8_t type, uint8_t data_size)
{
	struct nandsim_ev *ev;

	ev = malloc(sizeof(*ev), M_NANDSIM, M_NOWAIT | M_ZERO);
	if (!ev) {
		nand_debug(NDBG_SIM,"Cannot create event");
		return (NULL);
	}

	if (data_size > 0)
		ev->data = malloc(sizeof(*ev), M_NANDSIM, M_NOWAIT | M_ZERO);
	ev->type = type;
	ev->chip = chip;

	return (ev);
}

void
destroy_event(struct nandsim_ev *ev)
{

	if (ev->data)
		free(ev->data, M_NANDSIM);
	free(ev, M_NANDSIM);
}

int
send_event(struct nandsim_ev *ev)
{
	struct nandsim_chip *chip = ev->chip;

	if (!(chip->flags & NANDSIM_CHIP_FROZEN)) {
		nand_debug(NDBG_SIM,"Chip%d [%p] send event %x",
		    chip->chip_num, chip, ev->type);

		NANDSIM_CHIP_LOCK(chip);
		STAILQ_INSERT_TAIL(&chip->nandsim_events, ev, links);
		NANDSIM_CHIP_UNLOCK(chip);

		wakeup(chip);
		if ((ev->type != NANDSIM_EV_TIMEOUT) && chip->nandsim_td &&
		    (curthread != chip->nandsim_td))
			tsleep(ev, PWAIT, "ns_ev", 5 * hz);
	}

	return (0);
}

static void
nandsim_callout_eh(void *arg)
{
	struct nandsim_ev *ev = (struct nandsim_ev *)arg;

	send_event(ev);
}

static int
nandsim_delay(struct nandsim_chip *chip, int timeout)
{
	struct nandsim_ev *ev;
	struct timeval delay;
	int tm;

	nand_debug(NDBG_SIM,"Chip[%d] Set delay: %d", chip->chip_num, timeout);

	ev = create_event(chip, NANDSIM_EV_TIMEOUT, 0);
	if (!ev)
		return (-1);

	chip->sm_state = NANDSIM_STATE_TIMEOUT;
	tm = (timeout/10000) * (hz / 100);
	if (callout_reset(&chip->ns_callout, tm, nandsim_callout_eh, ev))
		return (-1);

	delay.tv_sec = chip->read_delay / 1000000;
	delay.tv_usec = chip->read_delay % 1000000;
	timevaladd(&chip->delay_tv, &delay);

	return (0);
}

static void
nandsim_start_handler(struct nandsim_chip *chip, nandsim_evh_t evh)
{
	struct nandsim_ev *ev;

	chip->ev_handler = evh;

	nand_debug(NDBG_SIM,"Start handler %p for chip%d [%p]", evh,
	    chip->chip_num, chip);
	ev = create_event(chip, NANDSIM_EV_START, 0);
	if (!ev)
		nandsim_sm_error(chip);

	send_event(ev);
}

static void
nandchip_set_data(struct nandsim_chip *chip, uint8_t *data, uint32_t len,
    uint32_t idx)
{

	nand_debug(NDBG_SIM,"Chip [%x] data %p [%x] at %x", chip->chip_num,
	    data, len, idx);
	chip->data.data_ptr = data;
	chip->data.size = len;
	chip->data.index = idx;
}

static int
nandchip_chip_space(struct nandsim_chip *chip, int32_t row, int32_t column,
    size_t size, uint8_t writing)
{
	struct block_space *blk_space;
	uint32_t lun, block, page, offset, block_size;
	int err;

	block_size = chip->cg.block_size +
	    (chip->cg.oob_size * chip->cg.pgs_per_blk);

	err = nand_row_to_blkpg(&chip->cg, row, &lun, &block, &page);
	if (err) {
		nand_debug(NDBG_SIM,"cannot get address\n");
		return (-1);
	}

	if (!nandchip_is_block_valid(chip, block)) {
		nandchip_set_data(chip, NULL, 0, 0);
		return (-1);
	}

	blk_space = get_bs(chip->swap, block, writing);
	if (!blk_space) {
		nandchip_set_data(chip, NULL, 0, 0);
		return (-1);
	}

	if (size > block_size)
		size = block_size;

	if (size == block_size) {
		offset = 0;
		column = 0;
	} else
		offset = page * (chip->cg.page_size + chip->cg.oob_size);

	nandchip_set_data(chip, &blk_space->blk_ptr[offset], size, column);

	return (0);
}

static int
nandchip_get_addr_byte(struct nandsim_chip *chip, void *data, uint32_t *value)
{
	int ncycles = 0;
	uint8_t byte;
	uint8_t *buffer;

	buffer = (uint8_t *)value;
	byte = *((uint8_t *)data);

	KASSERT((chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW ||
	    chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL),
	    ("unexpected state"));

	if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) {
		ncycles = chip->params.address_cycles & 0xf;
		buffer[chip->sm_addr_cycle++] = byte;
	} else if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL) {
		ncycles = (chip->params.address_cycles >> 4) & 0xf;
		buffer[chip->sm_addr_cycle++] = byte;
	}

	nand_debug(NDBG_SIM, "Chip [%x] read addr byte: %02x (%d of %d)\n",
	    chip->chip_num, byte, chip->sm_addr_cycle, ncycles);

	if (chip->sm_addr_cycle == ncycles) {
		chip->sm_addr_cycle = 0;
		return (0);
	}

	return (1);
}

static int
nandchip_is_block_valid(struct nandsim_chip *chip, int block_num)
{

	if (!chip || !chip->blk_state)
		return (0);

	if (chip->blk_state[block_num].wear_lev == 0 ||
	    chip->blk_state[block_num].is_bad)
		return (0);

	return (1);
}

static void
nandchip_set_status(struct nandsim_chip *chip, uint8_t flags)
{

	chip->chip_status |= flags;
}

static void
nandchip_clear_status(struct nandsim_chip *chip, uint8_t flags)
{

	chip->chip_status &= ~flags;
}

uint8_t
nandchip_get_status(struct nandsim_chip *chip)
{
	return (chip->chip_status);
}

void
nandsim_chip_timeout(struct nandsim_chip *chip)
{
	struct timeval tv;

	getmicrotime(&tv);

	if (chip->sm_state == NANDSIM_STATE_TIMEOUT &&
	    timevalcmp(&tv, &chip->delay_tv, >=)) {
		nandchip_set_status(chip, NAND_STATUS_RDY);
	}
}
void
poweron_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{
	uint8_t cmd;

	if (type == NANDSIM_EV_START)
		chip->sm_state = NANDSIM_STATE_IDLE;
	else if (type == NANDSIM_EV_CMD) {
		cmd = *(uint8_t *)data;
		switch(cmd) {
		case NAND_CMD_RESET:
			nandsim_log(chip, NANDSIM_LOG_SM, "in RESET state\n");
			nandsim_start_handler(chip, reset_evh);
			break;
		default:
			nandsim_undefined(chip, type);
			break;
		}
	} else
		nandsim_undefined(chip, type);
}

void
idle_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{
	uint8_t cmd;

	if (type == NANDSIM_EV_START) {
		nandsim_log(chip, NANDSIM_LOG_SM, "in IDLE state\n");
		chip->sm_state = NANDSIM_STATE_WAIT_CMD;
	} else if (type == NANDSIM_EV_CMD) {
		nandchip_clear_status(chip, NAND_STATUS_FAIL);
		getmicrotime(&chip->delay_tv);
		cmd = *(uint8_t *)data;
		switch(cmd) {
		case NAND_CMD_READ_ID:
			nandsim_start_handler(chip, readid_evh);
			break;
		case NAND_CMD_READ_PARAMETER:
			nandsim_start_handler(chip, readparam_evh);
			break;
		case NAND_CMD_READ:
			nandsim_start_handler(chip, read_evh);
			break;
		case NAND_CMD_PROG:
			nandsim_start_handler(chip, write_evh);
			break;
		case NAND_CMD_ERASE:
			nandsim_start_handler(chip, erase_evh);
			break;
		default:
			nandsim_undefined(chip, type);
			break;
		}
	} else
		nandsim_undefined(chip, type);
}

void
readid_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{
	struct onfi_params *params;
	uint8_t addr;

	params = &chip->params;

	if (type == NANDSIM_EV_START) {
		nandsim_log(chip, NANDSIM_LOG_SM, "in READID state\n");
		chip->sm_state = NANDSIM_STATE_WAIT_ADDR_BYTE;
	} else if (type == NANDSIM_EV_ADDR) {

		addr = *((uint8_t *)data);

		if (addr == 0x0)
			nandchip_set_data(chip, (uint8_t *)&chip->id, 2, 0);
		else if (addr == ONFI_SIG_ADDR)
			nandchip_set_data(chip, (uint8_t *)&params->signature,
			    4, 0);
		else
			nandsim_bad_address(chip, &addr);

		nandsim_start_handler(chip, idle_evh);
	} else
		nandsim_undefined(chip, type);
}

void
readparam_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{
	struct onfi_params *params;
	uint8_t addr;

	params = &chip->params;

	if (type == NANDSIM_EV_START) {
		nandsim_log(chip, NANDSIM_LOG_SM, "in READPARAM state\n");
		chip->sm_state = NANDSIM_STATE_WAIT_ADDR_BYTE;
	} else if (type == NANDSIM_EV_ADDR) {
		addr = *((uint8_t *)data);

		if (addr == 0) {
			nandchip_set_data(chip, (uint8_t *)params,
			    sizeof(*params), 0);
		} else
			nandsim_bad_address(chip, &addr);

		nandsim_start_handler(chip, idle_evh);
	} else
		nandsim_undefined(chip, type);
}

void
read_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{
	static uint32_t column = 0, row = 0;
	uint32_t size;
	uint8_t cmd;

	size = chip->cg.page_size + chip->cg.oob_size;

	switch (type) {
	case NANDSIM_EV_START:
		nandsim_log(chip, NANDSIM_LOG_SM, "in READ state\n");
		chip->sm_state = NANDSIM_STATE_WAIT_ADDR_COL;
		break;
	case NANDSIM_EV_ADDR:
		if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL) {
			if (nandchip_get_addr_byte(chip, data, &column))
				break;

			chip->sm_state = NANDSIM_STATE_WAIT_ADDR_ROW;
		} else if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) {
			if (nandchip_get_addr_byte(chip, data, &row))
				break;

			chip->sm_state = NANDSIM_STATE_WAIT_CMD;
		} else
			nandsim_ignore_address(chip, *((uint8_t *)data));
		break;
	case NANDSIM_EV_CMD:
		cmd = *(uint8_t *)data;
		if (chip->sm_state == NANDSIM_STATE_WAIT_CMD &&
		    cmd == NAND_CMD_READ_END) {
			if (chip->read_delay != 0 &&
			    nandsim_delay(chip, chip->read_delay) == 0)
				nandchip_clear_status(chip, NAND_STATUS_RDY);
			else {
				nandchip_chip_space(chip, row, column, size, 0);
				nandchip_set_status(chip, NAND_STATUS_RDY);
				nandsim_start_handler(chip, idle_evh);
			}
		} else
			nandsim_undefined(chip, type);
		break;
	case NANDSIM_EV_TIMEOUT:
		if (chip->sm_state == NANDSIM_STATE_TIMEOUT) {
			nandchip_chip_space(chip, row, column, size, 0);
			nandchip_set_status(chip, NAND_STATUS_RDY);
			nandsim_start_handler(chip, idle_evh);
		} else
			nandsim_undefined(chip, type);
		break;
	}
}
void
write_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{
	static uint32_t column, row;
	uint32_t size;
	uint8_t cmd;
	int err;

	size = chip->cg.page_size + chip->cg.oob_size;

	switch(type) {
	case NANDSIM_EV_START:
		nandsim_log(chip, NANDSIM_LOG_SM, "in WRITE state\n");
		chip->sm_state = NANDSIM_STATE_WAIT_ADDR_COL;
		break;
	case NANDSIM_EV_ADDR:
		if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_COL) {
			if (nandchip_get_addr_byte(chip, data, &column))
				break;

			chip->sm_state = NANDSIM_STATE_WAIT_ADDR_ROW;
		} else if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) {
			if (nandchip_get_addr_byte(chip, data, &row))
				break;

			err = nandchip_chip_space(chip, row, column, size, 1);
			if (err == -1)
				nandchip_set_status(chip, NAND_STATUS_FAIL);

			chip->sm_state = NANDSIM_STATE_WAIT_CMD;
		} else
			nandsim_ignore_address(chip, *((uint8_t *)data));
		break;
	case NANDSIM_EV_CMD:
		cmd = *(uint8_t *)data;
		if (chip->sm_state == NANDSIM_STATE_WAIT_CMD &&
		    cmd == NAND_CMD_PROG_END) {
			if (chip->prog_delay != 0 &&
			    nandsim_delay(chip, chip->prog_delay) == 0)
				nandchip_clear_status(chip, NAND_STATUS_RDY);
			else {
				nandchip_set_status(chip, NAND_STATUS_RDY);
				nandsim_start_handler(chip, idle_evh);
			}
		} else
			nandsim_undefined(chip, type);
		break;
	case NANDSIM_EV_TIMEOUT:
		if (chip->sm_state == NANDSIM_STATE_TIMEOUT) {
			nandsim_start_handler(chip, idle_evh);
			nandchip_set_status(chip, NAND_STATUS_RDY);
		} else
			nandsim_undefined(chip, type);
		break;
	}
}

void
erase_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{
	static uint32_t row, block_size;
	uint32_t lun, block, page;
	int err;
	uint8_t cmd;

	block_size = chip->cg.block_size +
	    (chip->cg.oob_size * chip->cg.pgs_per_blk);

	switch (type) {
	case NANDSIM_EV_START:
		nandsim_log(chip, NANDSIM_LOG_SM, "in ERASE state\n");
		chip->sm_state = NANDSIM_STATE_WAIT_ADDR_ROW;
		break;
	case NANDSIM_EV_CMD:
		cmd = *(uint8_t *)data;
		if (chip->sm_state == NANDSIM_STATE_WAIT_CMD &&
		    cmd == NAND_CMD_ERASE_END) {
			if (chip->data.data_ptr != NULL &&
			    chip->data.size == block_size)
				memset(chip->data.data_ptr, 0xff, block_size);
			else
				nand_debug(NDBG_SIM,"Bad block erase data\n");

			err = nand_row_to_blkpg(&chip->cg, row, &lun,
			    &block, &page);
			if (!err) {
				if (chip->blk_state[block].wear_lev > 0)
					chip->blk_state[block].wear_lev--;
			}

			if (chip->erase_delay != 0 &&
			    nandsim_delay(chip, chip->erase_delay) == 0)
				nandchip_clear_status(chip, NAND_STATUS_RDY);
			else {
				nandchip_set_status(chip, NAND_STATUS_RDY);
				nandsim_start_handler(chip, idle_evh);
			}
		} else
			nandsim_undefined(chip, type);
		break;
	case NANDSIM_EV_ADDR:
		if (chip->sm_state == NANDSIM_STATE_WAIT_ADDR_ROW) {
			if (nandchip_get_addr_byte(chip, data, &row))
				break;

			err = nandchip_chip_space(chip, row, 0, block_size, 1);
			if (err == -1) {
				nandchip_set_status(chip, NAND_STATUS_FAIL);
			}
			chip->sm_state = NANDSIM_STATE_WAIT_CMD;
		} else
			nandsim_ignore_address(chip, *((uint8_t *)data));
		break;
	case NANDSIM_EV_TIMEOUT:
		if (chip->sm_state == NANDSIM_STATE_TIMEOUT) {
			nandchip_set_status(chip, NAND_STATUS_RDY);
			nandsim_start_handler(chip, idle_evh);
		} else
			nandsim_undefined(chip, type);
		break;
	}
}

void
reset_evh(struct nandsim_chip *chip, uint32_t type, void *data)
{

	if (type == NANDSIM_EV_START) {
		nandsim_log(chip, NANDSIM_LOG_SM, "in RESET state\n");
		chip->sm_state = NANDSIM_STATE_TIMEOUT;
		nandchip_set_data(chip, NULL, 0, 0);
		DELAY(500);
		nandsim_start_handler(chip, idle_evh);
	} else
		nandsim_undefined(chip, type);
}

static void
nandsim_undefined(struct nandsim_chip *chip, uint8_t type)
{

	nandsim_log(chip, NANDSIM_LOG_ERR,
	    "ERR: Chip received ev %x in state %x\n",
	    type, chip->sm_state);
	nandsim_start_handler(chip, idle_evh);
}

static void
nandsim_bad_address(struct nandsim_chip *chip, uint8_t *addr)
{

	nandsim_log(chip, NANDSIM_LOG_ERR,
	    "ERR: Chip received out of range address"
	    "%02x%02x - %02x%02x%02x\n", addr[0], addr[1], addr[2],
	    addr[3], addr[4]);
}

static void
nandsim_ignore_address(struct nandsim_chip *chip, uint8_t byte)
{
	nandsim_log(chip, NANDSIM_LOG_SM, "ignored address byte: %d\n", byte);
}

static void
nandsim_sm_error(struct nandsim_chip *chip)
{

	nandsim_log(chip, NANDSIM_LOG_ERR, "ERR: State machine error."
	    "Restart required.\n");
}