/*- * 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. */ /* Simulated NAND controller driver */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include struct sim_param sim; struct sim_ctrl_conf ctrls[MAX_SIM_DEV]; static struct cdev *nandsim_dev; static d_ioctl_t nandsim_ioctl; static void nandsim_init_sim_param(struct sim_param *); static int nandsim_create_ctrl(struct sim_ctrl *); static int nandsim_destroy_ctrl(int); static int nandsim_ctrl_status(struct sim_ctrl *); static int nandsim_create_chip(struct sim_chip *); static int nandsim_destroy_chip(struct sim_ctrl_chip *); static int nandsim_chip_status(struct sim_chip *); static int nandsim_start_ctrl(int); static int nandsim_stop_ctrl(int); static int nandsim_inject_error(struct sim_error *); static int nandsim_get_block_state(struct sim_block_state *); static int nandsim_set_block_state(struct sim_block_state *); static int nandsim_modify(struct sim_mod *); static int nandsim_dump(struct sim_dump *); static int nandsim_restore(struct sim_dump *); static int nandsim_freeze(struct sim_ctrl_chip *); static void nandsim_print_log(struct sim_log *); static struct nandsim_chip *get_nandsim_chip(uint8_t, uint8_t); static struct cdevsw nandsim_cdevsw = { .d_version = D_VERSION, .d_ioctl = nandsim_ioctl, .d_name = "nandsim", }; int nandsim_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { int ret = 0; switch (cmd) { case NANDSIM_SIM_PARAM: nandsim_init_sim_param((struct sim_param *)data); break; case NANDSIM_CREATE_CTRL: ret = nandsim_create_ctrl((struct sim_ctrl *)data); break; case NANDSIM_DESTROY_CTRL: ret = nandsim_destroy_ctrl(*(int *)data); break; case NANDSIM_STATUS_CTRL: ret = nandsim_ctrl_status((struct sim_ctrl *)data); break; case NANDSIM_CREATE_CHIP: ret = nandsim_create_chip((struct sim_chip *)data); break; case NANDSIM_DESTROY_CHIP: ret = nandsim_destroy_chip((struct sim_ctrl_chip *)data); break; case NANDSIM_STATUS_CHIP: ret = nandsim_chip_status((struct sim_chip *)data); break; case NANDSIM_MODIFY: ret = nandsim_modify((struct sim_mod *)data); break; case NANDSIM_START_CTRL: ret = nandsim_start_ctrl(*(int *)data); break; case NANDSIM_STOP_CTRL: ret = nandsim_stop_ctrl(*(int *)data); break; case NANDSIM_INJECT_ERROR: ret = nandsim_inject_error((struct sim_error *)data); break; case NANDSIM_SET_BLOCK_STATE: ret = nandsim_set_block_state((struct sim_block_state *)data); break; case NANDSIM_GET_BLOCK_STATE: ret = nandsim_get_block_state((struct sim_block_state *)data); break; case NANDSIM_PRINT_LOG: nandsim_print_log((struct sim_log *)data); break; case NANDSIM_DUMP: ret = nandsim_dump((struct sim_dump *)data); break; case NANDSIM_RESTORE: ret = nandsim_restore((struct sim_dump *)data); break; case NANDSIM_FREEZE: ret = nandsim_freeze((struct sim_ctrl_chip *)data); break; default: ret = EINVAL; break; } return (ret); } static void nandsim_init_sim_param(struct sim_param *param) { if (!param) return; nand_debug(NDBG_SIM,"log level:%d output %d", param->log_level, param->log_output); nandsim_log_level = param->log_level; nandsim_log_output = param->log_output; } static int nandsim_create_ctrl(struct sim_ctrl *ctrl) { struct sim_ctrl_conf *sim_ctrl; nand_debug(NDBG_SIM,"create controller num:%d cs:%d",ctrl->num, ctrl->num_cs); if (ctrl->num >= MAX_SIM_DEV) { return (EINVAL); } sim_ctrl = &ctrls[ctrl->num]; if(sim_ctrl->created) return (EEXIST); sim_ctrl->num = ctrl->num; sim_ctrl->num_cs = ctrl->num_cs; sim_ctrl->ecc = ctrl->ecc; memcpy(sim_ctrl->ecc_layout, ctrl->ecc_layout, MAX_ECC_BYTES * sizeof(ctrl->ecc_layout[0])); strlcpy(sim_ctrl->filename, ctrl->filename, FILENAME_SIZE); sim_ctrl->created = 1; return (0); } static int nandsim_destroy_ctrl(int ctrl_num) { nand_debug(NDBG_SIM,"destroy controller num:%d", ctrl_num); if (ctrl_num >= MAX_SIM_DEV) { return (EINVAL); } if (!ctrls[ctrl_num].created) { return (ENODEV); } if (ctrls[ctrl_num].running) { return (EBUSY); } memset(&ctrls[ctrl_num], 0, sizeof(ctrls[ctrl_num])); return (0); } static int nandsim_ctrl_status(struct sim_ctrl *ctrl) { nand_debug(NDBG_SIM,"status controller num:%d cs:%d",ctrl->num, ctrl->num_cs); if (ctrl->num >= MAX_SIM_DEV) { return (EINVAL); } ctrl->num_cs = ctrls[ctrl->num].num_cs; ctrl->ecc = ctrls[ctrl->num].ecc; memcpy(ctrl->ecc_layout, ctrls[ctrl->num].ecc_layout, MAX_ECC_BYTES * sizeof(ctrl->ecc_layout[0])); strlcpy(ctrl->filename, ctrls[ctrl->num].filename, FILENAME_SIZE); ctrl->running = ctrls[ctrl->num].running; ctrl->created = ctrls[ctrl->num].created; return (0); } static int nandsim_create_chip(struct sim_chip *chip) { struct sim_chip *sim_chip; nand_debug(NDBG_SIM,"create chip num:%d at ctrl:%d", chip->num, chip->ctrl_num); if (chip->ctrl_num >= MAX_SIM_DEV || chip->num >= MAX_CTRL_CS) { return (EINVAL); } if (ctrls[chip->ctrl_num].chips[chip->num]) { return (EEXIST); } sim_chip = malloc(sizeof(*sim_chip), M_NANDSIM, M_WAITOK); if (sim_chip == NULL) { return (ENOMEM); } memcpy(sim_chip, chip, sizeof(*sim_chip)); ctrls[chip->ctrl_num].chips[chip->num] = sim_chip; sim_chip->created = 1; return (0); } static int nandsim_destroy_chip(struct sim_ctrl_chip *chip) { struct sim_ctrl_conf *ctrl_conf; nand_debug(NDBG_SIM,"destroy chip num:%d at ctrl:%d", chip->chip_num, chip->ctrl_num); if (chip->ctrl_num >= MAX_SIM_DEV || chip->chip_num >= MAX_CTRL_CS) return (EINVAL); ctrl_conf = &ctrls[chip->ctrl_num]; if (!ctrl_conf->created || !ctrl_conf->chips[chip->chip_num]) return (ENODEV); if (ctrl_conf->running) return (EBUSY); free(ctrl_conf->chips[chip->chip_num], M_NANDSIM); ctrl_conf->chips[chip->chip_num] = NULL; return (0); } static int nandsim_chip_status(struct sim_chip *chip) { struct sim_ctrl_conf *ctrl_conf; nand_debug(NDBG_SIM,"status for chip num:%d at ctrl:%d", chip->num, chip->ctrl_num); if (chip->ctrl_num >= MAX_SIM_DEV && chip->num >= MAX_CTRL_CS) return (EINVAL); ctrl_conf = &ctrls[chip->ctrl_num]; if (!ctrl_conf->chips[chip->num]) chip->created = 0; else memcpy(chip, ctrl_conf->chips[chip->num], sizeof(*chip)); return (0); } static int nandsim_start_ctrl(int num) { device_t nexus, ndev; devclass_t nexus_devclass; int ret = 0; nand_debug(NDBG_SIM,"start ctlr num:%d", num); if (num >= MAX_SIM_DEV) return (EINVAL); if (!ctrls[num].created) return (ENODEV); if (ctrls[num].running) return (EBUSY); /* We will add our device as a child of the nexus0 device */ if (!(nexus_devclass = devclass_find("nexus")) || !(nexus = devclass_get_device(nexus_devclass, 0))) return (EFAULT); /* * Create a newbus device representing this frontend instance * * XXX powerpc nexus doesn't implement bus_add_child, so child * must be added by device_add_child(). */ #if defined(__powerpc__) ndev = device_add_child(nexus, "nandsim", num); #else ndev = BUS_ADD_CHILD(nexus, 0, "nandsim", num); #endif if (!ndev) return (EFAULT); mtx_lock(&Giant); ret = device_probe_and_attach(ndev); mtx_unlock(&Giant); if (ret == 0) { ctrls[num].sim_ctrl_dev = ndev; ctrls[num].running = 1; } return (ret); } static int nandsim_stop_ctrl(int num) { device_t nexus; devclass_t nexus_devclass; int ret = 0; nand_debug(NDBG_SIM,"stop controller num:%d", num); if (num >= MAX_SIM_DEV) { return (EINVAL); } if (!ctrls[num].created || !ctrls[num].running) { return (ENODEV); } /* We will add our device as a child of the nexus0 device */ if (!(nexus_devclass = devclass_find("nexus")) || !(nexus = devclass_get_device(nexus_devclass, 0))) { return (ENODEV); } mtx_lock(&Giant); if (ctrls[num].sim_ctrl_dev) { ret = device_delete_child(nexus, ctrls[num].sim_ctrl_dev); ctrls[num].sim_ctrl_dev = NULL; } mtx_unlock(&Giant); ctrls[num].running = 0; return (ret); } static struct nandsim_chip * get_nandsim_chip(uint8_t ctrl_num, uint8_t chip_num) { struct nandsim_softc *sc; if (!ctrls[ctrl_num].sim_ctrl_dev) return (NULL); sc = device_get_softc(ctrls[ctrl_num].sim_ctrl_dev); return (sc->chips[chip_num]); } static void nandsim_print_log(struct sim_log *sim_log) { struct nandsim_softc *sc; int len1, len2; if (!ctrls[sim_log->ctrl_num].sim_ctrl_dev) return; sc = device_get_softc(ctrls[sim_log->ctrl_num].sim_ctrl_dev); if (sc->log_buff) { len1 = strlen(&sc->log_buff[sc->log_idx + 1]); if (len1 >= sim_log->len) len1 = sim_log->len; copyout(&sc->log_buff[sc->log_idx + 1], sim_log->log, len1); len2 = strlen(sc->log_buff); if (len2 >= (sim_log->len - len1)) len2 = (sim_log->len - len1); copyout(sc->log_buff, &sim_log->log[len1], len2); sim_log->len = len1 + len2; } } static int nandsim_inject_error(struct sim_error *error) { struct nandsim_chip *chip; struct block_space *bs; struct onfi_params *param; int page, page_size, block, offset; nand_debug(NDBG_SIM,"inject error for chip %d at ctrl %d\n", error->chip_num, error->ctrl_num); if (error->ctrl_num >= MAX_SIM_DEV || error->chip_num >= MAX_CTRL_CS) return (EINVAL); if (!ctrls[error->ctrl_num].created || !ctrls[error->ctrl_num].running) return (ENODEV); chip = get_nandsim_chip(error->ctrl_num, error->chip_num); param = &chip->params; page_size = param->bytes_per_page + param->spare_bytes_per_page; block = error->page_num / param->pages_per_block; page = error->page_num % param->pages_per_block; bs = get_bs(chip->swap, block, 1); if (!bs) return (EINVAL); offset = (page * page_size) + error->column; memset(&bs->blk_ptr[offset], error->pattern, error->len); return (0); } static int nandsim_set_block_state(struct sim_block_state *bs) { struct onfi_params *params; struct nandsim_chip *chip; int blocks; nand_debug(NDBG_SIM,"set block state for %d:%d block %d\n", bs->chip_num, bs->ctrl_num, bs->block_num); if (bs->ctrl_num >= MAX_SIM_DEV || bs->chip_num >= MAX_CTRL_CS) return (EINVAL); chip = get_nandsim_chip(bs->ctrl_num, bs->chip_num); params = &chip->params; blocks = params->luns * params->blocks_per_lun; if (bs->block_num > blocks) return (EINVAL); chip->blk_state[bs->block_num].is_bad = bs->state; if (bs->wearout >= 0) chip->blk_state[bs->block_num].wear_lev = bs->wearout; return (0); } static int nandsim_get_block_state(struct sim_block_state *bs) { struct onfi_params *params; struct nandsim_chip *chip; int blocks; if (bs->ctrl_num >= MAX_SIM_DEV || bs->chip_num >= MAX_CTRL_CS) return (EINVAL); nand_debug(NDBG_SIM,"get block state for %d:%d block %d\n", bs->chip_num, bs->ctrl_num, bs->block_num); chip = get_nandsim_chip(bs->ctrl_num, bs->chip_num); params = &chip->params; blocks = params->luns * params->blocks_per_lun; if (bs->block_num > blocks) return (EINVAL); bs->state = chip->blk_state[bs->block_num].is_bad; bs->wearout = chip->blk_state[bs->block_num].wear_lev; return (0); } static int nandsim_dump(struct sim_dump *dump) { struct nandsim_chip *chip; struct block_space *bs; int blk_size; nand_debug(NDBG_SIM,"dump chip %d %d\n", dump->ctrl_num, dump->chip_num); if (dump->ctrl_num >= MAX_SIM_DEV || dump->chip_num >= MAX_CTRL_CS) return (EINVAL); chip = get_nandsim_chip(dump->ctrl_num, dump->chip_num); blk_size = chip->cg.block_size + (chip->cg.oob_size * chip->cg.pgs_per_blk); bs = get_bs(chip->swap, dump->block_num, 0); if (!bs) return (EINVAL); if (dump->len > blk_size) dump->len = blk_size; copyout(bs->blk_ptr, dump->data, dump->len); return (0); } static int nandsim_restore(struct sim_dump *dump) { struct nandsim_chip *chip; struct block_space *bs; int blk_size; nand_debug(NDBG_SIM,"restore chip %d %d\n", dump->ctrl_num, dump->chip_num); if (dump->ctrl_num >= MAX_SIM_DEV || dump->chip_num >= MAX_CTRL_CS) return (EINVAL); chip = get_nandsim_chip(dump->ctrl_num, dump->chip_num); blk_size = chip->cg.block_size + (chip->cg.oob_size * chip->cg.pgs_per_blk); bs = get_bs(chip->swap, dump->block_num, 1); if (!bs) return (EINVAL); if (dump->len > blk_size) dump->len = blk_size; copyin(dump->data, bs->blk_ptr, dump->len); return (0); } static int nandsim_freeze(struct sim_ctrl_chip *ctrl_chip) { struct nandsim_chip *chip; if (ctrl_chip->ctrl_num >= MAX_SIM_DEV || ctrl_chip->chip_num >= MAX_CTRL_CS) return (EINVAL); chip = get_nandsim_chip(ctrl_chip->ctrl_num, ctrl_chip->chip_num); nandsim_chip_freeze(chip); return (0); } static int nandsim_modify(struct sim_mod *mod) { struct sim_chip *sim_conf = NULL; struct nandsim_chip *sim_chip = NULL; nand_debug(NDBG_SIM,"modify ctlr %d chip %d", mod->ctrl_num, mod->chip_num); if (mod->field != SIM_MOD_LOG_LEVEL) { if (mod->ctrl_num >= MAX_SIM_DEV || mod->chip_num >= MAX_CTRL_CS) return (EINVAL); sim_conf = ctrls[mod->ctrl_num].chips[mod->chip_num]; sim_chip = get_nandsim_chip(mod->ctrl_num, mod->chip_num); } switch (mod->field) { case SIM_MOD_LOG_LEVEL: nandsim_log_level = mod->new_value; break; case SIM_MOD_ERASE_TIME: sim_conf->erase_time = sim_chip->erase_delay = mod->new_value; break; case SIM_MOD_PROG_TIME: sim_conf->prog_time = sim_chip->prog_delay = mod->new_value; break; case SIM_MOD_READ_TIME: sim_conf->read_time = sim_chip->read_delay = mod->new_value; break; case SIM_MOD_ERROR_RATIO: sim_conf->error_ratio = mod->new_value; sim_chip->error_ratio = mod->new_value; break; default: break; } return (0); } static int nandsim_modevent(module_t mod __unused, int type, void *data __unused) { struct sim_ctrl_chip chip_ctrl; int i, j; switch (type) { case MOD_LOAD: nandsim_dev = make_dev(&nandsim_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, "nandsim.ioctl"); break; case MOD_UNLOAD: for (i = 0; i < MAX_SIM_DEV; i++) { nandsim_stop_ctrl(i); chip_ctrl.ctrl_num = i; for (j = 0; j < MAX_CTRL_CS; j++) { chip_ctrl.chip_num = j; nandsim_destroy_chip(&chip_ctrl); } nandsim_destroy_ctrl(i); } destroy_dev(nandsim_dev); break; case MOD_SHUTDOWN: break; default: return (EOPNOTSUPP); } return (0); } DEV_MODULE(nandsim, nandsim_modevent, NULL); MODULE_VERSION(nandsim, 1); MODULE_DEPEND(nandsim, nand, 1, 1, 1); MODULE_DEPEND(nandsim, alq, 1, 1, 1);