Sean, All On Sun, Oct 29, 2023 at 4:49 AM Sean Anderson <sean...@gmail.com> wrote: > > Add a sandbox NAND flash driver to facilitate testing. This driver supports > any number of devices, each using a single chip-select. The OOB data is > stored in-band, with the separation enforced through the API. > > For now, create two devices to test with. The first is a very small device > with basic ECC. The second is an 8G device (chosen to be larger than 32 > bits). It uses ONFI, with the values copied from the datasheet. It also > doesn't need too strong ECC, which speeds things up. > > Although the nand subsystem determines the parameters of a chip based on > the ID, the driver itself requires devicetree properties for each > parameter. We do not derive parameters from the ID because parsing the ID > is non-trivial. We do not just use the parameters that the nand subsystem > has calculated since that is something we should be testing. > > Despite using file I/O to access the backing data, we do not support using > external files. In my experience, these are unnecessary for testing since > tests can generally be written to write their expected data beforehand. > Additionally, we would need to store the "programmed" information somewhere > (complicating the format and the programming process) or try to detect > whether block are erased at runtime (degrading probe speeds). > > Information about whether each page has been programmed is stored in an > in-memory buffer. To simplify the implementation, we only support a single > program per erase. While this is accurate for many larger flashes, some > smaller flashes (512 byte) support multiple programs and/or subpage > programs. Support for this could be added later as I believe some > filesystems expect this. > > To test ECC, we support error-injection. Surprisingly, only ECC bytes in > the OOB area are protected, even though all bytes are equally susceptible > to error. Because of this, we take care to only corrupt ECC bytes. > Similarly, because ECC covers "steps" and not the whole page, we must take > care to corrupt data in the same way. > > Signed-off-by: Sean Anderson <sean...@gmail.com> > --- > > arch/sandbox/dts/test.dts | 67 +++ > configs/sandbox64_defconfig | 10 +- > configs/sandbox_defconfig | 9 + > configs/sandbox_noinst_defconfig | 10 +- > drivers/mtd/nand/raw/Kconfig | 14 + > drivers/mtd/nand/raw/Makefile | 1 + > drivers/mtd/nand/raw/sand_nand.c | 682 +++++++++++++++++++++++++++++++ > test/dm/Makefile | 1 + > test/dm/nand.c | 106 +++++ > 9 files changed, 898 insertions(+), 2 deletions(-) > create mode 100644 drivers/mtd/nand/raw/sand_nand.c > create mode 100644 test/dm/nand.c > > diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts > index 5b54651a1da..5fba2943fad 100644 > --- a/arch/sandbox/dts/test.dts > +++ b/arch/sandbox/dts/test.dts > @@ -1900,6 +1900,73 @@ > compatible = "sandbox,arm-ffa"; > }; > }; > + > + nand-controller { > + #address-cells = <1>; > + #size-cells = <0>; > + compatible = "sandbox,nand"; > + > + nand@0 { > + reg = <0>; > + nand-ecc-mode = "soft"; > + sandbox,id = [00 e3]; > + sandbox,erasesize = <(8 * 1024)>; > + sandbox,oobsize = <16>; > + sandbox,pagesize = <512>; > + sandbox,pages = <0x2000>; > + sandbox,err-count = <1>; > + sandbox,err-step-size = <512>; > + sandbox,ecc-bytes = <6>; > + }; > + > + /* MT29F64G08AKABA */ > + nand@1 { > + reg = <1>; > + nand-ecc-mode = "soft_bch"; > + sandbox,id = [2C 48 00 26 89 00 00 00]; > + sandbox,onfi = [ > + 4f 4e 46 49 0e 00 5a 00 > + ff 01 00 00 00 00 03 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 4d 49 43 52 4f 4e 20 20 > + 20 20 20 20 4d 54 32 39 > + 46 36 34 47 30 38 41 4b > + 41 42 41 43 35 20 20 20 > + 2c 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 10 00 00 e0 00 00 02 > + 00 00 1c 00 80 00 00 00 > + 00 10 00 00 02 23 01 50 > + 00 01 05 01 00 00 04 00 > + 04 01 1e 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 0e 1f 00 1f 00 f4 01 ac > + 0d 19 00 c8 00 00 00 00 > + 00 00 00 00 00 00 0a 07 > + 19 00 00 00 00 00 00 00 > + 00 00 00 00 01 00 01 00 > + 00 00 04 10 01 81 04 02 > + 02 01 1e 90 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 00 00 00 > + 00 00 00 00 00 03 20 7d > + ]; > + sandbox,erasesize = <(512 * 1024)>; > + sandbox,oobsize = <224>; > + sandbox,pagesize = <4096>; > + sandbox,pages = <0x200000>; > + sandbox,err-count = <3>; > + sandbox,err-step-size = <512>; > + sandbox,ecc-bytes = <7>; > + }; > + }; > }; > > #include "sandbox_pmic.dtsi" > diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig > index 1a01f51a0b7..d4e5eb3000b 100644 > --- a/configs/sandbox64_defconfig > +++ b/configs/sandbox64_defconfig > @@ -1,4 +1,5 @@ > CONFIG_TEXT_BASE=0 > +CONFIG_SYS_MALLOC_LEN=0x6000000 > CONFIG_NR_DRAM_BANKS=1 > CONFIG_ENV_SIZE=0x2000 > CONFIG_DEFAULT_DEVICE_TREE="sandbox64" > @@ -50,7 +51,7 @@ CONFIG_CMD_GPT_RENAME=y > CONFIG_CMD_IDE=y > CONFIG_CMD_I2C=y > CONFIG_CMD_LOADM=y > -CONFIG_CMD_MBR=y > +CONFIG_CMD_MTD=y > CONFIG_CMD_OSD=y > CONFIG_CMD_PCI=y > CONFIG_CMD_READ=y > @@ -168,6 +169,13 @@ CONFIG_PWRSEQ=y > CONFIG_I2C_EEPROM=y > CONFIG_MMC_SANDBOX=y > CONFIG_MTD=y > +CONFIG_DM_MTD=y > +CONFIG_MTD_RAW_NAND=y > +CONFIG_SYS_MAX_NAND_DEVICE=8 > +CONFIG_SYS_NAND_USE_FLASH_BBT=y > +CONFIG_NAND_SANDBOX=y > +CONFIG_SYS_NAND_ONFI_DETECTION=y > +CONFIG_SYS_NAND_PAGE_SIZE=0x200 > CONFIG_SPI_FLASH_SANDBOX=y > CONFIG_BOOTDEV_SPI_FLASH=y > CONFIG_SPI_FLASH_ATMEL=y > diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig > index e3a2f9eb170..1c71f0542a3 100644 > --- a/configs/sandbox_defconfig > +++ b/configs/sandbox_defconfig > @@ -1,4 +1,5 @@ > CONFIG_TEXT_BASE=0 > +CONFIG_SYS_MALLOC_LEN=0x6000000 > CONFIG_NR_DRAM_BANKS=1 > CONFIG_ENV_SIZE=0x2000 > CONFIG_DEFAULT_DEVICE_TREE="sandbox" > @@ -73,6 +74,7 @@ CONFIG_CMD_IDE=y > CONFIG_CMD_I2C=y > CONFIG_CMD_LOADM=y > CONFIG_CMD_LSBLK=y > +CONFIG_CMD_MTD=y > CONFIG_CMD_MUX=y > CONFIG_CMD_OSD=y > CONFIG_CMD_PCI=y > @@ -215,6 +217,13 @@ CONFIG_MMC_PCI=y > CONFIG_MMC_SANDBOX=y > CONFIG_MMC_SDHCI=y > CONFIG_MTD=y > +CONFIG_DM_MTD=y > +CONFIG_MTD_RAW_NAND=y > +CONFIG_SYS_MAX_NAND_DEVICE=8 > +CONFIG_SYS_NAND_USE_FLASH_BBT=y > +CONFIG_NAND_SANDBOX=y > +CONFIG_SYS_NAND_ONFI_DETECTION=y > +CONFIG_SYS_NAND_PAGE_SIZE=0x200 > CONFIG_SPI_FLASH_SANDBOX=y > CONFIG_BOOTDEV_SPI_FLASH=y > CONFIG_SPI_FLASH_ATMEL=y > diff --git a/configs/sandbox_noinst_defconfig > b/configs/sandbox_noinst_defconfig > index db05e630832..09ebafeccca 100644 > --- a/configs/sandbox_noinst_defconfig > +++ b/configs/sandbox_noinst_defconfig > @@ -80,7 +80,7 @@ CONFIG_CMD_GPIO=y > CONFIG_CMD_GPT=y > CONFIG_CMD_IDE=y > CONFIG_CMD_I2C=y > -CONFIG_CMD_MBR=y > +CONFIG_CMD_MTD=y > CONFIG_CMD_OSD=y > CONFIG_CMD_PCI=y > CONFIG_CMD_REMOTEPROC=y > @@ -181,6 +181,14 @@ CONFIG_PWRSEQ=y > CONFIG_SPL_PWRSEQ=y > CONFIG_FS_LOADER=y > CONFIG_MMC_SANDBOX=y > +CONFIG_MTD=y > +CONFIG_DM_MTD=y > +CONFIG_MTD_RAW_NAND=y > +CONFIG_SYS_MAX_NAND_DEVICE=8 > +CONFIG_SYS_NAND_USE_FLASH_BBT=y > +CONFIG_NAND_SANDBOX=y > +CONFIG_SYS_NAND_ONFI_DETECTION=y > +CONFIG_SYS_NAND_PAGE_SIZE=0x200 > CONFIG_SPI_FLASH_SANDBOX=y > CONFIG_SPI_FLASH_ATMEL=y > CONFIG_SPI_FLASH_EON=y > diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig > index ee484dc0f51..6b34aa6782c 100644 > --- a/drivers/mtd/nand/raw/Kconfig > +++ b/drivers/mtd/nand/raw/Kconfig > @@ -447,6 +447,20 @@ config NAND_PXA3XX > This enables the driver for the NAND flash device found on > PXA3xx processors (NFCv1) and also on Armada 370/XP (NFCv2). > > +config NAND_SANDBOX > + bool "Support for NAND in sandbox" > + depends on SANDBOX > + select SYS_NAND_SELF_INIT > + select SYS_NAND_SOFT_ECC > + select BCH > + select NAND_ECC_BCH > + imply CMD_NAND > + help > + Enable a dummy NAND driver for sandbox. It simulates any number of > + arbitrary NAND chips with a RAM buffer. It will also inject errors > to > + test ECC. At the moment, only 8-bit busses and single-chip devices > are > + supported. > + > config NAND_SUNXI > bool "Support for NAND on Allwinner SoCs" > default ARCH_SUNXI > diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile > index add2b4cf655..ddbba899e58 100644 > --- a/drivers/mtd/nand/raw/Makefile > +++ b/drivers/mtd/nand/raw/Makefile > @@ -70,6 +70,7 @@ obj-$(CONFIG_NAND_PXA3XX) += pxa3xx_nand.o > obj-$(CONFIG_TEGRA_NAND) += tegra_nand.o > obj-$(CONFIG_NAND_OMAP_GPMC) += omap_gpmc.o > obj-$(CONFIG_NAND_OMAP_ELM) += omap_elm.o > +obj-$(CONFIG_NAND_SANDBOX) += sand_nand.o > obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o > obj-$(CONFIG_NAND_MXIC) += mxic_nand.o > obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o > diff --git a/drivers/mtd/nand/raw/sand_nand.c > b/drivers/mtd/nand/raw/sand_nand.c > new file mode 100644 > index 00000000000..f1855877630 > --- /dev/null > +++ b/drivers/mtd/nand/raw/sand_nand.c > @@ -0,0 +1,682 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (C) Sean Anderson <sean...@gmail.com> > + */ > + > +#define LOG_CATEGORY UCLASS_MTD > +#include <errno.h> > +#include <hexdump.h> > +#include <log.h> > +#include <nand.h> > +#include <os.h> > +#include <rand.h> > +#include <dm/device_compat.h> > +#include <dm/read.h> > +#include <dm/uclass.h> > +#include <asm/bitops.h> > +#include <linux/bitmap.h> > +#include <linux/mtd/rawnand.h> > +#include <linux/sizes.h> > + > +enum sand_nand_state { > + STATE_READY, > + STATE_IDLE, > + STATE_READ, > + STATE_READ_ID, > + STATE_READ_ONFI, > + STATE_PARAM_ONFI, > + STATE_STATUS, > + STATE_PROG, > + STATE_ERASE, > +}; > + > +static const char *const state_name[] = { > + [STATE_READY] = "READY", > + [STATE_IDLE] = "IDLE", > + [STATE_READ] = "READ", > + [STATE_READ_ID] = "READ_ID", > + [STATE_READ_ONFI] = "READ_ONFI", > + [STATE_PARAM_ONFI] = "PARAM_ONFI", > + [STATE_STATUS] = "STATUS", > + [STATE_PROG] = "PROG", > + [STATE_ERASE] = "ERASE", > +}; > + > +/** > + * struct sand_nand_chip - Per-device private data > + * @nand: The nand chip > + * @node: The next device in this controller > + * @programmed: Bitmap of whether sectors are programmed > + * @id: ID to report for NAND_CMD_READID > + * @id_len: Length of @id > + * @onfi: Three copies of ONFI parameter page > + * @status: Status to report for NAND_CMD_STATUS > + * @chunksize: Size of one "chunk" (page + oob) in bytes > + * @pageize: Size of one page in bytes > + * @pages: Total number of pages > + * @pages_per_erase: Number of pages per eraseblock > + * @err_count: Number of errors to inject per @err_step_bits of data > + * @err_step_bits: Number of data bits per error "step" > + * @err_steps: Number of err steps in a page > + * @ecc_bits: Number of ECC bits uper @err_step_bits > + * @cs: Chip select for this device > + * @state: Current state of the device > + * @column: Column of the most-recent command > + * @page_addr: Page address of the most-recent command > + * @fd: File descriptor for the backing data > + * @fd_page_addr: Page address that @fd is seek'd to > + * @selected: Whether this device is selected > + * @tmp: "Cache" buffer used to store transferred data before committing it > + * @tmp_dirty: Whether @tmp is dirty (modified) or clean (all ones) > + */ > +struct sand_nand_chip { > + struct nand_chip nand; > + struct list_head node; > + long *programmed; > + const u8 *id; > + u32 chunksize, pagesize, pages, pages_per_erase; > + u32 err_count, err_step_bits, err_steps, ecc_bits; > + unsigned int cs; > + enum sand_nand_state state; > + int column, page_addr, fd, fd_page_addr; > + bool selected, tmp_dirty; > + u8 status;
I see a character "^L". Please remove it. Thanks and regards, Dario > + u8 id_len; > + u8 tmp[NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE]; > + u8 onfi[sizeof(struct nand_onfi_params) * 3]; > +}; > + > +#define SAND_DEBUG(chip, fmt, ...) \ > + dev_dbg((chip)->nand.mtd.dev, "%u (%s): " fmt, (chip)->cs, \ > + state_name[(chip)->state], ##__VA_ARGS__) > + > +static inline void to_state(struct sand_nand_chip *chip, > + enum sand_nand_state new_state) > +{ > + if (new_state != chip->state) > + SAND_DEBUG(chip, "to state %s\n", state_name[new_state]); > + chip->state = new_state; > +} > + > +static inline struct sand_nand_chip *to_sand_nand(struct nand_chip *nand) > +{ > + return container_of(nand, struct sand_nand_chip, nand); > +} > + > +struct sand_nand_priv { > + struct list_head chips; > +}; > + > +static int sand_nand_dev_ready(struct mtd_info *mtd) > +{ > + return 1; > +} > + > +static int sand_nand_wait(struct mtd_info *mtd, struct nand_chip *chip) > +{ > + u8 status; > + > + return nand_status_op(chip, &status) ?: status; > +} > + > +static int sand_nand_seek(struct sand_nand_chip *chip) > +{ > + if (chip->fd_page_addr == chip->page_addr) > + return 0; > + > + if (os_lseek(chip->fd, (off_t)chip->page_addr * chip->chunksize, > + OS_SEEK_SET) < 0) { > + SAND_DEBUG(chip, "could not seek: %d\n", errno); > + return -EIO; > + } > + > + chip->fd_page_addr = chip->page_addr; > + return 0; > +} > + > +static void sand_nand_inject_error(struct sand_nand_chip *chip, > + unsigned int step, unsigned int pos) > +{ > + int byte, index; > + > + if (pos < chip->err_step_bits) { > + __change_bit(step * chip->err_step_bits + pos, chip->tmp); > + return; > + } > + > + /* > + * Only ECC bytes are covered in the OOB area, so > + * pretend that those are the only bytes which can have > + * errors. > + */ > + byte = (pos - chip->err_step_bits + step * chip->ecc_bits) / 8; > + index = chip->nand.ecc.layout->eccpos[byte]; > + /* Avoid endianness issues by working with bytes */ > + chip->tmp[chip->pagesize + index] ^= BIT(pos & 0x7); > +} > + > +static int sand_nand_read(struct sand_nand_chip *chip) > +{ > + unsigned int i, stop = 0; > + > + if (chip->column == chip->pagesize) > + stop = chip->err_step_bits; > + > + if (test_bit(chip->page_addr, chip->programmed)) { > + if (sand_nand_seek(chip)) > + return -EIO; > + > + if (os_read(chip->fd, chip->tmp, chip->chunksize) != > + chip->chunksize) { > + SAND_DEBUG(chip, "could not read: %d\n", errno); > + return -EIO; > + } > + chip->fd_page_addr++; > + } else if (chip->tmp_dirty) { > + memset(chip->tmp + chip->column, 0xff, > + chip->chunksize - chip->column); > + } > + > + /* > + * Inject some errors; this is Method A from "An Efficient Algorithm > for > + * Sequential Random Sampling" (Vitter 87). This is still slow when > + * generating a lot (dozens) of ECC errors. > + * > + * To avoid generating too many errors in any one ECC step, we > separate > + * our error generation by ECC step. > + */ > + chip->tmp_dirty = true; > + for (i = 0; i < chip->err_steps; i++) { > + u32 bit_errors = chip->err_count; > + unsigned int j = chip->err_step_bits + chip->ecc_bits; > + > + while (bit_errors) { > + unsigned int u = rand(); > + float quot = 1ULL << 32; > + > + do { > + quot *= j - bit_errors; > + quot /= j; > + j--; > + > + if (j < stop) > + goto next; > + } while (u < quot); > + > + sand_nand_inject_error(chip, i, j); > + bit_errors--; > + } > +next: > + ; > + } > + > + return 0; > +} > + > +static void sand_nand_command(struct mtd_info *mtd, unsigned int command, > + int column, int page_addr) > +{ > + struct nand_chip *nand = mtd_to_nand(mtd); > + struct sand_nand_chip *chip = to_sand_nand(nand); > + enum sand_nand_state new_state = chip->state; > + > + SAND_DEBUG(chip, "command=%02x column=%d page_addr=%d\n", command, > + column, page_addr); > + > + if (!chip->selected) > + return; > + > + switch (chip->state) { > + case STATE_READY: > + if (command == NAND_CMD_RESET) > + goto reset; > + break; > + case STATE_PROG: > + new_state = STATE_IDLE; > + if (command != NAND_CMD_PAGEPROG || > + test_and_set_bit(chip->page_addr, chip->programmed)) { > + chip->status |= NAND_STATUS_FAIL; > + break; > + } > + > + if (sand_nand_seek(chip)) { > + chip->status |= NAND_STATUS_FAIL; > + break; > + } > + > + if (os_write(chip->fd, chip->tmp, chip->chunksize) != > + chip->chunksize) { > + SAND_DEBUG(chip, "could not write: %d\n", errno); > + chip->status |= NAND_STATUS_FAIL; > + break; > + } > + > + chip->fd_page_addr++; > + break; > + case STATE_ERASE: > + new_state = STATE_IDLE; > + if (command != NAND_CMD_ERASE2) { > + chip->status |= NAND_STATUS_FAIL; > + break; > + } > + > + if (chip->page_addr < 0 || > + chip->page_addr >= chip->pages || > + chip->page_addr % chip->pages_per_erase) > + chip->status |= NAND_STATUS_FAIL; > + else > + bitmap_clear(chip->programmed, chip->page_addr, > + chip->pages_per_erase); > + break; > + default: > + chip->column = column; > + chip->page_addr = page_addr; > + switch (command) { > + case NAND_CMD_READOOB: > + if (column >= 0) > + chip->column += chip->pagesize; > + fallthrough; > + case NAND_CMD_READ0: > + new_state = STATE_IDLE; > + if (page_addr < 0 || page_addr >= chip->pages) > + break; > + > + if (chip->column < 0 || chip->column >= > chip->chunksize) > + break; > + > + if (sand_nand_read(chip)) > + break; > + > + chip->page_addr = page_addr; > + new_state = STATE_READ; > + break; > + case NAND_CMD_ERASE1: > + new_state = STATE_ERASE; > + chip->status = ~NAND_STATUS_FAIL; > + break; > + case NAND_CMD_STATUS: > + new_state = STATE_STATUS; > + chip->column = 0; > + break; > + case NAND_CMD_SEQIN: > + new_state = STATE_PROG; > + chip->status = ~NAND_STATUS_FAIL; > + if (page_addr < 0 || page_addr >= chip->pages || > + chip->column < 0 || > + chip->column >= chip->chunksize) { > + chip->status |= NAND_STATUS_FAIL; > + } else if (chip->tmp_dirty) { > + memset(chip->tmp, 0xff, chip->chunksize); > + chip->tmp_dirty = false; > + } > + break; > + case NAND_CMD_READID: > + if (chip->onfi[0] && column == 0x20) > + new_state = STATE_READ_ONFI; > + else > + new_state = STATE_READ_ID; > + chip->column = 0; > + break; > + case NAND_CMD_PARAM: > + if (chip->onfi[0] && !column) > + new_state = STATE_PARAM_ONFI; > + else > + new_state = STATE_IDLE; > + break; > + case NAND_CMD_RESET: > +reset: > + new_state = STATE_IDLE; > + chip->column = -1; > + chip->page_addr = -1; > + chip->status = ~NAND_STATUS_FAIL; > + break; > + default: > + new_state = STATE_IDLE; > + SAND_DEBUG(chip, "Unsupported command %02x\n", > command); > + } > + } > + > + to_state(chip, new_state); > +} > + > +static void sand_nand_select_chip(struct mtd_info *mtd, int n) > +{ > + struct nand_chip *nand = mtd_to_nand(mtd); > + struct sand_nand_chip *chip = to_sand_nand(nand); > + > + chip->selected = !n; > +} > + > +static void sand_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len) > +{ > + struct nand_chip *nand = mtd_to_nand(mtd); > + struct sand_nand_chip *chip = to_sand_nand(nand); > + unsigned int to_copy; > + int src_len = 0; > + const u8 *src = NULL; > + > + if (!chip->selected) > + goto copy; > + > + switch (chip->state) { > + case STATE_READ: > + src = chip->tmp; > + src_len = chip->chunksize; > + break; > + case STATE_READ_ID: > + src = chip->id; > + src_len = chip->id_len; > + break; > + case STATE_READ_ONFI: > + src = "ONFI"; > + src_len = 4; > + break; > + case STATE_PARAM_ONFI: > + src = chip->onfi; > + src_len = sizeof(chip->onfi); > + break; > + case STATE_STATUS: > + src = &chip->status; > + src_len = 1; > + break; > + default: > + break; > + } > + > +copy: > + if (chip->column >= 0) > + to_copy = max(min(len, src_len - chip->column), 0); > + else > + to_copy = 0; > + memcpy(buf, src + chip->column, to_copy); > + memset(buf + to_copy, 0xff, len - to_copy); > + chip->column += to_copy; > + > + if (len == 1) { > + SAND_DEBUG(chip, "read [ %02x ]\n", buf[0]); > + } else if (src_len) { > + SAND_DEBUG(chip, "read %d bytes\n", len); > +#ifdef VERBOSE_DEBUG > + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len); > +#endif > + } > + > + if (src_len && chip->column == src_len) > + to_state(chip, STATE_IDLE); > +} > + > +static u8 sand_nand_read_byte(struct mtd_info *mtd) > +{ > + u8 ret; > + > + sand_nand_read_buf(mtd, &ret, 1); > + return ret; > +} > + > +static u16 sand_nand_read_word(struct mtd_info *mtd) > +{ > + struct nand_chip *nand = mtd_to_nand(mtd); > + struct sand_nand_chip *chip = to_sand_nand(nand); > + > + SAND_DEBUG(chip, "16-bit access unsupported\n"); > + return sand_nand_read_byte(mtd) | 0xff00; > +} > + > +static void sand_nand_write_buf(struct mtd_info *mtd, const u8 *buf, int len) > +{ > + struct nand_chip *nand = mtd_to_nand(mtd); > + struct sand_nand_chip *chip = to_sand_nand(nand); > + > + SAND_DEBUG(chip, "write %d bytes\n", len); > +#ifdef VERBOSE_DEBUG > + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len); > +#endif > + > + if (chip->state != STATE_PROG || chip->status & NAND_STATUS_FAIL) > + return; > + > + chip->tmp_dirty = true; > + len = min((unsigned int)len, chip->chunksize - chip->column); > + memcpy(chip->tmp + chip->column, buf, len); > + chip->column += len; > +} > + > +static struct nand_chip *nand_chip; > + > +int sand_nand_remove(struct udevice *dev) > +{ > + struct sand_nand_priv *priv = dev_get_priv(dev); > + struct sand_nand_chip *chip; > + > + list_for_each_entry(chip, &priv->chips, node) { > + struct nand_chip *nand = &chip->nand; > + > + if (nand_chip == nand) > + nand_chip = NULL; > + > + nand_unregister(nand_to_mtd(nand)); > + free(chip->programmed); > + os_close(chip->fd); > + free(chip); > + } > + > + return 0; > +} > + > +static int sand_nand_probe(struct udevice *dev) > +{ > + struct sand_nand_priv *priv = dev_get_priv(dev); > + struct sand_nand_chip *chip; > + int ret, devnum = 0; > + ofnode np; > + > + INIT_LIST_HEAD(&priv->chips); > + > + dev_for_each_subnode(np, dev) { > + struct nand_chip *nand; > + struct mtd_info *mtd; > + u32 erasesize, oobsize, pagesize, pages; > + u32 err_count, err_step_size, ecc_bytes; > + off_t expected_size; > + char filename[30]; > + fdt_addr_t cs; > + const u8 *id, *onfi; > + int id_len, onfi_len; > + > + cs = ofnode_get_addr_size_index_notrans(np, 0, NULL); > + if (cs == FDT_ADDR_T_NONE) { > + dev_dbg(dev, "Invalid cs for chip %s\n", > + ofnode_get_name(np)); > + ret = -ENOENT; > + goto err; > + } > + > + id = ofnode_read_prop(np, "sandbox,id", &id_len); > + if (!id) { > + dev_dbg(dev, "No sandbox,id property for chip %s\n", > + ofnode_get_name(np)); > + ret = -EINVAL; > + goto err; > + } > + > + onfi = ofnode_read_prop(np, "sandbox,onfi", &onfi_len); > + if (onfi && onfi_len != sizeof(struct nand_onfi_params)) { > + dev_dbg(dev, "Invalid length %d for onfi params\n", > + onfi_len); > + ret = -EINVAL; > + goto err; > + } > + > + ret = ofnode_read_u32(np, "sandbox,erasesize", &erasesize); > + if (ret) { > + dev_dbg(dev, "No sandbox,erasesize property for chip > %s", > + ofnode_get_name(np)); > + goto err; > + } > + > + ret = ofnode_read_u32(np, "sandbox,oobsize", &oobsize); > + if (ret) { > + dev_dbg(dev, "No sandbox,oobsize property for chip > %s", > + ofnode_get_name(np)); > + goto err; > + } > + > + ret = ofnode_read_u32(np, "sandbox,pagesize", &pagesize); > + if (ret) { > + dev_dbg(dev, "No sandbox,pagesize property for chip > %s", > + ofnode_get_name(np)); > + goto err; > + } > + > + ret = ofnode_read_u32(np, "sandbox,pages", &pages); > + if (ret) { > + dev_dbg(dev, "No sandbox,pages property for chip %s", > + ofnode_get_name(np)); > + goto err; > + } > + > + ret = ofnode_read_u32(np, "sandbox,err-count", &err_count); > + if (ret) { > + dev_dbg(dev, > + "No sandbox,err-count property for chip %s", > + ofnode_get_name(np)); > + goto err; > + } > + > + ret = ofnode_read_u32(np, "sandbox,err-step-size", > + &err_step_size); > + if (ret) { > + dev_dbg(dev, > + "No sandbox,err-step-size property for chip > %s", > + ofnode_get_name(np)); > + goto err; > + } > + > + ret = ofnode_read_u32(np, "sandbox,ecc-bytes", &ecc_bytes); > + if (ret) { > + dev_dbg(dev, > + "No sandbox,ecc-bytes property for chip %s", > + ofnode_get_name(np)); > + goto err; > + } > + > + chip = calloc(sizeof(*chip), 1); > + if (!chip) { > + ret = -ENOMEM; > + goto err; > + } > + > + chip->cs = cs; > + chip->id = id; > + chip->id_len = id_len; > + chip->chunksize = pagesize + oobsize; > + chip->pagesize = pagesize; > + chip->pages = pages; > + chip->pages_per_erase = erasesize / pagesize; > + memset(chip->tmp, 0xff, chip->chunksize); > + > + chip->err_count = err_count; > + chip->err_step_bits = err_step_size * 8; > + chip->err_steps = pagesize / err_step_size; > + chip->ecc_bits = ecc_bytes * 8; > + > + expected_size = (off_t)pages * chip->chunksize; > + snprintf(filename, sizeof(filename), > + "/tmp/u-boot.nand%d.XXXXXX", devnum); > + chip->fd = os_mktemp(filename, expected_size); > + if (chip->fd < 0) { > + dev_dbg(dev, "Could not create temp file %s\n", > + filename); > + ret = chip->fd; > + goto err_chip; > + } > + > + chip->programmed = calloc(sizeof(long), > + BITS_TO_LONGS(pages)); > + if (!chip->programmed) { > + ret = -ENOMEM; > + goto err_fd; > + } > + > + if (onfi) { > + memcpy(chip->onfi, onfi, onfi_len); > + memcpy(chip->onfi + onfi_len, onfi, onfi_len); > + memcpy(chip->onfi + 2 * onfi_len, onfi, onfi_len); > + } > + > + nand = &chip->nand; > + nand->flash_node = np; > + nand->dev_ready = sand_nand_dev_ready; > + nand->cmdfunc = sand_nand_command; > + nand->waitfunc = sand_nand_wait; > + nand->select_chip = sand_nand_select_chip; > + nand->read_byte = sand_nand_read_byte; > + nand->read_word = sand_nand_read_word; > + nand->read_buf = sand_nand_read_buf; > + nand->write_buf = sand_nand_write_buf; > + nand->ecc.options = NAND_ECC_GENERIC_ERASED_CHECK; > + > + mtd = nand_to_mtd(nand); > + mtd->dev = dev; > + > + ret = nand_scan(mtd, CONFIG_SYS_NAND_MAX_CHIPS); > + if (ret) { > + dev_dbg(dev, "Could not scan chip %s: %d\n", > + ofnode_get_name(np), ret); > + goto err_prog; > + } > + > + ret = nand_register(devnum, mtd); > + if (ret) { > + dev_dbg(dev, "Could not register nand %d: %d\n", > devnum, > + ret); > + goto err_prog; > + } > + > + if (!nand_chip) > + nand_chip = nand; > + > + list_add_tail(&chip->node, &priv->chips); > + devnum++; > + continue; > + > +err_prog: > + free(chip->programmed); > +err_fd: > + os_close(chip->fd); > +err_chip: > + free(chip); > + goto err; > + } > + > + return 0; > + > +err: > + sand_nand_remove(dev); > + return ret; > +} > + > +static const struct udevice_id sand_nand_ids[] = { > + { .compatible = "sandbox,nand" }, > + { } > +}; > + > +U_BOOT_DRIVER(sand_nand) = { > + .name = "sand-nand", > + .id = UCLASS_MTD, > + .of_match = sand_nand_ids, > + .probe = sand_nand_probe, > + .remove = sand_nand_remove, > + .priv_auto = sizeof(struct sand_nand_priv), > +}; > + > +void board_nand_init(void) > +{ > + struct udevice *dev; > + int err; > + > + err = uclass_get_device_by_driver(UCLASS_MTD, > DM_DRIVER_REF(sand_nand), > + &dev); > + if (err && err != -ENODEV) > + log_info("Failed to get sandbox NAND: %d\n", err); > +} > diff --git a/test/dm/Makefile b/test/dm/Makefile > index cb82d839f8a..a3ce7b3889f 100644 > --- a/test/dm/Makefile > +++ b/test/dm/Makefile > @@ -73,6 +73,7 @@ obj-$(CONFIG_CMD_MUX) += mux-cmd.o > obj-$(CONFIG_MULTIPLEXER) += mux-emul.o > obj-$(CONFIG_MUX_MMIO) += mux-mmio.o > obj-y += fdtdec.o > +obj-$(CONFIG_MTD_RAW_NAND) += nand.o > obj-$(CONFIG_UT_DM) += nop.o > obj-y += ofnode.o > obj-y += ofread.o > diff --git a/test/dm/nand.c b/test/dm/nand.c > new file mode 100644 > index 00000000000..a1304965072 > --- /dev/null > +++ b/test/dm/nand.c > @@ -0,0 +1,106 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (C) 2023 Sean Anderson <sean...@gmail.com> > + */ > + > +#include <nand.h> > +#include <part.h> > +#include <rand.h> > +#include <dm/test.h> > +#include <test/test.h> > +#include <test/ut.h> > +#include <linux/mtd/mtd.h> > +#include <linux/mtd/rawnand.h> > + > +static int dm_test_nand(struct unit_test_state *uts, int dev, bool end) > +{ > + nand_erase_options_t opts = { }; > + struct mtd_info *mtd; > + size_t length; > + loff_t size; > + char *buf; > + int *gold; > + u8 oob[NAND_MAX_OOBSIZE]; > + int i; > + loff_t off = 0; > + mtd_oob_ops_t ops = { }; > + > + /* Seed RNG for bit errors */ > + srand((off >> 32) ^ off ^ ~dev); > + > + mtd = get_nand_dev_by_index(dev); > + ut_assertnonnull(mtd); > + size = mtd->erasesize * 4; > + length = size; > + > + buf = malloc(size); > + ut_assertnonnull(buf); > + gold = malloc(size); > + ut_assertnonnull(gold); > + > + /* Mark a block as bad */ > + ut_assertok(mtd_block_markbad(mtd, off + mtd->erasesize)); > + /* Save the OOB for later */ > + ut_assertok(mtd_read_oob(mtd, mtd->erasesize, &ops)); > + > + /* Erase some stuff */ > + if (end) > + off = mtd->size - size - mtd->erasesize; > + opts.offset = off; > + opts.length = size; > + opts.spread = 1; > + opts.lim = U32_MAX; > + ut_assertok(nand_erase_opts(mtd, &opts)); > + > + /* Make sure everything is erased */ > + memset(gold, 0xff, size); > + ut_assertok(nand_read_skip_bad(mtd, off, &length, NULL, U64_MAX, > buf)); > + ut_asserteq(size, length); > + ut_asserteq_mem(gold, buf, size); > + > + /* ...but our bad block marker is still there */ > + ops.oobbuf = oob; > + ops.ooblen = mtd->oobsize; > + ut_assertok(mtd_read_oob(mtd, mtd->erasesize, &ops)); > + ut_asserteq(0, oob[mtd_to_nand(mtd)->badblockpos]); > + > + /* Generate some data and write it */ > + for (i = 0; i < size / sizeof(int); i++) > + gold[i] = 0;//rand(); > + ut_assertok(nand_write_skip_bad(mtd, off, &length, NULL, U64_MAX, > + (void *)gold, 0)); > + ut_asserteq(size, length); > + > + /* Verify */ > + ut_assertok(nand_read_skip_bad(mtd, off, &length, NULL, U64_MAX, > buf)); > + ut_asserteq(size, length); > + ut_asserteq_mem(gold, buf, size); > + > + /* Erase some blocks */ > + memset(((char *)gold) + mtd->erasesize, 0xff, mtd->erasesize * 2); > + opts.offset = off + mtd->erasesize; > + opts.length = mtd->erasesize * 2, > + ut_assertok(nand_erase_opts(mtd, &opts)); > + > + /* Verify */ > + ut_assertok(nand_read_skip_bad(mtd, off, &length, NULL, U64_MAX, > buf)); > + ut_asserteq(size, length); > + ut_asserteq_mem(gold, buf, size); > + > + return 0; > +} > + > +#define DM_NAND_TEST(dev) \ > +static int dm_test_nand##dev##_start(struct unit_test_state *uts) \ > +{ \ > + return dm_test_nand(uts, dev, false); \ > +} \ > +DM_TEST(dm_test_nand##dev##_start, UT_TESTF_SCAN_FDT); \ > +static int dm_test_nand##dev##_end(struct unit_test_state *uts) \ > +{ \ > + return dm_test_nand(uts, dev, true); \ > +} \ > +DM_TEST(dm_test_nand##dev##_end, UT_TESTF_SCAN_FDT) > + > +DM_NAND_TEST(0); > +DM_NAND_TEST(1); > -- > 2.37.1 > -- Dario Binacchi Senior Embedded Linux Developer dario.binac...@amarulasolutions.com __________________________________ Amarula Solutions SRL Via Le Canevare 30, 31100 Treviso, Veneto, IT T. +39 042 243 5310 i...@amarulasolutions.com www.amarulasolutions.com