[PATCH] nand: nandsim: Implement approximation mode
Boris Brezillon
boris.brezillon at free-electrons.com
Thu May 12 05:36:50 PDT 2016
Hi Richard,
On Thu, 12 May 2016 13:32:45 +0200
Richard Weinberger <richard at nod.at> wrote:
> Sometimes all you need is a NAND with a given page, erase and chip size
> to load and inspect a certain image.
> OOB, ECC sizes and other metrics don't matter much then.
> In such a situation I find myself often fiddling around with NAND IDs
> to get what I need, especially when the given NAND ID from the datasheet
> decode to something else than the NAND advertises via ONFI.
> Using the new nandsim.approx module parameter it is possible to specify
> the page size, number of pages per block and the total number of blocks.
> Nandsim will then emulate a SLC NAND with 128 bytes OOB and the given
> sizes. Nandsim achieves this by providing ONFI parameters to NAND core.
The current nandsim implementation is indeed incomplete and does not
allow for the full NAND chain emulation.
I first considered dispatching the emulation bit in the different
components (the NAND controller, the NAND chip and the generic/standard
code), but that means introduction a new infrastructure and I'm now
unsure that this is appropriate.
The other question we should ask ourselves is whether this NAND
emulation layer belongs here. I mean, we could emulate a bunch of NAND
chips and NAND controller directly in Qemu instead of trying to teach
nandsim how to emulate advanced chips.
>
> Signed-off-by: Richard Weinberger <richard at nod.at>
> ---
> drivers/mtd/nand/nand_base.c | 12 +++++----
> drivers/mtd/nand/nandsim.c | 62 +++++++++++++++++++++++++++++++++++++++-----
> include/linux/mtd/nand.h | 3 +++
> 3 files changed, 66 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
> index 557b846..25432c2 100644
> --- a/drivers/mtd/nand/nand_base.c
> +++ b/drivers/mtd/nand/nand_base.c
> @@ -3208,7 +3208,7 @@ static void sanitize_string(uint8_t *s, size_t len)
> strim(s);
> }
>
> -static u16 onfi_crc16(u16 crc, u8 const *p, size_t len)
> +u16 nand_onfi_crc16(u16 crc, u8 const *p, size_t len)
> {
> int i;
> while (len--) {
> @@ -3246,7 +3246,7 @@ static int nand_flash_detect_ext_param_page(struct mtd_info *mtd,
>
> /* Read out the Extended Parameter Page. */
> chip->read_buf(mtd, (uint8_t *)ep, len);
> - if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2)
> + if ((nand_onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2)
> != le16_to_cpu(ep->crc))) {
> pr_debug("fail in the CRC.\n");
> goto ext_out;
> @@ -3328,14 +3328,16 @@ static int nand_flash_detect_onfi(struct mtd_info *mtd, struct nand_chip *chip,
> /* Try ONFI for unknown chip or LP */
> chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1);
> if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' ||
> - chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I')
> + chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I') {
> +
> return 0;
> + }
Hm, this change seems unnecessary.
>
> chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
> for (i = 0; i < 3; i++) {
> for (j = 0; j < sizeof(*p); j++)
> ((uint8_t *)p)[j] = chip->read_byte(mtd);
> - if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) ==
> + if (nand_onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) ==
> le16_to_cpu(p->crc)) {
> break;
> }
> @@ -3442,7 +3444,7 @@ static int nand_flash_detect_jedec(struct mtd_info *mtd, struct nand_chip *chip,
> for (j = 0; j < sizeof(*p); j++)
> ((uint8_t *)p)[j] = chip->read_byte(mtd);
>
> - if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) ==
> + if (nand_onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) ==
> le16_to_cpu(p->crc))
> break;
> }
> diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c
> index a58169a2..b5be38a 100644
> --- a/drivers/mtd/nand/nandsim.c
> +++ b/drivers/mtd/nand/nandsim.c
> @@ -114,6 +114,15 @@ static u_char id_bytes[8] = {
> [3] = CONFIG_NANDSIM_FOURTH_ID_BYTE,
> [4 ... 7] = 0xFF,
> };
> +static unsigned int approx[3];
> +
> +static struct nand_onfi_params id_onfi = {
> + .sig = {'O', 'N', 'F', 'I'},
> + .manufacturer = "nandsim ",
> + .model = "nandsim ",
> +};
> +
> +static unsigned char *id_p;
>
> module_param_array(id_bytes, byte, NULL, 0400);
> module_param_named(first_id_byte, id_bytes[0], byte, 0400);
> @@ -139,6 +148,7 @@ module_param(overridesize, uint, 0400);
> module_param(cache_file, charp, 0400);
> module_param(bbt, uint, 0400);
> module_param(bch, uint, 0400);
> +module_param_array(approx, uint, NULL, 0400);
>
> MODULE_PARM_DESC(id_bytes, "The ID bytes returned by NAND Flash 'read ID' command");
> MODULE_PARM_DESC(first_id_byte, "The first byte returned by NAND Flash 'read ID' command (manufacturer ID) (obsolete)");
> @@ -174,6 +184,10 @@ MODULE_PARM_DESC(cache_file, "File to use to cache nand pages instead of mem
> MODULE_PARM_DESC(bbt, "0 OOB, 1 BBT with marker in OOB, 2 BBT with marker in data area");
> MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should "
> "be correctable in 512-byte blocks");
> +MODULE_PARM_DESC(approx, "Approximation mode, simulate a good enough NAND that satisfies"
> + " page size, block size and number of blocks."
> + " e.g. 4096,128,8192 will give you a NAND with 4KiB page size, 128 pages per"
> + " block and 8192 blocks in total.");
>
> /* The largest possible page size */
> #define NS_LARGEST_PAGE_SIZE 4096
> @@ -229,6 +243,7 @@ MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should "
> #define STATE_CMD_RESET 0x0000000C /* reset */
> #define STATE_CMD_RNDOUT 0x0000000D /* random output command */
> #define STATE_CMD_RNDOUTSTART 0x0000000E /* random output start command */
> +#define STATE_CMD_PARAM 0x0000000F /* param output start command */
> #define STATE_CMD_MASK 0x0000000F /* command states mask */
>
> /* After an address is input, the simulator goes to one of these states */
> @@ -245,7 +260,8 @@ MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should "
> #define STATE_DATAOUT 0x00001000 /* waiting for page data output */
> #define STATE_DATAOUT_ID 0x00002000 /* waiting for ID bytes output */
> #define STATE_DATAOUT_STATUS 0x00003000 /* waiting for status output */
> -#define STATE_DATAOUT_MASK 0x00007000 /* data output states mask */
> +#define STATE_DATAOUT_ONFI 0x00007000 /* waiting for ONFI output */
> +#define STATE_DATAOUT_MASK 0x0000F000 /* data output states mask */
>
> /* Previous operation is done, ready to accept new requests */
> #define STATE_READY 0x00000000
> @@ -307,7 +323,6 @@ struct nandsim {
> unsigned int nbparts;
>
> uint busw; /* flash chip bus width (8 or 16) */
> - u_char ids[8]; /* chip's ID bytes */
> uint32_t options; /* chip's characteristic bits */
> uint32_t state; /* current chip state */
> uint32_t nxstate; /* next expected state */
> @@ -408,6 +423,8 @@ static struct nandsim_operations {
> {OPT_ANY, {STATE_CMD_STATUS, STATE_DATAOUT_STATUS, STATE_READY}},
> /* Read ID */
> {OPT_ANY, {STATE_CMD_READID, STATE_ADDR_ZERO, STATE_DATAOUT_ID, STATE_READY}},
> + /* Read param */
> + {OPT_ANY, {STATE_CMD_PARAM, STATE_ADDR_ZERO, STATE_DATAOUT_ONFI, STATE_READY}},
> /* Large page devices read page */
> {OPT_LARGEPAGE, {STATE_CMD_READ0, STATE_ADDR_PAGE, STATE_CMD_READSTART | ACTION_CPY,
> STATE_DATAOUT, STATE_READY}},
> @@ -1077,6 +1094,8 @@ static char *get_state_name(uint32_t state)
> return "STATE_CMD_RNDOUT";
> case STATE_CMD_RNDOUTSTART:
> return "STATE_CMD_RNDOUTSTART";
> + case STATE_CMD_PARAM:
> + return "STATE_CMD_PARAM";
> case STATE_ADDR_PAGE:
> return "STATE_ADDR_PAGE";
> case STATE_ADDR_SEC:
> @@ -1125,6 +1144,7 @@ static int check_command(int cmd)
> case NAND_CMD_RESET:
> case NAND_CMD_RNDOUT:
> case NAND_CMD_RNDOUTSTART:
> + case NAND_CMD_PARAM:
> return 0;
>
> default:
> @@ -1164,6 +1184,8 @@ static uint32_t get_state_by_command(unsigned command)
> return STATE_CMD_RNDOUT;
> case NAND_CMD_RNDOUTSTART:
> return STATE_CMD_RNDOUTSTART;
> + case NAND_CMD_PARAM:
> + return STATE_CMD_PARAM;
> }
>
> NS_ERR("get_state_by_command: unknown command, BUG\n");
> @@ -1856,9 +1878,17 @@ static void switch_state(struct nandsim *ns)
> break;
>
> case STATE_DATAOUT_ID:
> - ns->regs.num = ns->geom.idbytes;
> + if (ns->regs.row == 0x20) {
> + ns->regs.num = sizeof(id_onfi.sig);
> + id_p = (unsigned char *)id_onfi.sig;
> + } else {
> + ns->regs.num = ns->geom.idbytes;
> + id_p = (unsigned char *)id_bytes;
> + }
> + break;
> + case STATE_DATAOUT_ONFI:
> + ns->regs.num = sizeof(id_onfi);
> break;
> -
> case STATE_DATAOUT_STATUS:
> ns->regs.count = ns->regs.num = 0;
> break;
> @@ -1951,7 +1981,11 @@ static u_char ns_nand_read_byte(struct mtd_info *mtd)
> break;
> case STATE_DATAOUT_ID:
> NS_DBG("read_byte: read ID byte %d, total = %d\n", ns->regs.count, ns->regs.num);
> - outb = ns->ids[ns->regs.count];
> + outb = id_p[ns->regs.count];
> + ns->regs.count += 1;
> + break;
> + case STATE_DATAOUT_ONFI:
> + outb = ((unsigned char *)&id_onfi)[ns->regs.count];
> ns->regs.count += 1;
> break;
> default:
> @@ -2289,10 +2323,26 @@ static int __init ns_init_module(void)
> nand->geom.idbytes = 4;
> else
> nand->geom.idbytes = 2;
> +
> + if (approx[0] && approx[1] && approx[2]) {
> + id_onfi.byte_per_page = cpu_to_le32(approx[0]);
> + id_onfi.pages_per_block = cpu_to_le32(approx[1]);
> + id_onfi.blocks_per_lun = cpu_to_le32(approx[2]);
> +
> + id_onfi.spare_bytes_per_page = cpu_to_le32(128);
> + id_onfi.lun_count = cpu_to_le32(1);
> + id_onfi.bits_per_cell = cpu_to_le32(1);
> + id_onfi.revision = cpu_to_le32(1 << 5);
> + id_onfi.crc = cpu_to_le16(nand_onfi_crc16(ONFI_CRC_BASE, (uint8_t *)&id_onfi,
> + sizeof(id_onfi) - sizeof(id_onfi.crc)));
> +
> + nand->geom.idbytes = 2;
> + id_bytes[0] = id_bytes[1] = 0xFF;
> + }
> +
> nand->regs.status = NS_STATUS_OK(nand);
> nand->nxstate = STATE_UNKNOWN;
> nand->options |= OPT_PAGE512; /* temporary value */
> - memcpy(nand->ids, id_bytes, sizeof(nand->ids));
> if (bus_width == 16) {
> nand->busw = 16;
> chip->options |= NAND_BUSWIDTH_16;
> diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
> index 56574ba..82f6db2 100644
> --- a/include/linux/mtd/nand.h
> +++ b/include/linux/mtd/nand.h
> @@ -50,6 +50,9 @@ extern int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
> /* unlocks specified locked blocks */
> extern int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
>
> +/* calculate ONFI CRC */
> +extern u16 nand_onfi_crc16(u16 crc, u8 const *p, size_t len);
> +
> /* The maximum number of NAND chips in an array */
> #define NAND_MAX_CHIPS 8
>
Just a general feedback on the whole approach. If what you really want
is a solution to skip the chip detection and manually set the chip
info, then you shouldn't call nand_scan_ident() and manually fill
the mtd and nand_chip fields instead.
Regards,
Boris
--
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
More information about the linux-mtd
mailing list