From bc8e8a4bfa0f7519573b79f5fff610370545b4e1 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Sat, 4 Nov 2023 16:37:52 -0400 Subject: nand: Add sandbox driver 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. An exception is made for the ECC layout, since that is difficult to encode in the device tree and is not a property of the device itself. 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 --- test/dm/Makefile | 1 + test/dm/nand.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 test/dm/nand.c (limited to 'test') 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..0b992fdce1c --- /dev/null +++ b/test/dm/nand.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Sean Anderson + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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)); + + /* 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] = 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); -- cgit v1.3.1 From 8502b5bf20505408773d98fbc6e9307cb962e8b0 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Sat, 4 Nov 2023 16:37:53 -0400 Subject: test: spl: Add a test for NAND Add a SPL test for the NAND load method. We use some different functions to do the writing from the main test since things like nand_write_skip_bad aren't available in SPL. We disable BBT scanning, since scan_bbt is only populated when not in SPL. We use nand_spl_loaders.c as it seems to be common to at least a few boards already. However, we do not use nand_spl_simple.c because it would require us to implement cmd_ctrl. The various nand load functions are adapted from omap_gpmc. However, they have been modified for simplicity/correctness. Signed-off-by: Sean Anderson --- arch/sandbox/include/asm/spl.h | 1 + configs/sandbox_noinst_defconfig | 11 ++++++++ drivers/mtd/nand/raw/Kconfig | 5 +++- drivers/mtd/nand/raw/sand_nand.c | 29 +++++++++++++++++++++ test/image/Kconfig | 9 +++++++ test/image/Makefile | 1 + test/image/spl_load_nand.c | 54 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 test/image/spl_load_nand.c (limited to 'test') diff --git a/arch/sandbox/include/asm/spl.h b/arch/sandbox/include/asm/spl.h index f349ea19971..4fab24cd156 100644 --- a/arch/sandbox/include/asm/spl.h +++ b/arch/sandbox/include/asm/spl.h @@ -15,6 +15,7 @@ enum { BOOT_DEVICE_CPGMAC, BOOT_DEVICE_NOR, BOOT_DEVICE_SPI, + BOOT_DEVICE_NAND, }; /** diff --git a/configs/sandbox_noinst_defconfig b/configs/sandbox_noinst_defconfig index 09ebafeccca..0e5f84abbd8 100644 --- a/configs/sandbox_noinst_defconfig +++ b/configs/sandbox_noinst_defconfig @@ -51,6 +51,13 @@ CONFIG_SPL_ETH=y CONFIG_SPL_FS_EXT4=y CONFIG_SPL_I2C=y CONFIG_SPL_MMC_WRITE=y +CONFIG_SPL_MTD=y +CONFIG_SPL_NAND_SUPPORT=y +CONFIG_SPL_NAND_DRIVERS=y +CONFIG_SPL_NAND_ECC=y +CONFIG_SPL_NAND_SOFTECC=y +CONFIG_SPL_NAND_BASE=y +CONFIG_SPL_NAND_IDENT=y CONFIG_SPL_DM_SPI_FLASH=y CONFIG_SPL_NET=y CONFIG_SPL_NOR_SUPPORT=y @@ -183,12 +190,16 @@ CONFIG_FS_LOADER=y CONFIG_MMC_SANDBOX=y CONFIG_MTD=y CONFIG_DM_MTD=y +CONFIG_MTD_CONCAT=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_BLOCK_SIZE=0x2000 CONFIG_SYS_NAND_ONFI_DETECTION=y CONFIG_SYS_NAND_PAGE_SIZE=0x200 +CONFIG_SYS_NAND_U_BOOT_LOCATIONS=y +CONFIG_SYS_NAND_U_BOOT_OFFS=0x0 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 4ab5459452f..bb9994b8626 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -451,6 +451,8 @@ config NAND_SANDBOX bool "Support for NAND in sandbox" depends on SANDBOX select SYS_NAND_SELF_INIT + select SPL_SYS_NAND_SELF_INIT + select SPL_NAND_INIT select SYS_NAND_SOFT_ECC select BCH select NAND_ECC_BCH @@ -678,7 +680,8 @@ config SYS_NAND_PAGE_SIZE depends on ARCH_SUNXI || NAND_OMAP_GPMC || NAND_LPC32XX_SLC || \ SPL_NAND_SIMPLE || (NAND_MXC && SPL_NAND_SUPPORT) || \ MVEBU_SPL_BOOT_DEVICE_NAND || \ - (NAND_ATMEL && SPL_NAND_SUPPORT) || SPL_GENERATE_ATMEL_PMECC_HEADER + (NAND_ATMEL && SPL_NAND_SUPPORT) || \ + SPL_GENERATE_ATMEL_PMECC_HEADER || NAND_SANDBOX depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC && !NAND_MT7621 help Number of data bytes in one page for the NAND chip on the diff --git a/drivers/mtd/nand/raw/sand_nand.c b/drivers/mtd/nand/raw/sand_nand.c index 9b34146fea1..229d7b5b65a 100644 --- a/drivers/mtd/nand/raw/sand_nand.c +++ b/drivers/mtd/nand/raw/sand_nand.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -599,6 +601,7 @@ static int sand_nand_probe(struct udevice *dev) } nand = &chip->nand; + nand->options = spl_in_proper() ? 0 : NAND_SKIP_BBTSCAN; nand->flash_node = np; nand->dev_ready = sand_nand_dev_ready; nand->cmdfunc = sand_nand_command; @@ -676,3 +679,29 @@ void board_nand_init(void) if (err && err != -ENODEV) log_info("Failed to get sandbox NAND: %d\n", err); } + +#if IS_ENABLED(CONFIG_SPL_BUILD) && IS_ENABLED(CONFIG_SPL_NAND_INIT) +void nand_deselect(void) +{ + nand_chip->select_chip(nand_to_mtd(nand_chip), -1); +} + +static int nand_is_bad_block(int block) +{ + struct mtd_info *mtd = nand_to_mtd(nand_chip); + + return mtd_block_isbad(mtd, block << mtd->erasesize_shift); +} + +static int nand_read_page(int block, int page, uchar *dst) +{ + struct mtd_info *mtd = nand_to_mtd(nand_chip); + loff_t ofs = ((loff_t)block << mtd->erasesize_shift) + + ((loff_t)page << mtd->writesize_shift); + size_t len = mtd->writesize; + + return nand_read(mtd, ofs, &len, dst); +} + +#include "nand_spl_loaders.c" +#endif /* CONFIG_SPL_NAND_INIT */ diff --git a/test/image/Kconfig b/test/image/Kconfig index 8f9e6ae036b..6f0bb81f835 100644 --- a/test/image/Kconfig +++ b/test/image/Kconfig @@ -23,6 +23,15 @@ config SPL_UT_LOAD_FS help Test filesystems and the various load methods which use them. +config SPL_UT_LOAD_NAND + bool "Test loading from NAND flash" + depends on SANDBOX && SPL_OF_REAL + depends on SPL_NAND_SUPPORT + depends on SPL_MTD + default y + help + Test the NAND flash load method. + config SPL_UT_LOAD_NET bool "Test loading over TFTP" depends on SANDBOX && SPL_OF_REAL diff --git a/test/image/Makefile b/test/image/Makefile index b30210106a4..11ed25734e8 100644 --- a/test/image/Makefile +++ b/test/image/Makefile @@ -4,6 +4,7 @@ obj-y += spl_load.o obj-$(CONFIG_SPL_UT_LOAD_FS) += spl_load_fs.o +obj-$(CONFIG_SPL_UT_LOAD_NAND) += spl_load_nand.o obj-$(CONFIG_SPL_UT_LOAD_NET) += spl_load_net.o obj-$(CONFIG_SPL_NOR_SUPPORT) += spl_load_nor.o obj-$(CONFIG_SPL_UT_LOAD_OS) += spl_load_os.o diff --git a/test/image/spl_load_nand.c b/test/image/spl_load_nand.c new file mode 100644 index 00000000000..30179de98e7 --- /dev/null +++ b/test/image/spl_load_nand.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Sean Anderson + */ + +#include +#include +#include +#include + +uint32_t spl_nand_get_uboot_raw_page(void); + +static int spl_test_nand_write_image(struct unit_test_state *uts, void *img, + size_t img_size) +{ + uint32_t off = spl_nand_get_uboot_raw_page(); + struct mtd_info *mtd; + struct erase_info erase = { }; + size_t length; + + nand_reinit(); + mtd = get_nand_dev_by_index(0); + ut_assertnonnull(mtd); + + /* Mark the first block as bad to test that it gets skipped */ + ut_assertok(mtd_block_markbad(mtd, off & ~mtd->erasesize_mask)); + off += mtd->erasesize; + + erase.mtd = mtd; + erase.len = img_size + (off & mtd->erasesize_mask); + erase.len += mtd->erasesize_mask; + erase.len &= ~mtd->erasesize_mask; + erase.addr = off & ~mtd->erasesize_mask; + erase.scrub = 1; + ut_assertok(mtd_erase(mtd, &erase)); + + ut_assertok(mtd_write(mtd, off, img_size, &length, img)); + + return 0; +} + +static int spl_test_nand(struct unit_test_state *uts, const char *test_name, + enum spl_test_image type) +{ + return do_spl_test_load(uts, test_name, type, + SPL_LOAD_IMAGE_GET(1, BOOT_DEVICE_NAND, + spl_nand_load_image), + spl_test_nand_write_image); +} +SPL_IMG_TEST(spl_test_nand, LEGACY, DM_FLAGS); +SPL_IMG_TEST(spl_test_nand, LEGACY_LZMA, DM_FLAGS); +SPL_IMG_TEST(spl_test_nand, IMX8, DM_FLAGS); +SPL_IMG_TEST(spl_test_nand, FIT_INTERNAL, DM_FLAGS); +SPL_IMG_TEST(spl_test_nand, FIT_EXTERNAL, DM_FLAGS); -- cgit v1.3.1